Update To 11.40.268.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 from utils import file_path
18
19
20 # Access to a protected member XXX of a client class
21 # pylint: disable=W0212
22
23
24 FAKE_DIR = (
25     u'z:\\path\\to\\non_existing'
26     if sys.platform == 'win32' else u'/path/to/non_existing')
27
28
29 class IsolateFormatTest(unittest.TestCase):
30   def test_unknown_key(self):
31     try:
32       isolate_format.verify_variables({'foo': [],})
33       self.fail()
34     except AssertionError:
35       pass
36
37   def test_unknown_var(self):
38     try:
39       isolate_format.verify_condition({'variables': {'foo': [],}}, {})
40       self.fail()
41     except AssertionError:
42       pass
43
44   def test_eval_content(self):
45     try:
46       # Intrinsics are not available.
47       isolate_format.eval_content('map(str, [1, 2])')
48       self.fail()
49     except NameError:
50       pass
51
52   def test_load_isolate_as_config_empty(self):
53     expected = {
54       (): {
55         'isolate_dir': FAKE_DIR,
56       },
57     }
58     self.assertEqual(
59         expected,
60         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).flatten())
61
62   def test_load_isolate_as_config(self):
63     value = {
64       'conditions': [
65         ['OS=="amiga" or OS=="atari" or OS=="coleco" or OS=="dendy"', {
66           'variables': {
67             'files': ['a', 'b', 'touched'],
68           },
69         }],
70         ['OS=="atari"', {
71           'variables': {
72             'files': ['c', 'd', 'touched_a', 'x'],
73             'command': ['echo', 'Hello World'],
74             'read_only': 2,
75           },
76         }],
77         ['OS=="amiga" or OS=="coleco" or OS=="dendy"', {
78           'variables': {
79             'files': ['e', 'f', 'touched_e', 'x'],
80             'command': ['echo', 'You should get an Atari'],
81           },
82         }],
83         ['OS=="amiga"', {
84           'variables': {
85             'files': ['g'],
86             'read_only': 1,
87           },
88         }],
89         ['OS=="amiga" or OS=="atari" or OS=="dendy"', {
90           'variables': {
91             'files': ['h'],
92           },
93         }],
94       ],
95     }
96     expected = {
97       (None,): {
98         'isolate_dir': FAKE_DIR,
99       },
100       ('amiga',): {
101         'files': ['a', 'b', 'e', 'f', 'g', 'h', 'touched', 'touched_e', 'x'],
102         'command': ['echo', 'You should get an Atari'],
103         'isolate_dir': FAKE_DIR,
104         'read_only': 1,
105       },
106       ('atari',): {
107         'files': ['a', 'b', 'c', 'd', 'h', 'touched', 'touched_a', 'x'],
108         'command': ['echo', 'Hello World'],
109         'isolate_dir': FAKE_DIR,
110         'read_only': 2,
111       },
112       ('coleco',): {
113         'files': ['a', 'b', 'e', 'f', 'touched', 'touched_e', 'x'],
114         'command': ['echo', 'You should get an Atari'],
115         'isolate_dir': FAKE_DIR,
116       },
117       ('dendy',): {
118         'files': ['a', 'b', 'e', 'f', 'h', 'touched', 'touched_e', 'x'],
119         'command': ['echo', 'You should get an Atari'],
120         'isolate_dir': FAKE_DIR,
121       },
122     }
123     self.assertEqual(
124         expected, isolate_format.load_isolate_as_config(
125             FAKE_DIR, value, None).flatten())
126
127   def test_load_isolate_as_config_duplicate_command(self):
128     value = {
129       'variables': {
130         'command': ['rm', '-rf', '/'],
131       },
132       'conditions': [
133         ['OS=="atari"', {
134           'variables': {
135             'command': ['echo', 'Hello World'],
136           },
137         }],
138       ],
139     }
140     try:
141       isolate_format.load_isolate_as_config(FAKE_DIR, value, None)
142       self.fail()
143     except AssertionError:
144       pass
145
146   def test_load_isolate_as_config_no_variable(self):
147     value = {
148       'variables': {
149         'command': ['echo', 'You should get an Atari'],
150         'files': ['a', 'b', 'touched'],
151         'read_only': 1,
152       },
153     }
154     # The key is the empty tuple, since there is no variable to bind to.
155     expected = {
156       (): {
157         'command': ['echo', 'You should get an Atari'],
158         'files': ['a', 'b', 'touched'],
159         'isolate_dir': FAKE_DIR,
160         'read_only': 1,
161       },
162     }
163     self.assertEqual(
164         expected, isolate_format.load_isolate_as_config(
165             FAKE_DIR, value, None).flatten())
166
167   def test_merge_two_empty(self):
168     # Flat stay flat. Pylint is confused about union() return type.
169     # pylint: disable=E1103
170     actual = isolate_format.Configs(None, ()).union(
171         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None)).union(
172             isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
173     expected = {
174       (): {
175         'isolate_dir': FAKE_DIR,
176       },
177     }
178     self.assertEqual(expected, actual.flatten())
179
180   def test_load_two_conditions(self):
181     linux = {
182       'conditions': [
183         ['OS=="linux"', {
184           'variables': {
185             'files': [
186               'file_linux',
187               'file_common',
188             ],
189           },
190         }],
191       ],
192     }
193     mac = {
194       'conditions': [
195         ['OS=="mac"', {
196           'variables': {
197             'files': [
198               'file_mac',
199               'file_common',
200             ],
201           },
202         }],
203       ],
204     }
205     expected = {
206       (None,): {
207         'isolate_dir': FAKE_DIR,
208       },
209       ('linux',): {
210         'files': ['file_common', 'file_linux'],
211         'isolate_dir': FAKE_DIR,
212       },
213       ('mac',): {
214         'files': ['file_common', 'file_mac'],
215         'isolate_dir': FAKE_DIR,
216       },
217     }
218     # Pylint is confused about union() return type.
219     # pylint: disable=E1103
220     configs = isolate_format.Configs(None, ()).union(
221         isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
222             isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)
223         ).flatten()
224     self.assertEqual(expected, configs)
225
226   def test_load_three_conditions(self):
227     linux = {
228       'conditions': [
229         ['OS=="linux" and chromeos==1', {
230           'variables': {
231             'files': [
232               'file_linux',
233               'file_common',
234             ],
235           },
236         }],
237       ],
238     }
239     mac = {
240       'conditions': [
241         ['OS=="mac" and chromeos==0', {
242           'variables': {
243             'files': [
244               'file_mac',
245               'file_common',
246             ],
247           },
248         }],
249       ],
250     }
251     win = {
252       'conditions': [
253         ['OS=="win" and chromeos==0', {
254           'variables': {
255             'files': [
256               'file_win',
257               'file_common',
258             ],
259           },
260         }],
261       ],
262     }
263     expected = {
264       (None, None): {
265         'isolate_dir': FAKE_DIR,
266       },
267       ('linux', 1): {
268         'files': ['file_common', 'file_linux'],
269         'isolate_dir': FAKE_DIR,
270       },
271       ('mac', 0): {
272         'files': ['file_common', 'file_mac'],
273         'isolate_dir': FAKE_DIR,
274       },
275       ('win', 0): {
276         'files': ['file_common', 'file_win'],
277         'isolate_dir': FAKE_DIR,
278       },
279     }
280     # Pylint is confused about union() return type.
281     # pylint: disable=E1103
282     configs = isolate_format.Configs(None, ()).union(
283         isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
284             isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)).union(
285                 isolate_format.load_isolate_as_config(FAKE_DIR, win, None))
286     self.assertEqual(expected, configs.flatten())
287
288   def test_safe_index(self):
289     self.assertEqual(1, isolate_format._safe_index(('a', 'b'), 'b'))
290     self.assertEqual(None, isolate_format._safe_index(('a', 'b'), 'c'))
291
292   def test_get_map_keys(self):
293     self.assertEqual(
294         (0, None, 1), isolate_format._get_map_keys(('a', 'b', 'c'), ('a', 'c')))
295
296   def test_map_keys(self):
297     self.assertEqual(
298         ('a', None, 'c'),
299         isolate_format._map_keys((0, None, 1), ('a', 'c')))
300
301   def test_load_multi_variables(self):
302     # Load an .isolate with different condition on different variables.
303     data = {
304       'conditions': [
305         ['OS=="abc"', {
306           'variables': {
307             'command': ['bar'],
308           },
309         }],
310         ['CHROMEOS=="1"', {
311           'variables': {
312             'command': ['foo'],
313           },
314         }],
315       ],
316     }
317     configs = isolate_format.load_isolate_as_config(FAKE_DIR, data, None)
318     self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
319     flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
320     expected = {
321       (None, None): {
322         'isolate_dir': FAKE_DIR,
323       },
324       (None, 'abc'): {
325         'command': ['bar'],
326         'isolate_dir': FAKE_DIR,
327       },
328       ('1', None): {
329         'command': ['foo'],
330         'isolate_dir': FAKE_DIR,
331       },
332       # TODO(maruel): It is a conflict.
333       ('1', 'abc'): {
334         'command': ['bar'],
335         'isolate_dir': FAKE_DIR,
336       },
337     }
338     self.assertEqual(expected, flatten)
339
340   def test_union_multi_variables(self):
341     data1 = {
342       'conditions': [
343         ['OS=="abc"', {
344           'variables': {
345             'command': ['bar'],
346           },
347         }],
348       ],
349     }
350     data2 = {
351       'conditions': [
352         ['CHROMEOS=="1"', {
353           'variables': {
354             'command': ['foo'],
355           },
356         }],
357       ],
358     }
359     configs1 = isolate_format.load_isolate_as_config(FAKE_DIR, data1, None)
360     configs2 = isolate_format.load_isolate_as_config(FAKE_DIR, data2, None)
361     configs = configs1.union(configs2)
362     self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
363     flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
364     expected = {
365       (None, None): {
366         'isolate_dir': FAKE_DIR,
367       },
368       (None, 'abc'): {
369         'command': ['bar'],
370         'isolate_dir': FAKE_DIR,
371       },
372       ('1', None): {
373         'command': ['foo'],
374         'isolate_dir': FAKE_DIR,
375       },
376     }
377     self.assertEqual(expected, flatten)
378
379   def test_ConfigSettings_union(self):
380     lhs_values = {}
381     rhs_values = {'files': ['data/', 'test/data/']}
382     lhs = isolate_format.ConfigSettings(lhs_values, '/src/net/third_party/nss')
383     rhs = isolate_format.ConfigSettings(rhs_values, '/src/base')
384     out = lhs.union(rhs)
385     expected = {
386       'files': ['data/', 'test/data/'],
387       'isolate_dir': '/src/base',
388     }
389     self.assertEqual(expected, out.flatten())
390
391   def test_configs_comment(self):
392     # Pylint is confused with isolate_format.union() return type.
393     # pylint: disable=E1103
394     configs = isolate_format.load_isolate_as_config(
395             FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n').union(
396         isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
397     self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
398
399     configs = isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).union(
400         isolate_format.load_isolate_as_config(
401             FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n'))
402     self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
403
404     # Only keep the first one.
405     configs = isolate_format.load_isolate_as_config(
406         FAKE_DIR, {}, '# Yo dawg!\n').union(
407             isolate_format.load_isolate_as_config(
408                 FAKE_DIR, {}, '# Chill out.\n'))
409     self.assertEqual('# Yo dawg!\n', configs.file_comment)
410
411   def test_extract_comment(self):
412     self.assertEqual(
413         '# Foo\n# Bar\n', isolate_format.extract_comment('# Foo\n# Bar\n{}'))
414     self.assertEqual('', isolate_format.extract_comment('{}'))
415
416   def _test_pretty_print_impl(self, value, expected):
417     actual = cStringIO.StringIO()
418     isolate_format.pretty_print(value, actual)
419     self.assertEqual(expected.splitlines(), actual.getvalue().splitlines())
420
421   def test_pretty_print_empty(self):
422     self._test_pretty_print_impl({}, '{\n}\n')
423
424   def test_pretty_print_mid_size(self):
425     value = {
426       'variables': {
427         'files': [
428           'file1',
429           'file2',
430         ],
431       },
432       'conditions': [
433         ['OS==\"foo\"', {
434           'variables': {
435             'files': [
436               'dir1/',
437               'dir2/',
438               'file3',
439               'file4',
440             ],
441             'command': ['python', '-c', 'print "H\\i\'"'],
442             'read_only': 2,
443           },
444         }],
445         ['OS==\"bar\"', {
446           'variables': {},
447         }],
448       ],
449     }
450     isolate_format.verify_root(value, {})
451     # This is an .isolate format.
452     expected = (
453         "{\n"
454         "  'variables': {\n"
455         "    'files': [\n"
456         "      'file1',\n"
457         "      'file2',\n"
458         "    ],\n"
459         "  },\n"
460         "  'conditions': [\n"
461         "    ['OS==\"foo\"', {\n"
462         "      'variables': {\n"
463         "        'command': [\n"
464         "          'python',\n"
465         "          '-c',\n"
466         "          'print \"H\\i\'\"',\n"
467         "        ],\n"
468         "        'files': [\n"
469         "          'dir1/',\n"
470         "          'dir2/',\n"
471         "          'file3',\n"
472         "          'file4',\n"
473         "        ],\n"
474         "        'read_only': 2,\n"
475         "      },\n"
476         "    }],\n"
477         "    ['OS==\"bar\"', {\n"
478         "      'variables': {\n"
479         "      },\n"
480         "    }],\n"
481         "  ],\n"
482         "}\n")
483     self._test_pretty_print_impl(value, expected)
484
485   def test_convert_old_to_new_else(self):
486     isolate_with_else_clauses = {
487       'conditions': [
488         ['OS=="mac"', {
489           'variables': {'foo': 'bar'},
490         }, {
491           'variables': {'x': 'y'},
492         }],
493       ],
494     }
495     with self.assertRaises(isolate_format.IsolateError):
496       isolate_format.load_isolate_as_config(
497           FAKE_DIR, isolate_with_else_clauses, None)
498
499   def test_match_configs(self):
500     expectations = [
501         (
502           ('OS=="win"', ('OS',), [('win',), ('mac',), ('linux',)]),
503           [('win',)],
504         ),
505         (
506           (
507             '(foo==1 or foo==2) and bar=="b"',
508             ['foo', 'bar'],
509             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
510           ),
511           [(1, 'b'), (2, 'b')],
512         ),
513         (
514           (
515             'bar=="b"',
516             ['foo', 'bar'],
517             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
518           ),
519           # TODO(maruel): When a free variable match is found, it should not
520           # list all the bounded values in addition. The problem is when an
521           # intersection of two different bound variables that are tested singly
522           # in two different conditions.
523           [(1, 'b'), (2, 'b'), (None, 'b')],
524         ),
525         (
526           (
527             'foo==1 or bar=="b"',
528             ['foo', 'bar'],
529             [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
530           ),
531           # TODO(maruel): (None, 'b') would match.
532           # It is hard in this case to realize that each of the variables 'foo'
533           # and 'bar' can be unbounded in a specific case.
534           [(1, 'a'), (1, 'b'), (2, 'b'), (1, None)],
535         ),
536     ]
537     for data, expected in expectations:
538       self.assertEqual(expected, isolate_format.match_configs(*data))
539
540   def test_load_with_globals(self):
541     values = {
542       'variables': {
543         'files': [
544           'file_common',
545         ],
546       },
547       'conditions': [
548         ['OS=="linux"', {
549           'variables': {
550             'files': [
551               'file_linux',
552             ],
553             'read_only': 1,
554           },
555         }],
556         ['OS=="mac" or OS=="win"', {
557           'variables': {
558             'files': [
559               'file_non_linux',
560             ],
561             'read_only': 0,
562           },
563         }],
564       ],
565     }
566     expected = {
567       (None,): {
568         'files': [
569           'file_common',
570         ],
571         'isolate_dir': FAKE_DIR,
572       },
573       ('linux',): {
574         'files': [
575           'file_linux',
576         ],
577         'isolate_dir': FAKE_DIR,
578         'read_only': 1,
579       },
580       ('mac',): {
581         'files': [
582           'file_non_linux',
583         ],
584         'isolate_dir': FAKE_DIR,
585         'read_only': 0,
586       },
587       ('win',): {
588         'files': [
589           'file_non_linux',
590         ],
591         'isolate_dir': FAKE_DIR,
592         'read_only': 0,
593       },
594     }
595     actual = isolate_format.load_isolate_as_config(FAKE_DIR, values, None)
596     self.assertEqual(expected, actual.flatten())
597
598
599 class IsolateFormatTmpDirTest(unittest.TestCase):
600   def setUp(self):
601     super(IsolateFormatTmpDirTest, self).setUp()
602     self.tempdir = tempfile.mkdtemp(prefix='isolate_')
603
604   def tearDown(self):
605     try:
606       file_path.rmtree(self.tempdir)
607     finally:
608       super(IsolateFormatTmpDirTest, self).tearDown()
609
610   def test_load_with_includes(self):
611     included_isolate = {
612       'variables': {
613         'files': [
614           'file_common',
615         ],
616       },
617       'conditions': [
618         ['OS=="linux"', {
619           'variables': {
620             'files': [
621               'file_linux',
622             ],
623             'read_only': 1,
624           },
625         }],
626         ['OS=="mac" or OS=="win"', {
627           'variables': {
628             'files': [
629               'file_non_linux',
630             ],
631             'read_only': 0,
632           },
633         }],
634       ],
635     }
636     with open(os.path.join(self.tempdir, 'included.isolate'), 'wb') as f:
637       isolate_format.pretty_print(included_isolate, f)
638     values = {
639       'includes': ['included.isolate'],
640       'variables': {
641         'files': [
642           'file_less_common',
643         ],
644       },
645       'conditions': [
646         ['OS=="mac"', {
647           'variables': {
648             'files': [
649               'file_mac',
650             ],
651             'read_only': 2,
652           },
653         }],
654       ],
655     }
656     actual = isolate_format.load_isolate_as_config(self.tempdir, values, None)
657
658     expected = {
659       (None,): {
660         'files': [
661           'file_common',
662           'file_less_common',
663         ],
664         'isolate_dir': self.tempdir,
665       },
666       ('linux',): {
667         'files': [
668           'file_linux',
669         ],
670         'isolate_dir': self.tempdir,
671         'read_only': 1,
672       },
673       ('mac',): {
674         'files': [
675           'file_mac',
676           'file_non_linux',
677         ],
678         'isolate_dir': self.tempdir,
679         'read_only': 2,
680       },
681       ('win',): {
682         'files': [
683           'file_non_linux',
684         ],
685         'isolate_dir': self.tempdir,
686         'read_only': 0,
687       },
688     }
689     self.assertEqual(expected, actual.flatten())
690
691   def test_load_with_includes_with_commands(self):
692     # This one is messy. Check that isolate_dir is the expected value. To
693     # achieve this, put the .isolate files into subdirectories.
694     dir_1 = os.path.join(self.tempdir, '1')
695     dir_3 = os.path.join(self.tempdir, '3')
696     dir_3_2 = os.path.join(self.tempdir, '3', '2')
697     os.mkdir(dir_1)
698     os.mkdir(dir_3)
699     os.mkdir(dir_3_2)
700
701     isolate1 = {
702       'conditions': [
703         ['OS=="amiga" or OS=="win"', {
704           'variables': {
705             'command': [
706               'foo', 'amiga_or_win',
707             ],
708           },
709         }],
710         ['OS=="linux"', {
711           'variables': {
712             'command': [
713               'foo', 'linux',
714             ],
715             'files': [
716               'file_linux',
717             ],
718           },
719         }],
720         ['OS=="mac" or OS=="win"', {
721           'variables': {
722             'files': [
723               'file_non_linux',
724             ],
725           },
726         }],
727       ],
728     }
729     isolate2 = {
730       'conditions': [
731         ['OS=="linux" or OS=="mac"', {
732           'variables': {
733             'command': [
734               'foo', 'linux_or_mac',
735             ],
736             'files': [
737               'other/file',
738             ],
739           },
740         }],
741       ],
742     }
743     isolate3 = {
744       'includes': [
745         '../1/isolate1.isolate',
746         '2/isolate2.isolate',
747       ],
748       'conditions': [
749         ['OS=="amiga"', {
750           'variables': {
751             'files': [
752               'file_amiga',
753             ],
754           },
755         }],
756         ['OS=="mac"', {
757           'variables': {
758             'command': [
759               'foo', 'mac',
760             ],
761             'files': [
762               'file_mac',
763             ],
764           },
765         }],
766       ],
767     }
768     # No need to write isolate3.
769     with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
770       isolate_format.pretty_print(isolate1, f)
771     with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
772       isolate_format.pretty_print(isolate2, f)
773
774     # The 'isolate_dir' are important, they are what will be used when
775     # definining the final isolate_dir to use to run the command in the
776     # .isolated file.
777     actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
778     expected = {
779       (None,): {
780         # TODO(maruel): See TODO in ConfigSettings.flatten().
781         # TODO(maruel): If kept, in this case dir_3 should be selected.
782         'isolate_dir': dir_1,
783       },
784       ('amiga',): {
785         'command': ['foo', 'amiga_or_win'],
786         'files': [
787           # Note that the file was rebased from isolate1. This is important,
788           # isolate1 represent the canonical root path because it is the one
789           # that defined the command.
790           '../3/file_amiga',
791         ],
792         'isolate_dir': dir_1,
793       },
794       ('linux',): {
795         # Last included takes precedence. *command comes from isolate2*, so
796         # it becomes the canonical root, so reference to file from isolate1 is
797         # via '../../1'.
798         'command': ['foo', 'linux_or_mac'],
799         'files': [
800           '../../1/file_linux',
801           'other/file',
802         ],
803         'isolate_dir': dir_3_2,
804       },
805       ('mac',): {
806         # command in isolate3 takes precedence over the ones included.
807         'command': ['foo', 'mac'],
808         'files': [
809           '../1/file_non_linux',
810           '2/other/file',
811           'file_mac',
812         ],
813         'isolate_dir': dir_3,
814       },
815       ('win',): {
816         # command comes from isolate1.
817         'command': ['foo', 'amiga_or_win'],
818         'files': [
819           # While this may be surprising, this is because the command was
820           # defined in isolate1, not isolate3.
821           'file_non_linux',
822         ],
823         'isolate_dir': dir_1,
824       },
825     }
826     self.assertEqual(expected, actual.flatten())
827
828   def test_load_with_includes_with_commands_and_variables(self):
829     # This one is the pinacle of fun. Check that isolate_dir is the expected
830     # value. To achieve this, put the .isolate files into subdirectories.
831     dir_1 = os.path.join(self.tempdir, '1')
832     dir_3 = os.path.join(self.tempdir, '3')
833     dir_3_2 = os.path.join(self.tempdir, '3', '2')
834     os.mkdir(dir_1)
835     os.mkdir(dir_3)
836     os.mkdir(dir_3_2)
837
838     isolate1 = {
839       'conditions': [
840         ['OS=="amiga" or OS=="win"', {
841           'variables': {
842             'command': [
843               'foo', 'amiga_or_win', '<(PATH)',
844             ],
845           },
846         }],
847         ['OS=="linux"', {
848           'variables': {
849             'command': [
850               'foo', 'linux', '<(PATH)',
851             ],
852             'files': [
853               '<(PATH)/file_linux',
854             ],
855           },
856         }],
857         ['OS=="mac" or OS=="win"', {
858           'variables': {
859             'files': [
860               '<(PATH)/file_non_linux',
861             ],
862           },
863         }],
864       ],
865     }
866     isolate2 = {
867       'conditions': [
868         ['OS=="linux" or OS=="mac"', {
869           'variables': {
870             'command': [
871               'foo', 'linux_or_mac', '<(PATH)',
872             ],
873             'files': [
874               '<(PATH)/other/file',
875             ],
876           },
877         }],
878       ],
879     }
880     isolate3 = {
881       'includes': [
882         '../1/isolate1.isolate',
883         '2/isolate2.isolate',
884       ],
885       'conditions': [
886         ['OS=="amiga"', {
887           'variables': {
888             'files': [
889               '<(PATH)/file_amiga',
890             ],
891           },
892         }],
893         ['OS=="mac"', {
894           'variables': {
895             'command': [
896               'foo', 'mac', '<(PATH)',
897             ],
898             'files': [
899               '<(PATH)/file_mac',
900             ],
901           },
902         }],
903       ],
904     }
905     # No need to write isolate3.
906     with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
907       isolate_format.pretty_print(isolate1, f)
908     with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
909       isolate_format.pretty_print(isolate2, f)
910
911     # The 'isolate_dir' are important, they are what will be used when
912     # definining the final isolate_dir to use to run the command in the
913     # .isolated file.
914     actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
915     expected = {
916       (None,): {
917         'isolate_dir': dir_1,
918       },
919       ('amiga',): {
920         'command': ['foo', 'amiga_or_win', '<(PATH)'],
921         'files': [
922           '<(PATH)/file_amiga',
923         ],
924         'isolate_dir': dir_1,
925       },
926       ('linux',): {
927         # Last included takes precedence. *command comes from isolate2*, so
928         # it becomes the canonical root, so reference to file from isolate1 is
929         # via '../../1'.
930         'command': ['foo', 'linux_or_mac', '<(PATH)'],
931         'files': [
932           '<(PATH)/file_linux',
933           '<(PATH)/other/file',
934         ],
935         'isolate_dir': dir_3_2,
936       },
937       ('mac',): {
938         'command': ['foo', 'mac', '<(PATH)'],
939         'files': [
940           '<(PATH)/file_mac',
941           '<(PATH)/file_non_linux',
942           '<(PATH)/other/file',
943         ],
944         'isolate_dir': dir_3,
945       },
946       ('win',): {
947         # command comes from isolate1.
948         'command': ['foo', 'amiga_or_win', '<(PATH)'],
949         'files': [
950           '<(PATH)/file_non_linux',
951         ],
952         'isolate_dir': dir_1,
953       },
954     }
955     self.assertEqual(expected, actual.flatten())
956
957
958 if __name__ == '__main__':
959   logging.basicConfig(
960       level=logging.DEBUG if '-v' in sys.argv else logging.ERROR,
961       format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
962   if '-v' in sys.argv:
963     unittest.TestCase.maxDiff = None
964   unittest.main()