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.
13 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 sys.path.insert(0, ROOT_DIR)
17 from utils import file_path
20 # Access to a protected member XXX of a client class
21 # pylint: disable=W0212
25 u'z:\\path\\to\\non_existing'
26 if sys.platform == 'win32' else u'/path/to/non_existing')
29 class IsolateFormatTest(unittest.TestCase):
30 def test_unknown_key(self):
32 isolate_format.verify_variables({'foo': [],})
34 except AssertionError:
37 def test_unknown_var(self):
39 isolate_format.verify_condition({'variables': {'foo': [],}}, {})
41 except AssertionError:
44 def test_eval_content(self):
46 # Intrinsics are not available.
47 isolate_format.eval_content('map(str, [1, 2])')
52 def test_load_isolate_as_config_empty(self):
55 'isolate_dir': FAKE_DIR,
60 isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).flatten())
62 def test_load_isolate_as_config(self):
65 ['OS=="amiga" or OS=="atari" or OS=="coleco" or OS=="dendy"', {
67 'files': ['a', 'b', 'touched'],
72 'files': ['c', 'd', 'touched_a', 'x'],
73 'command': ['echo', 'Hello World'],
77 ['OS=="amiga" or OS=="coleco" or OS=="dendy"', {
79 'files': ['e', 'f', 'touched_e', 'x'],
80 'command': ['echo', 'You should get an Atari'],
89 ['OS=="amiga" or OS=="atari" or OS=="dendy"', {
98 'isolate_dir': FAKE_DIR,
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,
107 'files': ['a', 'b', 'c', 'd', 'h', 'touched', 'touched_a', 'x'],
108 'command': ['echo', 'Hello World'],
109 'isolate_dir': FAKE_DIR,
113 'files': ['a', 'b', 'e', 'f', 'touched', 'touched_e', 'x'],
114 'command': ['echo', 'You should get an Atari'],
115 'isolate_dir': FAKE_DIR,
118 'files': ['a', 'b', 'e', 'f', 'h', 'touched', 'touched_e', 'x'],
119 'command': ['echo', 'You should get an Atari'],
120 'isolate_dir': FAKE_DIR,
124 expected, isolate_format.load_isolate_as_config(
125 FAKE_DIR, value, None).flatten())
127 def test_load_isolate_as_config_duplicate_command(self):
130 'command': ['rm', '-rf', '/'],
135 'command': ['echo', 'Hello World'],
141 isolate_format.load_isolate_as_config(FAKE_DIR, value, None)
143 except AssertionError:
146 def test_load_isolate_as_config_no_variable(self):
149 'command': ['echo', 'You should get an Atari'],
150 'files': ['a', 'b', 'touched'],
154 # The key is the empty tuple, since there is no variable to bind to.
157 'command': ['echo', 'You should get an Atari'],
158 'files': ['a', 'b', 'touched'],
159 'isolate_dir': FAKE_DIR,
164 expected, isolate_format.load_isolate_as_config(
165 FAKE_DIR, value, None).flatten())
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))
175 'isolate_dir': FAKE_DIR,
178 self.assertEqual(expected, actual.flatten())
180 def test_load_two_conditions(self):
207 'isolate_dir': FAKE_DIR,
210 'files': ['file_common', 'file_linux'],
211 'isolate_dir': FAKE_DIR,
214 'files': ['file_common', 'file_mac'],
215 'isolate_dir': FAKE_DIR,
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)
224 self.assertEqual(expected, configs)
226 def test_load_three_conditions(self):
229 ['OS=="linux" and chromeos==1', {
241 ['OS=="mac" and chromeos==0', {
253 ['OS=="win" and chromeos==0', {
265 'isolate_dir': FAKE_DIR,
268 'files': ['file_common', 'file_linux'],
269 'isolate_dir': FAKE_DIR,
272 'files': ['file_common', 'file_mac'],
273 'isolate_dir': FAKE_DIR,
276 'files': ['file_common', 'file_win'],
277 'isolate_dir': FAKE_DIR,
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())
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'))
292 def test_get_map_keys(self):
294 (0, None, 1), isolate_format._get_map_keys(('a', 'b', 'c'), ('a', 'c')))
296 def test_map_keys(self):
299 isolate_format._map_keys((0, None, 1), ('a', 'c')))
301 def test_load_multi_variables(self):
302 # Load an .isolate with different condition on different variables.
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())
322 'isolate_dir': FAKE_DIR,
326 'isolate_dir': FAKE_DIR,
330 'isolate_dir': FAKE_DIR,
332 # TODO(maruel): It is a conflict.
335 'isolate_dir': FAKE_DIR,
338 self.assertEqual(expected, flatten)
340 def test_union_multi_variables(self):
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())
366 'isolate_dir': FAKE_DIR,
370 'isolate_dir': FAKE_DIR,
374 'isolate_dir': FAKE_DIR,
377 self.assertEqual(expected, flatten)
379 def test_ConfigSettings_union(self):
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')
386 'files': ['data/', 'test/data/'],
387 'isolate_dir': '/src/base',
389 self.assertEqual(expected, out.flatten())
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)
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)
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)
411 def test_extract_comment(self):
413 '# Foo\n# Bar\n', isolate_format.extract_comment('# Foo\n# Bar\n{}'))
414 self.assertEqual('', isolate_format.extract_comment('{}'))
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())
421 def test_pretty_print_empty(self):
422 self._test_pretty_print_impl({}, '{\n}\n')
424 def test_pretty_print_mid_size(self):
441 'command': ['python', '-c', 'print "H\\i\'"'],
450 isolate_format.verify_root(value, {})
451 # This is an .isolate format.
461 " ['OS==\"foo\"', {\n"
466 " 'print \"H\\i\'\"',\n"
477 " ['OS==\"bar\"', {\n"
483 self._test_pretty_print_impl(value, expected)
485 def test_convert_old_to_new_else(self):
486 isolate_with_else_clauses = {
489 'variables': {'foo': 'bar'},
491 'variables': {'x': 'y'},
495 with self.assertRaises(isolate_format.IsolateError):
496 isolate_format.load_isolate_as_config(
497 FAKE_DIR, isolate_with_else_clauses, None)
499 def test_match_configs(self):
502 ('OS=="win"', ('OS',), [('win',), ('mac',), ('linux',)]),
507 '(foo==1 or foo==2) and bar=="b"',
509 [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
511 [(1, 'b'), (2, 'b')],
517 [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
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')],
527 'foo==1 or bar=="b"',
529 [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
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)],
537 for data, expected in expectations:
538 self.assertEqual(expected, isolate_format.match_configs(*data))
540 def test_load_with_globals(self):
556 ['OS=="mac" or OS=="win"', {
571 'isolate_dir': FAKE_DIR,
577 'isolate_dir': FAKE_DIR,
584 'isolate_dir': FAKE_DIR,
591 'isolate_dir': FAKE_DIR,
595 actual = isolate_format.load_isolate_as_config(FAKE_DIR, values, None)
596 self.assertEqual(expected, actual.flatten())
599 class IsolateFormatTmpDirTest(unittest.TestCase):
601 super(IsolateFormatTmpDirTest, self).setUp()
602 self.tempdir = tempfile.mkdtemp(prefix='isolate_')
606 file_path.rmtree(self.tempdir)
608 super(IsolateFormatTmpDirTest, self).tearDown()
610 def test_load_with_includes(self):
626 ['OS=="mac" or OS=="win"', {
636 with open(os.path.join(self.tempdir, 'included.isolate'), 'wb') as f:
637 isolate_format.pretty_print(included_isolate, f)
639 'includes': ['included.isolate'],
656 actual = isolate_format.load_isolate_as_config(self.tempdir, values, None)
664 'isolate_dir': self.tempdir,
670 'isolate_dir': self.tempdir,
678 'isolate_dir': self.tempdir,
685 'isolate_dir': self.tempdir,
689 self.assertEqual(expected, actual.flatten())
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')
703 ['OS=="amiga" or OS=="win"', {
706 'foo', 'amiga_or_win',
720 ['OS=="mac" or OS=="win"', {
731 ['OS=="linux" or OS=="mac"', {
734 'foo', 'linux_or_mac',
745 '../1/isolate1.isolate',
746 '2/isolate2.isolate',
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)
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
777 actual = isolate_format.load_isolate_as_config(dir_3, isolate3, 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,
785 'command': ['foo', 'amiga_or_win'],
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.
792 'isolate_dir': dir_1,
795 # Last included takes precedence. *command comes from isolate2*, so
796 # it becomes the canonical root, so reference to file from isolate1 is
798 'command': ['foo', 'linux_or_mac'],
800 '../../1/file_linux',
803 'isolate_dir': dir_3_2,
806 # command in isolate3 takes precedence over the ones included.
807 'command': ['foo', 'mac'],
809 '../1/file_non_linux',
813 'isolate_dir': dir_3,
816 # command comes from isolate1.
817 'command': ['foo', 'amiga_or_win'],
819 # While this may be surprising, this is because the command was
820 # defined in isolate1, not isolate3.
823 'isolate_dir': dir_1,
826 self.assertEqual(expected, actual.flatten())
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')
840 ['OS=="amiga" or OS=="win"', {
843 'foo', 'amiga_or_win', '<(PATH)',
850 'foo', 'linux', '<(PATH)',
853 '<(PATH)/file_linux',
857 ['OS=="mac" or OS=="win"', {
860 '<(PATH)/file_non_linux',
868 ['OS=="linux" or OS=="mac"', {
871 'foo', 'linux_or_mac', '<(PATH)',
874 '<(PATH)/other/file',
882 '../1/isolate1.isolate',
883 '2/isolate2.isolate',
889 '<(PATH)/file_amiga',
896 'foo', 'mac', '<(PATH)',
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)
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
914 actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
917 'isolate_dir': dir_1,
920 'command': ['foo', 'amiga_or_win', '<(PATH)'],
922 '<(PATH)/file_amiga',
924 'isolate_dir': dir_1,
927 # Last included takes precedence. *command comes from isolate2*, so
928 # it becomes the canonical root, so reference to file from isolate1 is
930 'command': ['foo', 'linux_or_mac', '<(PATH)'],
932 '<(PATH)/file_linux',
933 '<(PATH)/other/file',
935 'isolate_dir': dir_3_2,
938 'command': ['foo', 'mac', '<(PATH)'],
941 '<(PATH)/file_non_linux',
942 '<(PATH)/other/file',
944 'isolate_dir': dir_3,
947 # command comes from isolate1.
948 'command': ['foo', 'amiga_or_win', '<(PATH)'],
950 '<(PATH)/file_non_linux',
952 'isolate_dir': dir_1,
955 self.assertEqual(expected, actual.flatten())
958 if __name__ == '__main__':
960 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR,
961 format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
963 unittest.TestCase.maxDiff = None