Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / tests / isolate_format_test.py
1 #!/usr/bin/env python
2 # Copyright 2014 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 that
4 # can be found in the LICENSE file.
5
6 import cStringIO
7 import logging
8 import os
9 import sys
10 import tempfile
11 import unittest
12
13 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 sys.path.insert(0, ROOT_DIR)
15
16 import isolate_format
17 import run_isolated
18 # Create shortcuts.
19 from isolate_format import KEY_TOUCHED, KEY_TRACKED, KEY_UNTRACKED
20
21
22 # Access to a protected member XXX of a client class
23 # pylint: disable=W0212
24
25
26 FAKE_DIR = (
27     u'z:\\path\\to\\non_existing'
28     if sys.platform == 'win32' else u'/path/to/non_existing')
29
30
31 class IsolateFormatTest(unittest.TestCase):
32   def test_unknown_key(self):
33     try:
34       isolate_format.verify_variables({'foo': [],})
35       self.fail()
36     except AssertionError:
37       pass
38
39   def test_unknown_var(self):
40     try:
41       isolate_format.verify_condition({'variables': {'foo': [],}}, {})
42       self.fail()
43     except AssertionError:
44       pass
45
46   def test_eval_content(self):
47     try:
48       # Intrinsics are not available.
49       isolate_format.eval_content('map(str, [1, 2])')
50       self.fail()
51     except NameError:
52       pass
53
54   def test_load_isolate_as_config_empty(self):
55     expected = {
56       (): {
57         'isolate_dir': FAKE_DIR,
58       },
59     }
60     self.assertEqual(
61         expected,
62         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).flatten())
63
64   def test_load_isolate_as_config(self):
65     value = {
66       'conditions': [
67         ['OS=="amiga" or OS=="atari" or OS=="coleco" or OS=="dendy"', {
68           'variables': {
69             KEY_TRACKED: ['a'],
70             KEY_UNTRACKED: ['b'],
71             KEY_TOUCHED: ['touched'],
72           },
73         }],
74         ['OS=="atari"', {
75           'variables': {
76             KEY_TRACKED: ['c', 'x'],
77             KEY_UNTRACKED: ['d'],
78             KEY_TOUCHED: ['touched_a'],
79             'command': ['echo', 'Hello World'],
80             'read_only': 2,
81           },
82         }],
83         ['OS=="amiga" or OS=="coleco" or OS=="dendy"', {
84           'variables': {
85             KEY_TRACKED: ['e', 'x'],
86             KEY_UNTRACKED: ['f'],
87             KEY_TOUCHED: ['touched_e'],
88             'command': ['echo', 'You should get an Atari'],
89           },
90         }],
91         ['OS=="amiga"', {
92           'variables': {
93             KEY_TRACKED: ['g'],
94             'read_only': 1,
95           },
96         }],
97         ['OS=="amiga" or OS=="atari" or OS=="dendy"', {
98           'variables': {
99             KEY_UNTRACKED: ['h'],
100           },
101         }],
102       ],
103     }
104     expected = {
105       (None,): {
106         'isolate_dir': FAKE_DIR,
107       },
108       ('amiga',): {
109         KEY_TOUCHED: ['touched', 'touched_e'],
110         KEY_TRACKED: ['a', 'e', 'g', 'x'],
111         KEY_UNTRACKED: ['b', 'f', 'h'],
112         'command': ['echo', 'You should get an Atari'],
113         'isolate_dir': FAKE_DIR,
114         'read_only': 1,
115       },
116       ('atari',): {
117         KEY_TOUCHED: ['touched', 'touched_a'],
118         KEY_TRACKED: ['a', 'c', 'x'],
119         KEY_UNTRACKED: ['b', 'd', 'h'],
120         'command': ['echo', 'Hello World'],
121         'isolate_dir': FAKE_DIR,
122         'read_only': 2,
123       },
124       ('coleco',): {
125         KEY_TOUCHED: ['touched', 'touched_e'],
126         KEY_TRACKED: ['a', 'e', 'x'],
127         KEY_UNTRACKED: ['b', 'f'],
128         'command': ['echo', 'You should get an Atari'],
129         'isolate_dir': FAKE_DIR,
130       },
131       ('dendy',): {
132         KEY_TOUCHED: ['touched', 'touched_e'],
133         KEY_TRACKED: ['a', 'e', 'x'],
134         KEY_UNTRACKED: ['b', 'f', 'h'],
135         'command': ['echo', 'You should get an Atari'],
136         'isolate_dir': FAKE_DIR,
137       },
138     }
139     self.assertEqual(
140         expected, isolate_format.load_isolate_as_config(
141             FAKE_DIR, value, None).flatten())
142
143   def test_load_isolate_as_config_duplicate_command(self):
144     value = {
145       'variables': {
146         'command': ['rm', '-rf', '/'],
147       },
148       'conditions': [
149         ['OS=="atari"', {
150           'variables': {
151             'command': ['echo', 'Hello World'],
152           },
153         }],
154       ],
155     }
156     try:
157       isolate_format.load_isolate_as_config(FAKE_DIR, value, None)
158       self.fail()
159     except AssertionError:
160       pass
161
162   def test_load_isolate_as_config_no_variable(self):
163     value = {
164       'variables': {
165         'command': ['echo', 'You should get an Atari'],
166         KEY_TRACKED: ['a'],
167         KEY_UNTRACKED: ['b'],
168         KEY_TOUCHED: ['touched'],
169         'read_only': 1,
170       },
171     }
172     # The key is the empty tuple, since there is no variable to bind to.
173     expected = {
174       (): {
175         KEY_TRACKED: ['a'],
176         KEY_UNTRACKED: ['b'],
177         KEY_TOUCHED: ['touched'],
178         'command': ['echo', 'You should get an Atari'],
179         'isolate_dir': FAKE_DIR,
180         'read_only': 1,
181       },
182     }
183     self.assertEqual(
184         expected, isolate_format.load_isolate_as_config(
185             FAKE_DIR, value, None).flatten())
186
187   def test_invert_map(self):
188     value = {
189       ('amiga',): {
190         'command': ['echo', 'You should get an Atari'],
191         KEY_TOUCHED: ['touched', 'touched_e'],
192         KEY_TRACKED: ['a', 'e', 'g', 'x'],
193         KEY_UNTRACKED: ['b', 'f', 'h'],
194         'read_only': 0,
195       },
196       ('atari',): {
197         'command': ['echo', 'Hello World'],
198         KEY_TOUCHED: ['touched', 'touched_a'],
199         KEY_TRACKED: ['a', 'c', 'x'],
200         KEY_UNTRACKED: ['b', 'd', 'h'],
201         'read_only': 1,
202       },
203       ('coleco',): {
204         'command': ['echo', 'You should get an Atari'],
205         KEY_TOUCHED: ['touched', 'touched_e'],
206         KEY_TRACKED: ['a', 'e', 'x'],
207         KEY_UNTRACKED: ['b', 'f'],
208       },
209       ('dendy',): {
210         'command': ['echo', 'You should get an Atari'],
211         KEY_TOUCHED: ['touched', 'touched_e'],
212         KEY_TRACKED: ['a', 'e', 'x'],
213         KEY_UNTRACKED: ['b', 'f', 'h'],
214       },
215     }
216     amiga, atari, coleco, dendy = (
217         set([(os,)]) for os in ('amiga', 'atari', 'coleco', 'dendy'))
218     expected_values = {
219       'command': {
220         ('echo', 'Hello World'): atari,
221         ('echo', 'You should get an Atari'): amiga | coleco | dendy,
222       },
223       KEY_TRACKED: {
224         'a': amiga | atari | coleco | dendy,
225         'c': atari,
226         'e': amiga | coleco | dendy,
227         'g': amiga,
228         'x': amiga | atari | coleco | dendy,
229       },
230       KEY_UNTRACKED: {
231         'b': amiga | atari | coleco | dendy,
232         'd': atari,
233         'f': amiga | coleco | dendy,
234         'h': amiga | atari | dendy,
235       },
236       KEY_TOUCHED: {
237         'touched': amiga | atari | coleco | dendy,
238         'touched_a': atari,
239         'touched_e': amiga | coleco | dendy,
240       },
241       'read_only': {
242         0: amiga,
243         1: atari,
244       },
245     }
246     actual_values = isolate_format.invert_map(value)
247     self.assertEqual(expected_values, actual_values)
248
249   def test_reduce_inputs(self):
250     amiga, atari, coleco, dendy = (
251         set([(os,)]) for os in ('amiga', 'atari', 'coleco', 'dendy'))
252     values = {
253       'command': {
254         ('echo', 'Hello World'): atari,
255         ('echo', 'You should get an Atari'): amiga | coleco | dendy,
256       },
257       KEY_TRACKED: {
258         'a': amiga | atari | coleco | dendy,
259         'c': atari,
260         'e': amiga | coleco | dendy,
261         'g': amiga,
262         'x': amiga | atari | coleco | dendy,
263       },
264       KEY_UNTRACKED: {
265         'b': amiga | atari | coleco | dendy,
266         'd': atari,
267         'f': amiga | coleco | dendy,
268         'h': amiga | atari | dendy,
269       },
270       KEY_TOUCHED: {
271         'touched': amiga | atari | coleco | dendy,
272         'touched_a': atari,
273         'touched_e': amiga | coleco | dendy,
274       },
275       'read_only': {
276         0: amiga,
277         1: atari,
278       },
279     }
280     expected_values = {
281       'command': {
282         ('echo', 'Hello World'): atari,
283         ('echo', 'You should get an Atari'): amiga | coleco | dendy,
284       },
285       KEY_TRACKED: {
286         'a': amiga | atari | coleco | dendy,
287         'c': atari,
288         'e': amiga | coleco | dendy,
289         'g': amiga,
290         'x': amiga | atari | coleco | dendy,
291       },
292       KEY_UNTRACKED: {
293         'b': amiga | atari | coleco | dendy,
294         'd': atari,
295         'f': amiga | coleco | dendy,
296         'h': amiga | atari | dendy,
297       },
298       KEY_TOUCHED: {
299         'touched': amiga | atari | coleco | dendy,
300         'touched_a': atari,
301         'touched_e': amiga | coleco | dendy,
302       },
303       'read_only': {
304         0: amiga,
305         1: atari,
306       },
307     }
308     actual_values = isolate_format.reduce_inputs(values)
309     self.assertEqual(expected_values, actual_values)
310
311   def test_reduce_inputs_merge_subfolders_and_files(self):
312     linux, mac, win = (set([(os,)]) for os in ('linux', 'mac', 'win'))
313     values = {
314       KEY_TRACKED: {
315         'folder/tracked_file': win,
316         'folder_helper/tracked_file': win,
317       },
318       KEY_UNTRACKED: {
319         'folder/': linux | mac | win,
320         'folder/subfolder/': win,
321         'folder/untracked_file': linux | mac | win,
322         'folder_helper/': linux,
323       },
324       KEY_TOUCHED: {
325         'folder/touched_file': win,
326         'folder/helper_folder/deep_file': win,
327         'folder_helper/touched_file1': mac | win,
328         'folder_helper/touched_file2': linux,
329       },
330     }
331     expected_values = {
332       'command': {},
333       KEY_TRACKED: {
334         'folder_helper/tracked_file': win,
335       },
336       KEY_UNTRACKED: {
337         'folder/': linux | mac | win,
338         'folder_helper/': linux,
339       },
340       KEY_TOUCHED: {
341         'folder_helper/touched_file1': mac | win,
342       },
343       'read_only': {},
344     }
345     actual_values = isolate_format.reduce_inputs(values)
346     self.assertEqual(expected_values, actual_values)
347
348   def test_reduce_inputs_take_strongest_dependency(self):
349     amiga, atari, coleco, dendy = (
350         set([(os,)]) for os in ('amiga', 'atari', 'coleco', 'dendy'))
351     values = {
352       'command': {
353         ('echo', 'Hello World'): atari,
354         ('echo', 'You should get an Atari'): amiga | coleco | dendy,
355       },
356       KEY_TRACKED: {
357         'a': amiga | atari | coleco | dendy,
358         'b': amiga | atari | coleco,
359       },
360       KEY_UNTRACKED: {
361         'c': amiga | atari | coleco | dendy,
362         'd': amiga | coleco | dendy,
363       },
364       KEY_TOUCHED: {
365         'a': amiga | atari | coleco | dendy,
366         'b': atari | coleco | dendy,
367         'c': amiga | atari | coleco | dendy,
368         'd': atari | coleco | dendy,
369       },
370     }
371     expected_values = {
372       'command': {
373         ('echo', 'Hello World'): atari,
374         ('echo', 'You should get an Atari'): amiga | coleco | dendy,
375       },
376       KEY_TRACKED: {
377         'a': amiga | atari | coleco | dendy,
378         'b': amiga | atari | coleco,
379       },
380       KEY_UNTRACKED: {
381         'c': amiga | atari | coleco | dendy,
382         'd': amiga | coleco | dendy,
383       },
384       KEY_TOUCHED: {
385         'b': dendy,
386         'd': atari,
387       },
388       'read_only': {},
389     }
390     actual_values = isolate_format.reduce_inputs(values)
391     self.assertEqual(expected_values, actual_values)
392
393   def test_convert_map_to_isolate_dict(self):
394     amiga = ('amiga',)
395     atari = ('atari',)
396     coleco = ('coleco',)
397     dendy = ('dendy',)
398     values = {
399       'command': {
400         ('echo', 'Hello World'): (atari,),
401         ('echo', 'You should get an Atari'): (amiga, coleco, dendy),
402       },
403       KEY_TRACKED: {
404         'a': (amiga, atari, coleco, dendy),
405         'c': (atari,),
406         'e': (amiga, coleco, dendy),
407         'g': (amiga,),
408         'x': (amiga, atari, coleco, dendy),
409       },
410       KEY_UNTRACKED: {
411         'b': (amiga, atari, coleco, dendy),
412         'd': (atari,),
413         'f': (amiga, coleco, dendy),
414         'h': (amiga, atari, dendy),
415       },
416       KEY_TOUCHED: {
417         'touched': (amiga, atari, coleco, dendy),
418         'touched_a': (atari,),
419         'touched_e': (amiga, coleco, dendy),
420       },
421       'read_only': {
422         0: (amiga,),
423         1: (atari,),
424       },
425     }
426     expected_conditions = [
427       ['OS=="amiga"', {
428         'variables': {
429           KEY_TRACKED: ['g'],
430           'read_only': 0,
431         },
432       }],
433       ['OS=="amiga" or OS=="atari" or OS=="coleco" or OS=="dendy"', {
434         'variables': {
435           KEY_TRACKED: ['a', 'x'],
436           KEY_UNTRACKED: ['b'],
437           KEY_TOUCHED: ['touched'],
438         },
439       }],
440       ['OS=="amiga" or OS=="atari" or OS=="dendy"', {
441         'variables': {
442           KEY_UNTRACKED: ['h'],
443         },
444       }],
445       ['OS=="amiga" or OS=="coleco" or OS=="dendy"', {
446         'variables': {
447           'command': ['echo', 'You should get an Atari'],
448           KEY_TRACKED: ['e'],
449           KEY_UNTRACKED: ['f'],
450           KEY_TOUCHED: ['touched_e'],
451         },
452       }],
453       ['OS=="atari"', {
454         'variables': {
455           'command': ['echo', 'Hello World'],
456           KEY_TRACKED: ['c'],
457           KEY_UNTRACKED: ['d'],
458           KEY_TOUCHED: ['touched_a'],
459           'read_only': 1,
460         },
461       }],
462     ]
463     actual = isolate_format.convert_map_to_isolate_dict(values, ('OS',))
464     self.assertEqual(expected_conditions, sorted(actual.pop('conditions')))
465     self.assertFalse(actual)
466
467   def test_merge_two_empty(self):
468     # Flat stay flat. Pylint is confused about union() return type.
469     # pylint: disable=E1103
470     actual = isolate_format.Configs(None, ()).union(
471         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None)).union(
472             isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
473     expected = {
474       (): {
475         'isolate_dir': FAKE_DIR,
476       },
477     }
478     self.assertEqual(expected, actual.flatten())
479
480   def test_merge_empty(self):
481     actual = isolate_format.convert_map_to_isolate_dict(
482         isolate_format.reduce_inputs(isolate_format.invert_map({})),
483         ('dummy1', 'dummy2'))
484     self.assertEqual({'conditions': []}, actual)
485
486   def test_load_two_conditions(self):
487     linux = {
488       'conditions': [
489         ['OS=="linux"', {
490           'variables': {
491             'isolate_dependency_tracked': [
492               'file_linux',
493               'file_common',
494             ],
495           },
496         }],
497       ],
498     }
499     mac = {
500       'conditions': [
501         ['OS=="mac"', {
502           'variables': {
503             'isolate_dependency_tracked': [
504               'file_mac',
505               'file_common',
506             ],
507           },
508         }],
509       ],
510     }
511     expected = {
512       (None,): {
513         'isolate_dir': FAKE_DIR,
514       },
515       ('linux',): {
516         'isolate_dependency_tracked': ['file_common', 'file_linux'],
517         'isolate_dir': FAKE_DIR,
518       },
519       ('mac',): {
520         'isolate_dependency_tracked': ['file_common', 'file_mac'],
521         'isolate_dir': FAKE_DIR,
522       },
523     }
524     # Pylint is confused about union() return type.
525     # pylint: disable=E1103
526     configs = isolate_format.Configs(None, ()).union(
527         isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
528             isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)
529         ).flatten()
530     self.assertEqual(expected, configs)
531
532   def test_load_three_conditions(self):
533     linux = {
534       'conditions': [
535         ['OS=="linux" and chromeos==1', {
536           'variables': {
537             'isolate_dependency_tracked': [
538               'file_linux',
539               'file_common',
540             ],
541           },
542         }],
543       ],
544     }
545     mac = {
546       'conditions': [
547         ['OS=="mac" and chromeos==0', {
548           'variables': {
549             'isolate_dependency_tracked': [
550               'file_mac',
551               'file_common',
552             ],
553           },
554         }],
555       ],
556     }
557     win = {
558       'conditions': [
559         ['OS=="win" and chromeos==0', {
560           'variables': {
561             'isolate_dependency_tracked': [
562               'file_win',
563               'file_common',
564             ],
565           },
566         }],
567       ],
568     }
569     expected = {
570       (None, None): {
571         'isolate_dir': FAKE_DIR,
572       },
573       ('linux', 1): {
574         'isolate_dependency_tracked': ['file_common', 'file_linux'],
575         'isolate_dir': FAKE_DIR,
576       },
577       ('mac', 0): {
578         'isolate_dependency_tracked': ['file_common', 'file_mac'],
579         'isolate_dir': FAKE_DIR,
580       },
581       ('win', 0): {
582         'isolate_dependency_tracked': ['file_common', 'file_win'],
583         'isolate_dir': FAKE_DIR,
584       },
585     }
586     # Pylint is confused about union() return type.
587     # pylint: disable=E1103
588     configs = isolate_format.Configs(None, ()).union(
589         isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
590             isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)).union(
591                 isolate_format.load_isolate_as_config(FAKE_DIR, win, None))
592     self.assertEqual(expected, configs.flatten())
593
594   def test_safe_index(self):
595     self.assertEqual(1, isolate_format._safe_index(('a', 'b'), 'b'))
596     self.assertEqual(None, isolate_format._safe_index(('a', 'b'), 'c'))
597
598   def test_get_map_keys(self):
599     self.assertEqual(
600         (0, None, 1), isolate_format._get_map_keys(('a', 'b', 'c'), ('a', 'c')))
601
602   def test_map_keys(self):
603     self.assertEqual(
604         ('a', None, 'c'),
605         isolate_format._map_keys((0, None, 1), ('a', 'c')))
606
607   def test_load_multi_variables(self):
608     # Load an .isolate with different condition on different variables.
609     data = {
610       'conditions': [
611         ['OS=="abc"', {
612           'variables': {
613             'command': ['bar'],
614           },
615         }],
616         ['CHROMEOS=="1"', {
617           'variables': {
618             'command': ['foo'],
619           },
620         }],
621       ],
622     }
623     configs = isolate_format.load_isolate_as_config(FAKE_DIR, data, None)
624     self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
625     flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
626     expected = {
627       (None, None): {
628         'isolate_dir': FAKE_DIR,
629       },
630       (None, 'abc'): {
631         'command': ['bar'],
632         'isolate_dir': FAKE_DIR,
633       },
634       ('1', None): {
635         'command': ['foo'],
636         'isolate_dir': FAKE_DIR,
637       },
638       # TODO(maruel): It is a conflict.
639       ('1', 'abc'): {
640         'command': ['bar'],
641         'isolate_dir': FAKE_DIR,
642       },
643     }
644     self.assertEqual(expected, flatten)
645
646   def test_union_multi_variables(self):
647     data1 = {
648       'conditions': [
649         ['OS=="abc"', {
650           'variables': {
651             'command': ['bar'],
652           },
653         }],
654       ],
655     }
656     data2 = {
657       'conditions': [
658         ['CHROMEOS=="1"', {
659           'variables': {
660             'command': ['foo'],
661           },
662         }],
663       ],
664     }
665     configs1 = isolate_format.load_isolate_as_config(FAKE_DIR, data1, None)
666     configs2 = isolate_format.load_isolate_as_config(FAKE_DIR, data2, None)
667     configs = configs1.union(configs2)
668     self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
669     flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
670     expected = {
671       (None, None): {
672         'isolate_dir': FAKE_DIR,
673       },
674       (None, 'abc'): {
675         'command': ['bar'],
676         'isolate_dir': FAKE_DIR,
677       },
678       ('1', None): {
679         'command': ['foo'],
680         'isolate_dir': FAKE_DIR,
681       },
682     }
683     self.assertEqual(expected, flatten)
684
685   def test_make_isolate_multi_variables(self):
686     config = isolate_format.Configs(None, ('CHROMEOS', 'OS'))
687     config._by_config[(('0', 'linux'))] = isolate_format.ConfigSettings(
688         {'command': ['bar']}, FAKE_DIR)
689     config._by_config[(('1', 'linux'))] = isolate_format.ConfigSettings(
690         {'command': ['foo']}, FAKE_DIR)
691     expected = {
692       'conditions': [
693         ['CHROMEOS=="0" and OS=="linux"', {
694           'variables': {
695             'command': ['bar'],
696           },
697         }],
698         ['CHROMEOS=="1" and OS=="linux"', {
699           'variables': {
700             'command': ['foo'],
701           },
702         }],
703       ],
704     }
705     self.assertEqual(expected, config.make_isolate_file())
706
707   def test_make_isolate_multi_variables_missing(self):
708     config = isolate_format.Configs(None, ('CHROMEOS', 'OS'))
709     config._by_config[((None, 'abc'))] = isolate_format.ConfigSettings(
710         {'command': ['bar']}, FAKE_DIR)
711     config._by_config[(('1', None))] = isolate_format.ConfigSettings(
712         {'command': ['foo']}, FAKE_DIR)
713     expected = {
714       'conditions': [
715         ['CHROMEOS=="1"', {
716           'variables': {
717             'command': ['foo'],
718           },
719         }],
720         ['OS=="abc"', {
721           'variables': {
722             'command': ['bar'],
723           },
724         }],
725       ],
726     }
727     self.assertEqual(expected, config.make_isolate_file())
728
729   def test_make_isolate_4_variables(self):
730     # Test multiple combinations of bound and free variables.
731     config = isolate_format.Configs(None, ('BRAND', 'CHROMEOS', 'LIB', 'OS'))
732     config._by_config = {
733         (None, 0, 's', 'linux'): isolate_format.ConfigSettings(
734             {'command': ['bar']}, FAKE_DIR),
735         (None, None, 's', 'mac'): isolate_format.ConfigSettings(
736             {'command': ['foo']}, FAKE_DIR),
737         (None, None, 's', 'win'): isolate_format.ConfigSettings(
738             {'command': ['ziz']}, FAKE_DIR),
739         ('Chrome', 0, 's', 'win'): isolate_format.ConfigSettings(
740             {'command': ['baz']}, FAKE_DIR),
741     }
742     expected = {
743       'conditions': [
744         ['BRAND=="Chrome" and CHROMEOS==0 and LIB=="s" and OS=="win"', {
745           'variables': {
746             'command': ['baz'],
747           },
748         }],
749         ['CHROMEOS==0 and LIB=="s" and OS=="linux"', {
750           'variables': {
751             'command': ['bar'],
752           },
753         }],
754         ['LIB=="s" and OS=="mac"', {
755           'variables': {
756             'command': ['foo'],
757           },
758         }],
759         ['LIB=="s" and OS=="win"', {
760           'variables': {
761             'command': ['ziz'],
762           },
763         }],
764       ],
765     }
766     self.assertEqual(expected, config.make_isolate_file())
767
768   def test_ConfigSettings_union(self):
769     lhs_values = {}
770     rhs_values = {KEY_UNTRACKED: ['data/', 'test/data/']}
771     lhs = isolate_format.ConfigSettings(lhs_values, '/src/net/third_party/nss')
772     rhs = isolate_format.ConfigSettings(rhs_values, '/src/base')
773     out = lhs.union(rhs)
774     expected = {
775       KEY_UNTRACKED: ['data/', 'test/data/'],
776       'isolate_dir': '/src/base',
777     }
778     self.assertEqual(expected, out.flatten())
779
780   def test_merge_three_conditions(self):
781     values = {
782       ('linux',): {
783         'isolate_dependency_tracked': ['file_common', 'file_linux'],
784       },
785       ('mac',): {
786         'isolate_dependency_tracked': ['file_common', 'file_mac'],
787       },
788       ('win',): {
789         'isolate_dependency_tracked': ['file_common', 'file_win'],
790       },
791     }
792     expected = {
793       'conditions': [
794         ['OS=="linux"', {
795           'variables': {
796             'isolate_dependency_tracked': [
797               'file_linux',
798             ],
799           },
800         }],
801         ['OS=="linux" or OS=="mac" or OS=="win"', {
802           'variables': {
803             'isolate_dependency_tracked': [
804               'file_common',
805             ],
806           },
807         }],
808         ['OS=="mac"', {
809           'variables': {
810             'isolate_dependency_tracked': [
811               'file_mac',
812             ],
813           },
814         }],
815         ['OS=="win"', {
816           'variables': {
817             'isolate_dependency_tracked': [
818               'file_win',
819             ],
820           },
821         }],
822       ],
823     }
824     actual = isolate_format.convert_map_to_isolate_dict(
825         isolate_format.reduce_inputs(isolate_format.invert_map(values)),
826         ('OS',))
827     self.assertEqual(expected, actual)
828
829   def test_merge_three_conditions_read_only(self):
830     values = {
831       ('linux',): {
832         'isolate_dependency_tracked': ['file_common', 'file_linux'],
833         'read_only': 1,
834       },
835       ('mac',): {
836         'isolate_dependency_tracked': ['file_common', 'file_mac'],
837         'read_only': 0,
838       },
839       ('win',): {
840         'isolate_dependency_tracked': ['file_common', 'file_win'],
841         'read_only': 2,
842       },
843       ('amiga',): {
844         'read_only': 1,
845       }
846     }
847     expected = {
848       'conditions': [
849         ['OS=="amiga" or OS=="linux"', {
850           'variables': {
851             'read_only': 1,
852           },
853         }],
854         ['OS=="linux"', {
855           'variables': {
856             'isolate_dependency_tracked': [
857               'file_linux',
858             ],
859           },
860         }],
861         ['OS=="linux" or OS=="mac" or OS=="win"', {
862           'variables': {
863             'isolate_dependency_tracked': [
864               'file_common',
865             ],
866           },
867         }],
868         ['OS=="mac"', {
869           'variables': {
870             'isolate_dependency_tracked': [
871               'file_mac',
872             ],
873             'read_only': 0,
874           },
875         }],
876         ['OS=="win"', {
877           'variables': {
878             'isolate_dependency_tracked': [
879               'file_win',
880             ],
881             'read_only': 2,
882           },
883         }],
884       ],
885     }
886     actual = isolate_format.convert_map_to_isolate_dict(
887         isolate_format.reduce_inputs(isolate_format.invert_map(values)),
888         ('OS',))
889     self.assertEqual(expected, actual)
890
891   def test_configs_comment(self):
892     # Pylint is confused with isolate_format.union() return type.
893     # pylint: disable=E1103
894     configs = isolate_format.load_isolate_as_config(
895             FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n').union(
896         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
897     self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
898
899     configs = isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).union(
900         isolate_format.load_isolate_as_config(
901             FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n'))
902     self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
903
904     # Only keep the first one.
905     configs = isolate_format.load_isolate_as_config(
906         FAKE_DIR, {}, '# Yo dawg!\n').union(
907             isolate_format.load_isolate_as_config(
908                 FAKE_DIR, {}, '# Chill out.\n'))
909     self.assertEqual('# Yo dawg!\n', configs.file_comment)
910
911   def test_extract_comment(self):
912     self.assertEqual(
913         '# Foo\n# Bar\n', isolate_format.extract_comment('# Foo\n# Bar\n{}'))
914     self.assertEqual('', isolate_format.extract_comment('{}'))
915
916   def _test_pretty_print_impl(self, value, expected):
917     actual = cStringIO.StringIO()
918     isolate_format.pretty_print(value, actual)
919     self.assertEqual(expected.splitlines(), actual.getvalue().splitlines())
920
921   def test_pretty_print_empty(self):
922     self._test_pretty_print_impl({}, '{\n}\n')
923
924   def test_pretty_print_mid_size(self):
925     value = {
926       'variables': {
927         KEY_TOUCHED: [
928           'file1',
929           'file2',
930         ],
931       },
932       'conditions': [
933         ['OS==\"foo\"', {
934           'variables': {
935             KEY_UNTRACKED: [
936               'dir1',
937               'dir2',
938             ],
939             KEY_TRACKED: [
940               'file4',
941               'file3',
942             ],
943             'command': ['python', '-c', 'print "H\\i\'"'],
944             'read_only': 2,
945           },
946         }],
947         ['OS==\"bar\"', {
948           'variables': {},
949         }],
950       ],
951     }
952     isolate_format.verify_root(value, {})
953     # This is an .isolate format.
954     expected = (
955         "{\n"
956         "  'variables': {\n"
957         "    'isolate_dependency_touched': [\n"
958         "      'file1',\n"
959         "      'file2',\n"
960         "    ],\n"
961         "  },\n"
962         "  'conditions': [\n"
963         "    ['OS==\"foo\"', {\n"
964         "      'variables': {\n"
965         "        'command': [\n"
966         "          'python',\n"
967         "          '-c',\n"
968         "          'print \"H\\i\'\"',\n"
969         "        ],\n"
970         "        'read_only': 2,\n"
971         "        'isolate_dependency_tracked': [\n"
972         "          'file4',\n"
973         "          'file3',\n"
974         "        ],\n"
975         "        'isolate_dependency_untracked': [\n"
976         "          'dir1',\n"
977         "          'dir2',\n"
978         "        ],\n"
979         "      },\n"
980         "    }],\n"
981         "    ['OS==\"bar\"', {\n"
982         "      'variables': {\n"
983         "      },\n"
984         "    }],\n"
985         "  ],\n"
986         "}\n")
987     self._test_pretty_print_impl(value, expected)
988
989   def test_convert_old_to_new_else(self):
990     isolate_with_else_clauses = {
991       'conditions': [
992         ['OS=="mac"', {
993           'variables': {'foo': 'bar'},
994         }, {
995           'variables': {'x': 'y'},
996         }],
997       ],
998     }
999     with self.assertRaises(isolate_format.isolateserver.ConfigError):
1000       isolate_format.load_isolate_as_config(
1001           FAKE_DIR, isolate_with_else_clauses, None)
1002
1003   def test_match_configs(self):
1004     expectations = [
1005         (
1006           ('OS=="win"', ('OS',), [('win',), ('mac',), ('linux',)]),
1007           [('win',)],
1008         ),
1009         (
1010           (
1011             '(foo==1 or foo==2) and bar=="b"',
1012             ['foo', 'bar'],
1013             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
1014           ),
1015           [(1, 'b'), (2, 'b')],
1016         ),
1017         (
1018           (
1019             'bar=="b"',
1020             ['foo', 'bar'],
1021             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
1022           ),
1023           # TODO(maruel): When a free variable match is found, it should not
1024           # list all the bounded values in addition. The problem is when an
1025           # intersection of two different bound variables that are tested singly
1026           # in two different conditions.
1027           [(1, 'b'), (2, 'b'), (None, 'b')],
1028         ),
1029         (
1030           (
1031             'foo==1 or bar=="b"',
1032             ['foo', 'bar'],
1033             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
1034           ),
1035           # TODO(maruel): (None, 'b') would match.
1036           # It is hard in this case to realize that each of the variables 'foo'
1037           # and 'bar' can be unbounded in a specific case.
1038           [(1, 'a'), (1, 'b'), (2, 'b'), (1, None)],
1039         ),
1040     ]
1041     for data, expected in expectations:
1042       self.assertEqual(expected, isolate_format.match_configs(*data))
1043
1044   def test_load_with_globals(self):
1045     values = {
1046       'variables': {
1047         'isolate_dependency_tracked': [
1048           'file_common',
1049         ],
1050       },
1051       'conditions': [
1052         ['OS=="linux"', {
1053           'variables': {
1054             'isolate_dependency_tracked': [
1055               'file_linux',
1056             ],
1057             'read_only': 1,
1058           },
1059         }],
1060         ['OS=="mac" or OS=="win"', {
1061           'variables': {
1062             'isolate_dependency_tracked': [
1063               'file_non_linux',
1064             ],
1065             'read_only': 0,
1066           },
1067         }],
1068       ],
1069     }
1070     expected = {
1071       (None,): {
1072         'isolate_dependency_tracked': [
1073           'file_common',
1074         ],
1075         'isolate_dir': FAKE_DIR,
1076       },
1077       ('linux',): {
1078         'isolate_dependency_tracked': [
1079           'file_linux',
1080         ],
1081         'isolate_dir': FAKE_DIR,
1082         'read_only': 1,
1083       },
1084       ('mac',): {
1085         'isolate_dependency_tracked': [
1086           'file_non_linux',
1087         ],
1088         'isolate_dir': FAKE_DIR,
1089         'read_only': 0,
1090       },
1091       ('win',): {
1092         'isolate_dependency_tracked': [
1093           'file_non_linux',
1094         ],
1095         'isolate_dir': FAKE_DIR,
1096         'read_only': 0,
1097       },
1098     }
1099     actual = isolate_format.load_isolate_as_config(FAKE_DIR, values, None)
1100     self.assertEqual(expected, actual.flatten())
1101
1102   def test_configs_with_globals(self):
1103     c = isolate_format.Configs(None, ('x', 'y'))
1104     c.set_config(
1105         (1, 1), isolate_format.ConfigSettings({KEY_TRACKED: ['1,1']}, FAKE_DIR))
1106     c.set_config(
1107         (2, 2), isolate_format.ConfigSettings({KEY_TRACKED: ['2,2']}, FAKE_DIR))
1108     c.set_config(
1109         (1, None),
1110         isolate_format.ConfigSettings({KEY_TRACKED: ['1,y']}, FAKE_DIR))
1111     c.set_config(
1112         (None, 2),
1113         isolate_format.ConfigSettings({KEY_TRACKED: ['x,2']}, FAKE_DIR))
1114     c.set_config(
1115         (None, None),
1116         isolate_format.ConfigSettings({KEY_TRACKED: ['x,y']}, FAKE_DIR))
1117     expected = {
1118       (None, None): {
1119         KEY_TRACKED: ['x,y'],
1120         'isolate_dir': FAKE_DIR,
1121       },
1122       (None, 2): {
1123         KEY_TRACKED: ['x,2'],
1124         'isolate_dir': FAKE_DIR,
1125       },
1126       (1, None): {
1127         KEY_TRACKED: ['1,y'],
1128         'isolate_dir': FAKE_DIR,
1129       },
1130       (1, 1): {
1131         KEY_TRACKED: ['1,1'],
1132         'isolate_dir': FAKE_DIR,
1133       },
1134       (2, 2): {
1135         KEY_TRACKED: ['2,2'],
1136         'isolate_dir': FAKE_DIR,
1137       },
1138     }
1139     self.assertEqual(expected, c.flatten())
1140
1141     s = c.get_config((1, 1))
1142     expected = {
1143       KEY_TRACKED: ['1,1', '1,y', 'x,y'],
1144       'isolate_dir': FAKE_DIR,
1145     }
1146     self.assertEqual(expected, s.flatten())
1147
1148     s = c.get_config((1, None))
1149     expected = {
1150       KEY_TRACKED: ['1,y', 'x,y'],
1151       'isolate_dir': FAKE_DIR,
1152     }
1153     self.assertEqual(expected, s.flatten())
1154
1155     s = c.get_config((None, None))
1156     expected = {
1157       KEY_TRACKED: ['x,y'],
1158       'isolate_dir': FAKE_DIR,
1159     }
1160     self.assertEqual(expected, s.flatten())
1161
1162     expected = {
1163       'conditions': [
1164         ['x==1', {'variables': {KEY_TRACKED: ['1,y']}}],
1165         ['x==1 and y==1', {'variables': {KEY_TRACKED: ['1,1']}}],
1166         ['x==2 and y==2', {'variables': {KEY_TRACKED: ['2,2']}}],
1167         ['y==2', {'variables': {KEY_TRACKED: ['x,2']}}],
1168       ],
1169       'variables': {KEY_TRACKED: ['x,y']},
1170     }
1171     self.assertEqual(expected, c.make_isolate_file())
1172
1173
1174 class IsolateFormatTmpDirTest(unittest.TestCase):
1175   def setUp(self):
1176     super(IsolateFormatTmpDirTest, self).setUp()
1177     self.tempdir = tempfile.mkdtemp(prefix='isolate_')
1178
1179   def tearDown(self):
1180     try:
1181       run_isolated.rmtree(self.tempdir)
1182     finally:
1183       super(IsolateFormatTmpDirTest, self).tearDown()
1184
1185   def test_load_with_includes(self):
1186     included_isolate = {
1187       'variables': {
1188         'isolate_dependency_tracked': [
1189           'file_common',
1190         ],
1191       },
1192       'conditions': [
1193         ['OS=="linux"', {
1194           'variables': {
1195             'isolate_dependency_tracked': [
1196               'file_linux',
1197             ],
1198             'read_only': 1,
1199           },
1200         }],
1201         ['OS=="mac" or OS=="win"', {
1202           'variables': {
1203             'isolate_dependency_tracked': [
1204               'file_non_linux',
1205             ],
1206             'read_only': 0,
1207           },
1208         }],
1209       ],
1210     }
1211     with open(os.path.join(self.tempdir, 'included.isolate'), 'wb') as f:
1212       isolate_format.pretty_print(included_isolate, f)
1213     values = {
1214       'includes': ['included.isolate'],
1215       'variables': {
1216         'isolate_dependency_tracked': [
1217           'file_less_common',
1218         ],
1219       },
1220       'conditions': [
1221         ['OS=="mac"', {
1222           'variables': {
1223             'isolate_dependency_tracked': [
1224               'file_mac',
1225             ],
1226             'read_only': 2,
1227           },
1228         }],
1229       ],
1230     }
1231     actual = isolate_format.load_isolate_as_config(self.tempdir, values, None)
1232
1233     expected = {
1234       (None,): {
1235         'isolate_dependency_tracked': [
1236           'file_common',
1237           'file_less_common',
1238         ],
1239         'isolate_dir': self.tempdir,
1240       },
1241       ('linux',): {
1242         'isolate_dependency_tracked': [
1243           'file_linux',
1244         ],
1245         'isolate_dir': self.tempdir,
1246         'read_only': 1,
1247       },
1248       ('mac',): {
1249         'isolate_dependency_tracked': [
1250           'file_mac',
1251           'file_non_linux',
1252         ],
1253         'isolate_dir': self.tempdir,
1254         'read_only': 2,
1255       },
1256       ('win',): {
1257         'isolate_dependency_tracked': [
1258           'file_non_linux',
1259         ],
1260         'isolate_dir': self.tempdir,
1261         'read_only': 0,
1262       },
1263     }
1264     self.assertEqual(expected, actual.flatten())
1265
1266   def test_load_with_includes_with_commands(self):
1267     # This one is messy. Check that isolate_dir is the expected value. To
1268     # achieve this, put the .isolate files into subdirectories.
1269     dir_1 = os.path.join(self.tempdir, '1')
1270     dir_3 = os.path.join(self.tempdir, '3')
1271     dir_3_2 = os.path.join(self.tempdir, '3', '2')
1272     os.mkdir(dir_1)
1273     os.mkdir(dir_3)
1274     os.mkdir(dir_3_2)
1275
1276     isolate1 = {
1277       'conditions': [
1278         ['OS=="amiga" or OS=="win"', {
1279           'variables': {
1280             'command': [
1281               'foo', 'amiga_or_win',
1282             ],
1283           },
1284         }],
1285         ['OS=="linux"', {
1286           'variables': {
1287             'command': [
1288               'foo', 'linux',
1289             ],
1290             'isolate_dependency_tracked': [
1291               'file_linux',
1292             ],
1293           },
1294         }],
1295         ['OS=="mac" or OS=="win"', {
1296           'variables': {
1297             'isolate_dependency_tracked': [
1298               'file_non_linux',
1299             ],
1300           },
1301         }],
1302       ],
1303     }
1304     isolate2 = {
1305       'conditions': [
1306         ['OS=="linux" or OS=="mac"', {
1307           'variables': {
1308             'command': [
1309               'foo', 'linux_or_mac',
1310             ],
1311             'isolate_dependency_tracked': [
1312               'other/file',
1313             ],
1314           },
1315         }],
1316       ],
1317     }
1318     isolate3 = {
1319       'includes': [
1320         '../1/isolate1.isolate',
1321         '2/isolate2.isolate',
1322       ],
1323       'conditions': [
1324         ['OS=="amiga"', {
1325           'variables': {
1326             'isolate_dependency_tracked': [
1327               'file_amiga',
1328             ],
1329           },
1330         }],
1331         ['OS=="mac"', {
1332           'variables': {
1333             'command': [
1334               'foo', 'mac',
1335             ],
1336             'isolate_dependency_tracked': [
1337               'file_mac',
1338             ],
1339           },
1340         }],
1341       ],
1342     }
1343     # No need to write isolate3.
1344     with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
1345       isolate_format.pretty_print(isolate1, f)
1346     with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
1347       isolate_format.pretty_print(isolate2, f)
1348
1349     # The 'isolate_dir' are important, they are what will be used when
1350     # definining the final isolate_dir to use to run the command in the
1351     # .isolated file.
1352     actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
1353     expected = {
1354       (None,): {
1355         # TODO(maruel): See TODO in ConfigSettings.flatten().
1356         # TODO(maruel): If kept, in this case dir_3 should be selected.
1357         'isolate_dir': dir_1,
1358       },
1359       ('amiga',): {
1360         'command': ['foo', 'amiga_or_win'],
1361         'isolate_dependency_tracked': [
1362           # Note that the file was rebased from isolate1. This is important,
1363           # isolate1 represent the canonical root path because it is the one
1364           # that defined the command.
1365           '../3/file_amiga',
1366         ],
1367         'isolate_dir': dir_1,
1368       },
1369       ('linux',): {
1370         # Last included takes precedence. *command comes from isolate2*, so
1371         # it becomes the canonical root, so reference to file from isolate1 is
1372         # via '../../1'.
1373         'command': ['foo', 'linux_or_mac'],
1374         'isolate_dependency_tracked': [
1375           '../../1/file_linux',
1376           'other/file',
1377         ],
1378         'isolate_dir': dir_3_2,
1379       },
1380       ('mac',): {
1381         # command in isolate3 takes precedence over the ones included.
1382         'command': ['foo', 'mac'],
1383         'isolate_dependency_tracked': [
1384           '../1/file_non_linux',
1385           '2/other/file',
1386           'file_mac',
1387         ],
1388         'isolate_dir': dir_3,
1389       },
1390       ('win',): {
1391         # command comes from isolate1.
1392         'command': ['foo', 'amiga_or_win'],
1393         'isolate_dependency_tracked': [
1394           # While this may be surprising, this is because the command was
1395           # defined in isolate1, not isolate3.
1396           'file_non_linux',
1397         ],
1398         'isolate_dir': dir_1,
1399       },
1400     }
1401     self.assertEqual(expected, actual.flatten())
1402
1403   def test_load_with_includes_with_commands_and_variables(self):
1404     # This one is the pinacle of fun. Check that isolate_dir is the expected
1405     # value. To achieve this, put the .isolate files into subdirectories.
1406     dir_1 = os.path.join(self.tempdir, '1')
1407     dir_3 = os.path.join(self.tempdir, '3')
1408     dir_3_2 = os.path.join(self.tempdir, '3', '2')
1409     os.mkdir(dir_1)
1410     os.mkdir(dir_3)
1411     os.mkdir(dir_3_2)
1412
1413     isolate1 = {
1414       'conditions': [
1415         ['OS=="amiga" or OS=="win"', {
1416           'variables': {
1417             'command': [
1418               'foo', 'amiga_or_win', '<(PATH)',
1419             ],
1420           },
1421         }],
1422         ['OS=="linux"', {
1423           'variables': {
1424             'command': [
1425               'foo', 'linux', '<(PATH)',
1426             ],
1427             'isolate_dependency_tracked': [
1428               '<(PATH)/file_linux',
1429             ],
1430           },
1431         }],
1432         ['OS=="mac" or OS=="win"', {
1433           'variables': {
1434             'isolate_dependency_tracked': [
1435               '<(PATH)/file_non_linux',
1436             ],
1437           },
1438         }],
1439       ],
1440     }
1441     isolate2 = {
1442       'conditions': [
1443         ['OS=="linux" or OS=="mac"', {
1444           'variables': {
1445             'command': [
1446               'foo', 'linux_or_mac', '<(PATH)',
1447             ],
1448             'isolate_dependency_tracked': [
1449               '<(PATH)/other/file',
1450             ],
1451           },
1452         }],
1453       ],
1454     }
1455     isolate3 = {
1456       'includes': [
1457         '../1/isolate1.isolate',
1458         '2/isolate2.isolate',
1459       ],
1460       'conditions': [
1461         ['OS=="amiga"', {
1462           'variables': {
1463             'isolate_dependency_tracked': [
1464               '<(PATH)/file_amiga',
1465             ],
1466           },
1467         }],
1468         ['OS=="mac"', {
1469           'variables': {
1470             'command': [
1471               'foo', 'mac', '<(PATH)',
1472             ],
1473             'isolate_dependency_tracked': [
1474               '<(PATH)/file_mac',
1475             ],
1476           },
1477         }],
1478       ],
1479     }
1480     # No need to write isolate3.
1481     with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
1482       isolate_format.pretty_print(isolate1, f)
1483     with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
1484       isolate_format.pretty_print(isolate2, f)
1485
1486     # The 'isolate_dir' are important, they are what will be used when
1487     # definining the final isolate_dir to use to run the command in the
1488     # .isolated file.
1489     actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
1490     expected = {
1491       (None,): {
1492         'isolate_dir': dir_1,
1493       },
1494       ('amiga',): {
1495         'command': ['foo', 'amiga_or_win', '<(PATH)'],
1496         'isolate_dependency_tracked': [
1497           '<(PATH)/file_amiga',
1498         ],
1499         'isolate_dir': dir_1,
1500       },
1501       ('linux',): {
1502         # Last included takes precedence. *command comes from isolate2*, so
1503         # it becomes the canonical root, so reference to file from isolate1 is
1504         # via '../../1'.
1505         'command': ['foo', 'linux_or_mac', '<(PATH)'],
1506         'isolate_dependency_tracked': [
1507           '<(PATH)/file_linux',
1508           '<(PATH)/other/file',
1509         ],
1510         'isolate_dir': dir_3_2,
1511       },
1512       ('mac',): {
1513         'command': ['foo', 'mac', '<(PATH)'],
1514         'isolate_dependency_tracked': [
1515           '<(PATH)/file_mac',
1516           '<(PATH)/file_non_linux',
1517           '<(PATH)/other/file',
1518         ],
1519         'isolate_dir': dir_3,
1520       },
1521       ('win',): {
1522         # command comes from isolate1.
1523         'command': ['foo', 'amiga_or_win', '<(PATH)'],
1524         'isolate_dependency_tracked': [
1525           '<(PATH)/file_non_linux',
1526         ],
1527         'isolate_dir': dir_1,
1528       },
1529     }
1530     self.assertEqual(expected, actual.flatten())
1531
1532
1533 if __name__ == '__main__':
1534   logging.basicConfig(
1535       level=logging.DEBUG if '-v' in sys.argv else logging.ERROR,
1536       format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
1537   if '-v' in sys.argv:
1538     unittest.TestCase.maxDiff = None
1539   unittest.main()