2 # Copyright 2012 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.
17 ROOT_DIR = unicode(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18 sys.path.insert(0, ROOT_DIR)
19 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party'))
21 from depot_tools import auto_stub
25 import isolated_format
27 from utils import file_path
28 from utils import tools
35 return os.stat(os.path.join(ROOT_DIR, *args)).st_size
39 return isolated_format.hash_file(os.path.join(ROOT_DIR, *args), ALGO)
42 class IsolateBase(auto_stub.TestCase):
44 super(IsolateBase, self).setUp()
45 self.mock(auth, 'ensure_logged_in', lambda _: None)
46 self.old_cwd = os.getcwd()
47 self.cwd = tempfile.mkdtemp(prefix='isolate_')
48 # Everything should work even from another directory.
53 os.chdir(self.old_cwd)
54 file_path.rmtree(self.cwd)
56 super(IsolateBase, self).tearDown()
59 class IsolateTest(IsolateBase):
60 def test_savedstate_load_minimal(self):
61 # The file referenced by 'isolate_file' must exist even if its content is
63 open(os.path.join(self.cwd, 'fake.isolate'), 'wb').close()
67 'isolate_file': 'fake.isolate',
72 'child_isolated_files': [],
73 'config_variables': {},
75 'extra_variables': {},
77 'isolate_file': 'fake.isolate',
79 'version': isolate.SavedState.EXPECTED_VERSION,
81 saved_state = isolate.SavedState.load(values, self.cwd)
82 self.assertEqual(expected, saved_state.flatten())
84 def test_savedstate_load(self):
85 # The file referenced by 'isolate_file' must exist even if its content is
87 open(os.path.join(self.cwd, 'fake.isolate'), 'wb').close()
91 'config_variables': {},
95 'isolate_file': 'fake.isolate',
100 'child_isolated_files': [],
102 'config_variables': {},
107 'isolate_file': 'fake.isolate',
108 'path_variables': {},
109 'version': isolate.SavedState.EXPECTED_VERSION,
111 saved_state = isolate.SavedState.load(values, self.cwd)
112 self.assertEqual(expected, saved_state.flatten())
114 def test_variable_arg(self):
115 parser = optparse.OptionParser()
116 isolate.add_isolate_options(parser)
117 options, args = parser.parse_args(
118 ['--config-variable', 'Foo', 'bar',
119 '--path-variable', 'Baz=sub=string',
120 '--extra-variable', 'biz', 'b uz=a'])
121 isolate.process_isolate_options(parser, options, require_isolated=False)
131 'EXECUTABLE_SUFFIX': '.exe' if sys.platform == 'win32' else '',
133 self.assertEqual(expected_path, options.path_variables)
134 self.assertEqual(expected_config, options.config_variables)
135 self.assertEqual(expected_extra, options.extra_variables)
136 self.assertEqual([], args)
138 def test_variable_arg_fail(self):
139 parser = optparse.OptionParser()
140 isolate.add_isolate_options(parser)
141 self.mock(sys, 'stderr', cStringIO.StringIO())
142 with self.assertRaises(SystemExit):
143 parser.parse_args(['--config-variable', 'Foo'])
145 def test_blacklist_default(self):
155 os.path.join('foo', '.git'),
159 blacklist = tools.gen_blacklist(isolateserver.DEFAULT_BLACKLIST)
161 self.assertFalse(blacklist(i), i)
163 self.assertTrue(blacklist(i), i)
165 def test_blacklist_custom(self):
171 'foo.run_test_cases',
173 os.path.join('foo', 'testserver.log'),
175 blacklist = tools.gen_blacklist([r'^.+\.run_test_cases$', r'^.+\.log$'])
177 self.assertFalse(blacklist(i), i)
179 self.assertTrue(blacklist(i), i)
181 def test_read_only(self):
182 isolate_file = os.path.join(self.cwd, 'fake.isolate')
188 tools.write_json(isolate_file, isolate_content, False)
194 'version': isolated_format.ISOLATED_FILE_VERSION,
196 complete_state = isolate.CompleteState(None, isolate.SavedState(self.cwd))
197 complete_state.load_isolate(
198 unicode(self.cwd), unicode(isolate_file), {}, {}, {}, None, False)
199 self.assertEqual(expected, complete_state.saved_state.to_isolated())
202 class IsolateLoad(IsolateBase):
204 super(IsolateLoad, self).setUp()
205 self.directory = tempfile.mkdtemp(prefix='isolate_')
209 file_path.rmtree(self.directory)
211 super(IsolateLoad, self).tearDown()
213 def _get_option(self, isolate_file):
214 class Options(object):
215 isolated = os.path.join(self.directory, 'foo.isolated')
216 outdir = os.path.join(self.directory, 'outdir')
217 isolate = isolate_file
218 blacklist = list(isolateserver.DEFAULT_BLACKLIST)
224 extra_variables = {'foo': 'bar'}
225 ignore_broken_items = False
228 def _cleanup_isolated(self, expected_isolated):
229 """Modifies isolated to remove the non-deterministic parts."""
230 if sys.platform == 'win32':
231 # 'm' are not saved in windows.
232 for values in expected_isolated['files'].itervalues():
233 self.assertTrue(values.pop('m'))
235 def _cleanup_saved_state(self, actual_saved_state):
236 for item in actual_saved_state['files'].itervalues():
237 self.assertTrue(item.pop('t'))
239 def test_load_stale_isolated(self):
240 isolate_file = os.path.join(
241 ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
243 # Data to be loaded in the .isolated file. Do not create a .state file.
245 'command': ['python'],
253 os.path.join('tests', 'isolate', 'touch_root.py'): {
261 options = self._get_option(isolate_file)
262 tools.write_json(options.isolated, input_data, False)
264 # A CompleteState object contains two parts:
265 # - Result instance stored in complete_state.isolated, corresponding to the
266 # .isolated file, is what is read by run_test_from_archive.py.
267 # - SavedState instance stored in compelte_state.saved_state,
268 # corresponding to the .state file, which is simply to aid the developer
269 # when re-running the same command multiple times and contain
270 # discardable information.
271 complete_state = isolate.load_complete_state(options, self.cwd, None, False)
272 actual_isolated = complete_state.saved_state.to_isolated()
273 actual_saved_state = complete_state.saved_state.flatten()
275 expected_isolated = {
277 'command': ['python', 'touch_root.py'],
279 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
281 'h': hash_file('tests', 'isolate', 'touch_root.py'),
282 's': _size('tests', 'isolate', 'touch_root.py'),
286 'h': hash_file('isolate.py'),
287 's': _size('isolate.py'),
291 'relative_cwd': os.path.join(u'tests', 'isolate'),
292 'version': isolated_format.ISOLATED_FILE_VERSION,
294 self._cleanup_isolated(expected_isolated)
295 self.assertEqual(expected_isolated, actual_isolated)
297 expected_saved_state = {
300 'child_isolated_files': [],
301 'command': ['python', 'touch_root.py'],
302 'config_variables': {
304 'chromeos': options.config_variables['chromeos'],
310 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
312 'h': hash_file('tests', 'isolate', 'touch_root.py'),
313 's': _size('tests', 'isolate', 'touch_root.py'),
317 'h': hash_file('isolate.py'),
318 's': _size('isolate.py'),
321 'isolate_file': file_path.safe_relpath(
322 file_path.get_native_path_case(isolate_file),
323 os.path.dirname(options.isolated)),
324 'path_variables': {},
325 'relative_cwd': os.path.join(u'tests', 'isolate'),
326 'root_dir': file_path.get_native_path_case(ROOT_DIR),
327 'version': isolate.SavedState.EXPECTED_VERSION,
329 self._cleanup_isolated(expected_saved_state)
330 self._cleanup_saved_state(actual_saved_state)
331 self.assertEqual(expected_saved_state, actual_saved_state)
333 def test_subdir(self):
334 # The resulting .isolated file will be missing ../../isolate.py. It is
335 # because this file is outside the --subdir parameter.
336 isolate_file = os.path.join(
337 ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
338 options = self._get_option(isolate_file)
339 complete_state = isolate.load_complete_state(
340 options, self.cwd, os.path.join('tests', 'isolate'), False)
341 actual_isolated = complete_state.saved_state.to_isolated()
342 actual_saved_state = complete_state.saved_state.flatten()
344 expected_isolated = {
346 'command': ['python', 'touch_root.py'],
348 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
350 'h': hash_file('tests', 'isolate', 'touch_root.py'),
351 's': _size('tests', 'isolate', 'touch_root.py'),
355 'relative_cwd': os.path.join(u'tests', 'isolate'),
356 'version': isolated_format.ISOLATED_FILE_VERSION,
358 self._cleanup_isolated(expected_isolated)
359 self.assertEqual(expected_isolated, actual_isolated)
361 expected_saved_state = {
364 'child_isolated_files': [],
365 'command': ['python', 'touch_root.py'],
366 'config_variables': {
374 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
376 'h': hash_file('tests', 'isolate', 'touch_root.py'),
377 's': _size('tests', 'isolate', 'touch_root.py'),
380 'isolate_file': file_path.safe_relpath(
381 file_path.get_native_path_case(isolate_file),
382 os.path.dirname(options.isolated)),
383 'path_variables': {},
384 'relative_cwd': os.path.join(u'tests', 'isolate'),
385 'root_dir': file_path.get_native_path_case(ROOT_DIR),
386 'version': isolate.SavedState.EXPECTED_VERSION,
388 self._cleanup_isolated(expected_saved_state)
389 self._cleanup_saved_state(actual_saved_state)
390 self.assertEqual(expected_saved_state, actual_saved_state)
392 def test_subdir_variable(self):
393 # the resulting .isolated file will be missing ../../isolate.py. it is
394 # because this file is outside the --subdir parameter.
395 isolate_file = os.path.join(
396 ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
397 options = self._get_option(isolate_file)
398 # Path variables are keyed on the directory containing the .isolate file.
399 options.path_variables['TEST_ISOLATE'] = '.'
400 # Note that options.isolated is in self.directory, which is a temporary
402 complete_state = isolate.load_complete_state(
403 options, os.path.join(ROOT_DIR, 'tests', 'isolate'),
404 '<(TEST_ISOLATE)', False)
405 actual_isolated = complete_state.saved_state.to_isolated()
406 actual_saved_state = complete_state.saved_state.flatten()
408 expected_isolated = {
410 'command': ['python', 'touch_root.py'],
412 os.path.join('tests', 'isolate', 'touch_root.py'): {
414 'h': hash_file('tests', 'isolate', 'touch_root.py'),
415 's': _size('tests', 'isolate', 'touch_root.py'),
419 'relative_cwd': os.path.join(u'tests', 'isolate'),
420 'version': isolated_format.ISOLATED_FILE_VERSION,
422 self._cleanup_isolated(expected_isolated)
423 self.assertEqual(expected_isolated, actual_isolated)
425 # It is important to note:
426 # - the root directory is ROOT_DIR.
427 # - relative_cwd is tests/isolate.
428 # - TEST_ISOLATE is based of relative_cwd, so it represents tests/isolate.
429 # - anything outside TEST_ISOLATE was not included in the 'files' section.
430 expected_saved_state = {
433 'child_isolated_files': [],
434 'command': ['python', 'touch_root.py'],
435 'config_variables': {
443 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
445 'h': hash_file('tests', 'isolate', 'touch_root.py'),
446 's': _size('tests', 'isolate', 'touch_root.py'),
449 'isolate_file': file_path.safe_relpath(
450 file_path.get_native_path_case(isolate_file),
451 os.path.dirname(options.isolated)),
455 'relative_cwd': os.path.join(u'tests', 'isolate'),
456 'root_dir': file_path.get_native_path_case(ROOT_DIR),
457 'version': isolate.SavedState.EXPECTED_VERSION,
459 self._cleanup_isolated(expected_saved_state)
460 self._cleanup_saved_state(actual_saved_state)
461 self.assertEqual(expected_saved_state, actual_saved_state)
463 def test_variable_not_exist(self):
464 isolate_file = os.path.join(
465 ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
466 options = self._get_option(isolate_file)
467 options.path_variables['PRODUCT_DIR'] = os.path.join(u'tests', u'isolate')
468 native_cwd = file_path.get_native_path_case(unicode(self.cwd))
470 isolate.load_complete_state(options, self.cwd, None, False)
472 except isolate.ExecutionError, e:
474 'PRODUCT_DIR=%s is not a directory' %
475 os.path.join(native_cwd, 'tests', 'isolate'),
478 def test_variable(self):
479 isolate_file = os.path.join(
480 ROOT_DIR, 'tests', 'isolate', 'touch_root.isolate')
481 options = self._get_option(isolate_file)
482 options.path_variables['PRODUCT_DIR'] = os.path.join('tests', 'isolate')
483 complete_state = isolate.load_complete_state(options, ROOT_DIR, None, False)
484 actual_isolated = complete_state.saved_state.to_isolated()
485 actual_saved_state = complete_state.saved_state.flatten()
487 expected_isolated = {
489 'command': ['python', 'touch_root.py'],
493 'h': hash_file('isolate.py'),
494 's': _size('isolate.py'),
496 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
498 'h': hash_file('tests', 'isolate', 'touch_root.py'),
499 's': _size('tests', 'isolate', 'touch_root.py'),
503 'relative_cwd': os.path.join(u'tests', 'isolate'),
504 'version': isolated_format.ISOLATED_FILE_VERSION,
506 self._cleanup_isolated(expected_isolated)
507 self.assertEqual(expected_isolated, actual_isolated)
509 expected_saved_state = {
512 'child_isolated_files': [],
513 'command': ['python', 'touch_root.py'],
514 'config_variables': {
524 'h': hash_file('isolate.py'),
525 's': _size('isolate.py'),
527 os.path.join(u'tests', 'isolate', 'touch_root.py'): {
529 'h': hash_file('tests', 'isolate', 'touch_root.py'),
530 's': _size('tests', 'isolate', 'touch_root.py'),
533 'isolate_file': file_path.safe_relpath(
534 file_path.get_native_path_case(isolate_file),
535 os.path.dirname(options.isolated)),
539 'relative_cwd': os.path.join(u'tests', 'isolate'),
540 'root_dir': file_path.get_native_path_case(ROOT_DIR),
541 'version': isolate.SavedState.EXPECTED_VERSION,
543 self._cleanup_isolated(expected_saved_state)
544 self._cleanup_saved_state(actual_saved_state)
545 self.assertEqual(expected_saved_state, actual_saved_state)
546 self.assertEqual([], os.listdir(self.directory))
548 def test_root_dir_because_of_variable(self):
549 # Ensures that load_isolate() works even when path variables have deep root
550 # dirs. The end result is similar to touch_root.isolate, except that
551 # no_run.isolate doesn't reference '..' at all.
553 # A real world example would be PRODUCT_DIR=../../out/Release but nothing in
554 # this directory is mapped.
556 # Imagine base/base_unittests.isolate would not map anything in
557 # PRODUCT_DIR. In that case, the automatically determined root dir is
558 # src/base, since nothing outside this directory is mapped.
559 isolate_file = os.path.join(
560 ROOT_DIR, 'tests', 'isolate', 'no_run.isolate')
561 options = self._get_option(isolate_file)
562 # Any directory outside ROOT_DIR/tests/isolate.
563 options.path_variables['PRODUCT_DIR'] = os.path.join('third_party')
564 complete_state = isolate.load_complete_state(options, ROOT_DIR, None, False)
565 actual_isolated = complete_state.saved_state.to_isolated()
566 actual_saved_state = complete_state.saved_state.flatten()
568 expected_isolated = {
571 os.path.join(u'tests', 'isolate', 'files1', 'subdir', '42.txt'): {
573 'h': hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt'),
574 's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
576 os.path.join(u'tests', 'isolate', 'files1', 'test_file1.txt'): {
578 'h': hash_file('tests', 'isolate', 'files1', 'test_file1.txt'),
579 's': _size('tests', 'isolate', 'files1', 'test_file1.txt'),
581 os.path.join(u'tests', 'isolate', 'files1', 'test_file2.txt'): {
583 'h': hash_file('tests', 'isolate', 'files1', 'test_file2.txt'),
584 's': _size('tests', 'isolate', 'files1', 'test_file2.txt'),
586 os.path.join(u'tests', 'isolate', 'no_run.isolate'): {
588 'h': hash_file('tests', 'isolate', 'no_run.isolate'),
589 's': _size('tests', 'isolate', 'no_run.isolate'),
593 'relative_cwd': os.path.join(u'tests', 'isolate'),
594 'version': isolated_format.ISOLATED_FILE_VERSION,
596 self._cleanup_isolated(expected_isolated)
597 self.assertEqual(expected_isolated, actual_isolated)
599 expected_saved_state = {
602 'child_isolated_files': [],
604 'config_variables': {
612 os.path.join(u'tests', 'isolate', 'files1', 'subdir', '42.txt'): {
614 'h': hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt'),
615 's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
617 os.path.join(u'tests', 'isolate', 'files1', 'test_file1.txt'): {
619 'h': hash_file('tests', 'isolate', 'files1', 'test_file1.txt'),
620 's': _size('tests', 'isolate', 'files1', 'test_file1.txt'),
622 os.path.join(u'tests', 'isolate', 'files1', 'test_file2.txt'): {
624 'h': hash_file('tests', 'isolate', 'files1', 'test_file2.txt'),
625 's': _size('tests', 'isolate', 'files1', 'test_file2.txt'),
627 os.path.join(u'tests', 'isolate', 'no_run.isolate'): {
629 'h': hash_file('tests', 'isolate', 'no_run.isolate'),
630 's': _size('tests', 'isolate', 'no_run.isolate'),
633 'isolate_file': file_path.safe_relpath(
634 file_path.get_native_path_case(isolate_file),
635 os.path.dirname(options.isolated)),
637 'PRODUCT_DIR': os.path.join(u'..', '..', 'third_party'),
639 'relative_cwd': os.path.join(u'tests', 'isolate'),
640 'root_dir': file_path.get_native_path_case(ROOT_DIR),
641 'version': isolate.SavedState.EXPECTED_VERSION,
643 self._cleanup_isolated(expected_saved_state)
644 self._cleanup_saved_state(actual_saved_state)
645 self.assertEqual(expected_saved_state, actual_saved_state)
646 self.assertEqual([], os.listdir(self.directory))
648 def test_chromium_split(self):
649 # Create an .isolate file and a tree of random stuff.
650 isolate_file = os.path.join(
651 ROOT_DIR, 'tests', 'isolate', 'split.isolate')
652 options = self._get_option(isolate_file)
653 options.path_variables = {
655 'PRODUCT_DIR': os.path.join('files1'),
657 options.config_variables = {
660 complete_state = isolate.load_complete_state(
661 options, os.path.join(ROOT_DIR, 'tests', 'isolate'), None, False)
662 # By saving the files, it forces splitting the data up.
663 complete_state.save_files()
665 actual_isolated_master = tools.read_json(
666 os.path.join(self.directory, 'foo.isolated'))
667 expected_isolated_master = {
669 u'command': [u'python', u'split.py'],
673 u'h': unicode(hash_file('tests', 'isolate', 'split.py')),
674 u's': _size('tests', 'isolate', 'split.py'),
678 unicode(hash_file(os.path.join(self.directory, 'foo.0.isolated'))),
679 unicode(hash_file(os.path.join(self.directory, 'foo.1.isolated'))),
682 u'relative_cwd': u'.',
683 u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
685 self._cleanup_isolated(expected_isolated_master)
686 self.assertEqual(expected_isolated_master, actual_isolated_master)
688 actual_isolated_0 = tools.read_json(
689 os.path.join(self.directory, 'foo.0.isolated'))
690 expected_isolated_0 = {
693 os.path.join(u'test', 'data', 'foo.txt'): {
696 hash_file('tests', 'isolate', 'test', 'data', 'foo.txt')),
697 u's': _size('tests', 'isolate', 'test', 'data', 'foo.txt'),
700 u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
702 self._cleanup_isolated(expected_isolated_0)
703 self.assertEqual(expected_isolated_0, actual_isolated_0)
705 actual_isolated_1 = tools.read_json(
706 os.path.join(self.directory, 'foo.1.isolated'))
707 expected_isolated_1 = {
710 os.path.join(u'files1', 'subdir', '42.txt'): {
713 hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt')),
714 u's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
717 u'version': unicode(isolated_format.ISOLATED_FILE_VERSION),
719 self._cleanup_isolated(expected_isolated_1)
720 self.assertEqual(expected_isolated_1, actual_isolated_1)
722 actual_saved_state = tools.read_json(
723 isolate.isolatedfile_to_state(options.isolated))
724 isolated_base = unicode(os.path.basename(options.isolated))
725 expected_saved_state = {
726 u'OS': unicode(sys.platform),
728 u'child_isolated_files': [
729 isolated_base[:-len('.isolated')] + '.0.isolated',
730 isolated_base[:-len('.isolated')] + '.1.isolated',
732 u'command': [u'python', u'split.py'],
733 u'config_variables': {
736 u'extra_variables': {
740 os.path.join(u'files1', 'subdir', '42.txt'): {
743 hash_file('tests', 'isolate', 'files1', 'subdir', '42.txt')),
744 u's': _size('tests', 'isolate', 'files1', 'subdir', '42.txt'),
748 u'h': unicode(hash_file('tests', 'isolate', 'split.py')),
749 u's': _size('tests', 'isolate', 'split.py'),
751 os.path.join(u'test', 'data', 'foo.txt'): {
754 hash_file('tests', 'isolate', 'test', 'data', 'foo.txt')),
755 u's': _size('tests', 'isolate', 'test', 'data', 'foo.txt'),
758 u'isolate_file': file_path.safe_relpath(
759 file_path.get_native_path_case(isolate_file),
760 unicode(os.path.dirname(options.isolated))),
763 u'PRODUCT_DIR': u'files1',
765 u'relative_cwd': u'.',
766 u'root_dir': file_path.get_native_path_case(
767 os.path.dirname(isolate_file)),
768 u'version': unicode(isolate.SavedState.EXPECTED_VERSION),
770 self._cleanup_isolated(expected_saved_state)
771 self._cleanup_saved_state(actual_saved_state)
772 self.assertEqual(expected_saved_state, actual_saved_state)
775 'foo.0.isolated', 'foo.1.isolated',
776 'foo.isolated', 'foo.isolated.state',
778 sorted(os.listdir(self.directory)))
780 def test_load_isolate_include_command(self):
781 # Ensure that using a .isolate that includes another one in a different
782 # directory will lead to the proper relative directory. See
783 # test_load_with_includes_with_commands in isolate_format_test.py as
786 # Exactly the same thing as in isolate_format_test.py
789 ['OS=="amiga" or OS=="win"', {
792 'foo', 'amiga_or_win',
806 ['OS=="mac" or OS=="win"', {
817 ['OS=="linux" or OS=="mac"', {
820 'foo', 'linux_or_mac',
831 '../1/isolate1.isolate',
832 '2/isolate2.isolate',
856 config_os, files_to_create, expected_files, command, relative_cwd):
857 """Creates a tree of files in a subdirectory for testing and test this
860 directory = os.path.join(unicode(self.directory), config_os)
862 isolate_dir = os.path.join(directory, u'isolate')
863 isolate_dir_1 = os.path.join(isolate_dir, u'1')
864 isolate_dir_3 = os.path.join(isolate_dir, u'3')
865 isolate_dir_3_2 = os.path.join(isolate_dir_3, u'2')
866 isolated_dir = os.path.join(directory, u'isolated')
867 os.mkdir(isolated_dir)
868 os.mkdir(isolate_dir)
869 os.mkdir(isolate_dir_1)
870 os.mkdir(isolate_dir_3)
871 os.mkdir(isolate_dir_3_2)
872 isolated = os.path.join(isolated_dir, u'foo.isolated')
874 with open(os.path.join(isolate_dir_1, 'isolate1.isolate'), 'wb') as f:
875 isolate_format.pretty_print(isolate1, f)
876 with open(os.path.join(isolate_dir_3_2, 'isolate2.isolate'), 'wb') as f:
877 isolate_format.pretty_print(isolate2, f)
878 root_isolate = os.path.join(isolate_dir_3, 'isolate3.isolate')
879 with open(root_isolate, 'wb') as f:
880 isolate_format.pretty_print(isolate3, f)
882 # Make all the touched files.
883 mapping = {1: isolate_dir_1, 2: isolate_dir_3_2, 3: isolate_dir_3}
884 for k, v in files_to_create.iteritems():
885 f = os.path.join(mapping[k], v)
886 base = os.path.dirname(f)
887 if not os.path.isdir(base):
889 open(f, 'wb').close()
891 c = isolate.CompleteState(isolated, isolate.SavedState(isolated_dir))
896 unicode(self.cwd), root_isolate, {}, config, {}, None, False)
897 # Note that load_isolate() doesn't retrieve the meta data about each file.
901 'files': {unicode(f):{} for f in expected_files},
903 'relative_cwd': relative_cwd,
904 'version': isolated_format.ISOLATED_FILE_VERSION,
906 self.assertEqual(expected, c.saved_state.to_isolated())
908 # root is .../isolate/.
917 ['foo', 'amiga_or_win'],
919 # root is .../isolate/.
930 ['foo', 'linux_or_mac'],
932 # root is .../isolate/.
947 # root is .../isolate/1/.
956 ['foo', 'amiga_or_win'],
959 def test_load_isolate_include_command_and_variables(self):
960 # Ensure that using a .isolate that includes another one in a different
961 # directory will lead to the proper relative directory when using variables.
962 # See test_load_with_includes_with_commands_and_variables in
963 # isolate_format_test.py as reference.
965 # With path variables, 'cwd' is used instead of the path to the .isolate
966 # file. So the files have to be set towards the cwd accordingly. While this
967 # may seem surprising, this makes the whole thing work in the first place.
969 # Almost exactly the same thing as in isolate_format_test.py plus the EXTRA
970 # for better testing with variable replacement.
973 ['OS=="amiga" or OS=="win"', {
976 'foo', 'amiga_or_win', '<(PATH)', '<(EXTRA)',
983 'foo', 'linux', '<(PATH)', '<(EXTRA)',
986 '<(PATH)/file_linux',
990 ['OS=="mac" or OS=="win"', {
993 '<(PATH)/file_non_linux',
1001 ['OS=="linux" or OS=="mac"', {
1004 'foo', 'linux_or_mac', '<(PATH)', '<(EXTRA)',
1007 '<(PATH)/other/file',
1015 '../1/isolate1.isolate',
1016 '2/isolate2.isolate',
1022 '<(PATH)/file_amiga',
1029 'foo', 'mac', '<(PATH)', '<(EXTRA)',
1039 def test_with_os(config_os, expected_files, command, relative_cwd):
1040 """Creates a tree of files in a subdirectory for testing and test this
1043 directory = os.path.join(unicode(self.directory), config_os)
1045 cwd = os.path.join(unicode(self.cwd), config_os)
1047 isolate_dir = os.path.join(directory, u'isolate')
1048 isolate_dir_1 = os.path.join(isolate_dir, u'1')
1049 isolate_dir_3 = os.path.join(isolate_dir, u'3')
1050 isolate_dir_3_2 = os.path.join(isolate_dir_3, u'2')
1051 isolated_dir = os.path.join(directory, u'isolated')
1052 os.mkdir(isolated_dir)
1053 os.mkdir(isolate_dir)
1054 os.mkdir(isolate_dir_1)
1055 os.mkdir(isolate_dir_3)
1056 os.mkdir(isolate_dir_3_2)
1057 isolated = os.path.join(isolated_dir, u'foo.isolated')
1059 with open(os.path.join(isolate_dir_1, 'isolate1.isolate'), 'wb') as f:
1060 isolate_format.pretty_print(isolate1, f)
1061 with open(os.path.join(isolate_dir_3_2, 'isolate2.isolate'), 'wb') as f:
1062 isolate_format.pretty_print(isolate2, f)
1063 root_isolate = os.path.join(isolate_dir_3, 'isolate3.isolate')
1064 with open(root_isolate, 'wb') as f:
1065 isolate_format.pretty_print(isolate3, f)
1067 # Make all the touched files.
1068 path_dir = os.path.join(cwd, 'path')
1070 for v in expected_files:
1071 f = os.path.join(path_dir, v)
1072 base = os.path.dirname(f)
1073 if not os.path.isdir(base):
1076 open(f, 'wb').close()
1078 c = isolate.CompleteState(isolated, isolate.SavedState(isolated_dir))
1089 unicode(cwd), root_isolate, paths, config, extra, None, False)
1090 # Note that load_isolate() doesn't retrieve the meta data about each file.
1095 unicode(os.path.join(cwd_name, config_os, 'path', f)): {}
1096 for f in expected_files
1099 'relative_cwd': relative_cwd,
1100 'version': isolated_format.ISOLATED_FILE_VERSION,
1102 self.assertEqual(expected, c.saved_state.to_isolated())
1104 cwd_name = os.path.basename(self.cwd)
1105 dir_name = os.path.basename(self.directory)
1114 u'../../../../%s/amiga/path' % cwd_name,
1117 u'%s/amiga/isolate/1' % dir_name)
1127 u'../../../../../%s/linux/path' % cwd_name,
1130 u'%s/linux/isolate/3/2' % dir_name)
1141 u'../../../../%s/mac/path' % cwd_name,
1144 u'%s/mac/isolate/3' % dir_name)
1153 u'../../../../%s/win/path' % cwd_name,
1156 u'%s/win/isolate/1' % dir_name)
1159 class IsolateCommand(IsolateBase):
1160 def load_complete_state(self, *_):
1161 """Creates a minimalist CompleteState instance without an .isolated
1164 out = isolate.CompleteState(None, isolate.SavedState(self.cwd))
1165 out.saved_state.isolate_file = u'blah.isolate'
1166 out.saved_state.relative_cwd = u''
1167 out.saved_state.root_dir = ROOT_DIR
1170 def test_CMDarchive(self):
1173 def mocked_upload_tree(base_url, infiles, namespace):
1174 # |infiles| may be a generator of pair, materialize it into a list.
1176 'base_url': base_url,
1177 'infiles': dict(infiles),
1178 'namespace': namespace,
1180 self.mock(isolateserver, 'upload_tree', mocked_upload_tree)
1183 return os.path.join(self.cwd, *path)
1185 isolate_file = join('x.isolate')
1186 isolated_file = join('x.isolated')
1187 with open(isolate_file, 'wb') as f:
1192 ' [\'OS=="dendy"\', {'
1194 ' \'files\': [\'foo\'],'
1199 with open(join('foo'), 'wb') as f:
1202 self.mock(sys, 'stdout', cStringIO.StringIO())
1205 '-s', isolated_file,
1206 '--isolate-server', 'http://localhost:1',
1207 '--config-variable', 'OS', 'dendy',
1209 self.assertEqual(0, isolate.CMDarchive(optparse.OptionParser(), cmd))
1212 'base_url': 'http://localhost:1',
1214 join(isolated_file): {
1218 'h': '520d41b29f891bbaccf31d9fcfa72e82ea20fcf0',
1222 'namespace': 'default-gzip',
1225 # These always change.
1226 actual[0]['infiles'][join(isolated_file)].pop('h')
1227 actual[0]['infiles'][join(isolated_file)].pop('s')
1228 actual[0]['infiles'][join('foo')].pop('m')
1229 actual[0]['infiles'][join('foo')].pop('t')
1230 self.assertEqual(expected, actual)
1232 def test_CMDbatcharchive(self):
1233 # Same as test_CMDarchive but via code path that parses *.gen.json files.
1236 def mocked_upload_tree(base_url, infiles, namespace):
1237 # |infiles| may be a generator of pair, materialize it into a list.
1239 'base_url': base_url,
1240 'infiles': dict(infiles),
1241 'namespace': namespace,
1243 self.mock(isolateserver, 'upload_tree', mocked_upload_tree)
1246 return os.path.join(self.cwd, *path)
1248 # First isolate: x.isolate.
1249 isolate_file_x = join('x.isolate')
1250 isolated_file_x = join('x.isolated')
1251 with open(isolate_file_x, 'wb') as f:
1256 ' [\'OS=="dendy"\', {'
1258 ' \'files\': [\'foo\'],'
1263 with open(join('foo'), 'wb') as f:
1265 with open(join('x.isolated.gen.json'), 'wb') as f:
1268 '-i', isolate_file_x,
1269 '-s', isolated_file_x,
1270 '--config-variable', 'OS', 'dendy',
1276 # Second isolate: y.isolate.
1277 isolate_file_y = join('y.isolate')
1278 isolated_file_y = join('y.isolated')
1279 with open(isolate_file_y, 'wb') as f:
1284 ' [\'OS=="dendy"\', {'
1286 ' \'files\': [\'bar\'],'
1291 with open(join('bar'), 'wb') as f:
1293 with open(join('y.isolated.gen.json'), 'wb') as f:
1296 '-i', isolate_file_y,
1297 '-s', isolated_file_y,
1298 '--config-variable', 'OS', 'dendy',
1304 self.mock(sys, 'stdout', cStringIO.StringIO())
1306 '--isolate-server', 'http://localhost:1',
1307 '--dump-json', 'json_output.json',
1308 join('x.isolated.gen.json'),
1309 join('y.isolated.gen.json'),
1312 0, isolate.CMDbatcharchive(tools.OptionParserWithLogging(), cmd))
1315 'base_url': 'http://localhost:1',
1317 join(isolated_file_x): {
1321 'h': '520d41b29f891bbaccf31d9fcfa72e82ea20fcf0',
1324 join(isolated_file_y): {
1328 'h': 'e918b3a3f9597e3cfdc62ce20ecf5756191cb3ec',
1332 'namespace': 'default-gzip',
1335 # These always change.
1336 actual[0]['infiles'][join(isolated_file_x)].pop('h')
1337 actual[0]['infiles'][join(isolated_file_x)].pop('s')
1338 actual[0]['infiles'][join('foo')].pop('m')
1339 actual[0]['infiles'][join('foo')].pop('t')
1340 actual[0]['infiles'][join(isolated_file_y)].pop('h')
1341 actual[0]['infiles'][join(isolated_file_y)].pop('s')
1342 actual[0]['infiles'][join('bar')].pop('m')
1343 actual[0]['infiles'][join('bar')].pop('t')
1344 self.assertEqual(expected, actual)
1347 'x': isolated_format.hash_file('x.isolated', ALGO),
1348 'y': isolated_format.hash_file('y.isolated', ALGO),
1350 self.assertEqual(expected_json, tools.read_json('json_output.json'))
1352 def test_CMDcheck_empty(self):
1353 isolate_file = os.path.join(self.cwd, 'x.isolate')
1354 isolated_file = os.path.join(self.cwd, 'x.isolated')
1355 with open(isolate_file, 'wb') as f:
1356 f.write('# Foo\n{\n}')
1358 self.mock(sys, 'stdout', cStringIO.StringIO())
1359 cmd = ['-i', isolate_file, '-s', isolated_file]
1360 isolate.CMDcheck(optparse.OptionParser(), cmd)
1362 def test_CMDcheck_stale_version(self):
1363 isolate_file = os.path.join(self.cwd, 'x.isolate')
1364 isolated_file = os.path.join(self.cwd, 'x.isolated')
1365 with open(isolate_file, 'wb') as f:
1370 ' [\'OS=="dendy"\', {'
1372 ' \'command\': [\'foo\'],'
1378 self.mock(sys, 'stdout', cStringIO.StringIO())
1381 '-s', isolated_file,
1382 '--config-variable', 'OS=dendy',
1384 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1386 with open(isolate_file, 'rb') as f:
1389 '# Foo\n{ \'conditions\':[ [\'OS=="dendy"\', { '
1390 '\'variables\': { \'command\': [\'foo\'], }, }], ],}')
1391 self.assertEqual(expected, actual)
1393 with open(isolated_file, 'rb') as f:
1394 actual_isolated = f.read()
1395 expected_isolated = (
1396 '{"algo":"sha-1","command":["foo"],"files":{},'
1397 '"read_only":1,"relative_cwd":".","version":"%s"}'
1398 ) % isolated_format.ISOLATED_FILE_VERSION
1399 self.assertEqual(expected_isolated, actual_isolated)
1400 isolated_data = json.loads(actual_isolated)
1402 with open(isolated_file + '.state', 'rb') as f:
1403 actual_isolated_state = f.read()
1404 expected_isolated_state = (
1405 '{"OS":"%s","algo":"sha-1","child_isolated_files":[],"command":["foo"],'
1406 '"config_variables":{"OS":"dendy"},'
1407 '"extra_variables":{"EXECUTABLE_SUFFIX":""},"files":{},'
1408 '"isolate_file":"x.isolate","path_variables":{},'
1409 '"relative_cwd":".","root_dir":"%s","version":"%s"}'
1410 ) % (sys.platform, self.cwd, isolate.SavedState.EXPECTED_VERSION)
1411 self.assertEqual(expected_isolated_state, actual_isolated_state)
1412 isolated_state_data = json.loads(actual_isolated_state)
1414 # Now edit the .isolated.state file to break the version number and make
1415 # sure it doesn't crash.
1416 with open(isolated_file + '.state', 'wb') as f:
1417 isolated_state_data['version'] = '100.42'
1418 json.dump(isolated_state_data, f)
1419 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1421 # Now edit the .isolated file to break the version number and make
1422 # sure it doesn't crash.
1423 with open(isolated_file, 'wb') as f:
1424 isolated_data['version'] = '100.42'
1425 json.dump(isolated_data, f)
1426 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1428 # Make sure the files were regenerated.
1429 with open(isolated_file, 'rb') as f:
1430 actual_isolated = f.read()
1431 self.assertEqual(expected_isolated, actual_isolated)
1432 with open(isolated_file + '.state', 'rb') as f:
1433 actual_isolated_state = f.read()
1434 self.assertEqual(expected_isolated_state, actual_isolated_state)
1436 def test_CMDcheck_new_variables(self):
1438 isolate_file = os.path.join(self.cwd, 'x.isolate')
1439 isolated_file = os.path.join(self.cwd, 'x.isolated')
1442 '-s', isolated_file,
1443 '--config-variable', 'OS=dendy',
1445 with open(isolate_file, 'wb') as f:
1450 ' [\'OS=="dendy"\', {'
1452 ' \'command\': [\'foo\'],'
1453 ' \'files\': [\'foo\'],'
1458 with open(os.path.join(self.cwd, 'foo'), 'wb') as f:
1461 self.mock(sys, 'stdout', cStringIO.StringIO())
1462 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1464 # Now add a new config variable.
1465 with open(isolate_file, 'wb') as f:
1470 ' [\'OS=="dendy"\', {'
1472 ' \'command\': [\'foo\'],'
1473 ' \'files\': [\'foo\'],'
1476 ' [\'foo=="baz"\', {'
1478 ' \'files\': [\'bar\'],'
1483 with open(os.path.join(self.cwd, 'bar'), 'wb') as f:
1484 f.write('yeah right!')
1486 # The configuration is OS=dendy and foo=bar. So it should load both
1491 optparse.OptionParser(), cmd + ['--config-variable', 'foo=bar']))
1493 def test_CMDcheck_isolate_copied(self):
1494 # Note that moving the .isolate file is a different code path, this is about
1495 # copying the .isolate file to a new place and specifying the new location
1496 # on a subsequent execution.
1497 x_isolate_file = os.path.join(self.cwd, 'x.isolate')
1498 isolated_file = os.path.join(self.cwd, 'x.isolated')
1499 cmd = ['-i', x_isolate_file, '-s', isolated_file]
1500 with open(x_isolate_file, 'wb') as f:
1502 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1503 self.assertTrue(os.path.isfile(isolated_file + '.state'))
1504 with open(isolated_file + '.state', 'rb') as f:
1505 self.assertEqual(json.load(f)['isolate_file'], 'x.isolate')
1507 # Move the .isolate file.
1508 y_isolate_file = os.path.join(self.cwd, 'Y.isolate')
1509 shutil.copyfile(x_isolate_file, y_isolate_file)
1510 cmd = ['-i', y_isolate_file, '-s', isolated_file]
1511 self.assertEqual(0, isolate.CMDcheck(optparse.OptionParser(), cmd))
1512 with open(isolated_file + '.state', 'rb') as f:
1513 self.assertEqual(json.load(f)['isolate_file'], 'Y.isolate')
1515 def test_CMDrun_extra_args(self):
1518 '--isolate', 'blah.isolate',
1521 self.mock(isolate, 'load_complete_state', self.load_complete_state)
1522 self.mock(subprocess, 'call', lambda *_, **_kwargs: 0)
1523 self.assertEqual(0, isolate.CMDrun(optparse.OptionParser(), cmd))
1525 def test_CMDrun_no_isolated(self):
1526 isolate_file = os.path.join(self.cwd, 'x.isolate')
1527 with open(isolate_file, 'wb') as f:
1528 f.write('{"variables": {"command": ["python", "-c", "print(\'hi\')"]} }')
1530 def expect_call(cmd, cwd):
1531 self.assertEqual([sys.executable, '-c', "print('hi')", 'run'], cmd)
1532 self.assertTrue(os.path.isdir(cwd))
1534 self.mock(subprocess, 'call', expect_call)
1536 cmd = ['run', '--isolate', isolate_file]
1537 self.assertEqual(0, isolate.CMDrun(optparse.OptionParser(), cmd))
1540 def clear_env_vars():
1541 for e in ('ISOLATE_DEBUG', 'ISOLATE_SERVER'):
1542 os.environ.pop(e, None)
1545 if __name__ == '__main__':