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.
19 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20 sys.path.insert(0, ROOT_DIR)
24 from utils import file_path
25 import trace_test_util
27 from isolate_format import KEY_TOUCHED, KEY_TRACKED, KEY_UNTRACKED
33 HASH_NULL = ALGO().hexdigest()
36 # These are per test case, not per mode.
38 'all_items_invalid': '.',
40 'missing_trailing_slash': '.',
45 'symlink_partial': '.',
46 'symlink_outside_build_root': '.',
48 'touch_root': os.path.join('tests', 'isolate'),
53 'all_items_invalid' : ['empty.py'],
55 'missing_trailing_slash': [],
58 os.path.join('files1', 'subdir', '42.txt'),
59 os.path.join('files1', 'test_file1.txt'),
60 os.path.join('files1', 'test_file2.txt'),
64 os.path.join('files1', 'subdir', '42.txt'),
65 os.path.join('test', 'data', 'foo.txt'),
69 os.path.join('files1', 'subdir', '42.txt'),
70 os.path.join('files1', 'test_file1.txt'),
71 os.path.join('files1', 'test_file2.txt'),
72 # files2 is a symlink to files1.
77 os.path.join('files1', 'test_file2.txt'),
78 # files2 is a symlink to files1.
82 'symlink_outside_build_root': [
83 os.path.join('link_outside_build_root', 'test_file3.txt'),
84 'symlink_outside_build_root.py',
88 os.path.join('files1', 'test_file1.txt'),
91 os.path.join('tests', 'isolate', 'touch_root.py'),
96 os.path.join('files1', 'subdir', '42.txt'),
97 os.path.join('files1', 'test_file1.txt'),
98 os.path.join('files1', 'test_file2.txt'),
103 class CalledProcessError(subprocess.CalledProcessError):
104 """Makes 2.6 version act like 2.7"""
105 def __init__(self, returncode, cmd, output, stderr, cwd):
106 super(CalledProcessError, self).__init__(returncode, cmd)
112 return super(CalledProcessError, self).__str__() + (
114 'cwd=%s\n%s\n%s\n%s') % (
121 def list_files_tree(directory):
122 """Returns the list of all the files in a tree."""
124 for root, dirnames, filenames in os.walk(directory):
125 actual.extend(os.path.join(root, f)[len(directory)+1:] for f in filenames)
126 for dirname in dirnames:
127 full = os.path.join(root, dirname)
128 # Manually include symlinks.
129 if os.path.islink(full):
130 actual.append(full[len(directory)+1:])
131 return sorted(actual)
134 def _isolate_dict_to_string(values):
135 buf = cStringIO.StringIO()
136 isolate.isolate_format.pretty_print(values, buf)
137 return buf.getvalue()
140 def _wrap_in_condition(variables):
141 """Wraps a variables dict inside the current OS condition.
143 Returns the equivalent string.
145 return _isolate_dict_to_string(
148 ['OS=="mac" and chromeos==0', {
149 'variables': variables
155 def _fix_file_mode(filename, read_only):
156 """4 modes are supported, 0750 (rwx), 0640 (rw), 0550 (rx), 0440 (r)."""
160 return (min_mode | 0110) if filename.endswith('.py') else min_mode
163 class Isolate(unittest.TestCase):
164 def test_help_modes(self):
165 # Check coherency in the help and implemented modes.
166 p = subprocess.Popen(
167 [sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), '--help'],
168 stdout=subprocess.PIPE,
169 stderr=subprocess.STDOUT,
171 out = p.communicate()[0].splitlines()
172 self.assertEqual(0, p.returncode)
173 out = out[out.index('Commands are:') + 1:]
174 out = out[:out.index('')]
175 regexp = '^ (?:\x1b\\[\\d\\dm|)(\\w+)\s*(:?\x1b\\[\\d\\dm|) .+'
176 modes = [re.match(regexp, l) for l in out]
177 modes = [m.group(1) for m in modes if m]
190 # If a new command is added it should at least has a bare test.
191 self.assertEqual(sorted(EXPECTED_MODES), sorted(modes))
194 class IsolateTempdir(unittest.TestCase):
196 super(IsolateTempdir, self).setUp()
197 self.tempdir = tempfile.mkdtemp(prefix='isolate_smoke_')
198 self.isolated = os.path.join(self.tempdir, 'isolate_smoke_test.isolated')
202 logging.debug(self.tempdir)
203 shutil.rmtree(self.tempdir)
205 super(IsolateTempdir, self).tearDown()
207 def _gen_files(self, read_only, empty_file, with_time):
208 """Returns a dict of files like calling isolate.process_input() on each
212 - read_only: Mark all the 'm' modes without the writeable bit.
213 - empty_file: Add a specific empty file (size 0).
214 - with_time: Include 't' timestamps. For saved state .state files.
217 if RELATIVE_CWD[self.case()] == '.':
218 root_dir = os.path.join(root_dir, 'tests', 'isolate')
220 files = dict((unicode(f), {}) for f in DEPENDENCIES[self.case()])
222 for relfile, v in files.iteritems():
223 filepath = os.path.join(root_dir, relfile)
224 filestats = os.lstat(filepath)
225 is_link = stat.S_ISLNK(filestats.st_mode)
227 v[u's'] = filestats.st_size
228 if sys.platform != 'win32':
229 v[u'm'] = _fix_file_mode(relfile, read_only)
231 # Used to skip recalculating the hash. Use the most recent update
233 v[u't'] = int(round(filestats.st_mtime))
235 v[u'l'] = os.readlink(filepath) # pylint: disable=E1101
237 # Upgrade the value to unicode so diffing the structure in case of
238 # test failure is easier, since the basestring type must match,
240 v[u'h'] = unicode(isolateserver.hash_file(filepath, ALGO))
243 item = files[empty_file]
244 item['h'] = unicode(HASH_NULL)
245 if sys.platform != 'win32':
253 def _expected_isolated(self, args, read_only, empty_file):
254 """Verifies self.isolated contains the expected data."""
257 u'files': self._gen_files(read_only, empty_file, False),
258 u'relative_cwd': unicode(RELATIVE_CWD[self.case()]),
259 u'version': unicode(isolate.isolateserver.ISOLATED_FILE_VERSION),
261 if read_only is not None:
262 expected[u'read_only'] = read_only
264 expected[u'command'] = [u'python'] + [unicode(x) for x in args]
265 self.assertEqual(expected, json.load(open(self.isolated, 'r')))
267 def _expected_saved_state(
268 self, args, read_only, empty_file, extra_vars, root_dir):
270 u'OS': unicode(sys.platform),
272 u'child_isolated_files': [],
274 u'config_variables': {
278 u'extra_variables': {
279 u'EXECUTABLE_SUFFIX': u'.exe' if sys.platform == 'win32' else u'',
281 u'files': self._gen_files(read_only, empty_file, True),
282 u'isolate_file': file_path.safe_relpath(
283 file_path.get_native_path_case(unicode(self.filename())),
284 unicode(os.path.dirname(self.isolated))),
285 u'path_variables': {},
286 u'relative_cwd': unicode(RELATIVE_CWD[self.case()]),
287 u'root_dir': unicode(root_dir or os.path.dirname(self.filename())),
288 u'version': unicode(isolate.SavedState.EXPECTED_VERSION),
291 expected[u'command'] = [u'python'] + [unicode(x) for x in args]
292 expected['extra_variables'].update(extra_vars or {})
293 self.assertEqual(expected, json.load(open(self.saved_state(), 'r')))
296 self, args, read_only, extra_vars, empty_file, root_dir=None):
297 self._expected_isolated(args, read_only, empty_file)
298 self._expected_saved_state(
299 args, read_only, empty_file, extra_vars, root_dir)
300 # Also verifies run_isolated.py will be able to read it.
301 with open(self.isolated, 'rb') as f:
302 isolate.isolateserver.load_isolated(f.read(), ALGO)
304 def _expect_no_result(self):
305 self.assertFalse(os.path.exists(self.isolated))
307 def _get_cmd(self, mode):
309 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
311 '--isolated', self.isolated,
312 '--isolate', self.filename(),
313 '--config-variable', 'OS', 'mac',
314 '--config-variable', 'chromeos', '0',
317 def _execute(self, mode, case, args, need_output, cwd=ROOT_DIR):
318 """Executes isolate.py."""
321 self.case() + '.isolate',
322 'Rename the test case to test_%s()' % case)
323 cmd = self._get_cmd(mode)
326 env = os.environ.copy()
327 if 'ISOLATE_DEBUG' in env:
328 del env['ISOLATE_DEBUG']
330 if need_output or not VERBOSE:
331 stdout = subprocess.PIPE
332 stderr = subprocess.PIPE
334 cmd.extend(['-v'] * 3)
339 p = subprocess.Popen(
345 universal_newlines=True)
346 out, err = p.communicate()
348 raise CalledProcessError(p.returncode, cmd, out, err, cwd)
350 # Do not check on Windows since a lot of spew is generated there.
351 if sys.platform != 'win32':
352 self.assertTrue(err in (None, ''), err)
356 """Returns the filename corresponding to this test case."""
357 test_id = self.id().split('.')
358 return re.match('^test_([a-z_]+)$', test_id[2]).group(1)
361 """Returns the filename corresponding to this test case."""
362 filename = os.path.join(
363 ROOT_DIR, 'tests', 'isolate', self.case() + '.isolate')
364 self.assertTrue(os.path.isfile(filename), filename)
367 def saved_state(self):
368 return isolate.isolatedfile_to_state(self.isolated)
370 def _test_all_items_invalid(self, mode):
371 out = self._execute(mode, 'all_items_invalid.isolate',
372 ['--ignore_broken_item'], True)
373 self._expect_results(['empty.py'], None, None, None)
377 def _test_missing_trailing_slash(self, mode):
379 self._execute(mode, 'missing_trailing_slash.isolate', [], True)
381 except subprocess.CalledProcessError as e:
382 self.assertEqual('', e.output)
384 self._expect_no_result()
385 root = file_path.get_native_path_case(unicode(ROOT_DIR))
387 'Input directory %s must have a trailing slash' %
388 os.path.join(root, 'tests', 'isolate', 'files1')
390 self.assertIn(expected, out)
392 def _test_non_existent(self, mode):
394 self._execute(mode, 'non_existent.isolate', [], True)
396 except subprocess.CalledProcessError as e:
397 self.assertEqual('', e.output)
399 self._expect_no_result()
400 root = file_path.get_native_path_case(unicode(ROOT_DIR))
402 'Input file %s doesn\'t exist' %
403 os.path.join(root, 'tests', 'isolate', 'A_file_that_do_not_exist')
405 self.assertIn(expected, out)
408 class IsolateOutdir(IsolateTempdir):
410 super(IsolateOutdir, self).setUp()
411 # The tests assume the current directory is the file's directory.
413 self.outdir = os.path.join(self.tempdir, 'isolated')
415 def _expect_no_tree(self):
416 # No outdir was created.
417 self.assertFalse(os.path.exists(self.outdir))
419 def _result_tree(self):
420 return list_files_tree(self.outdir)
422 def _expected_tree(self):
423 """Verifies the files written in the temporary directory."""
424 self.assertEqual(sorted(DEPENDENCIES[self.case()]), self._result_tree())
426 def _get_cmd(self, mode):
427 """Adds --outdir for the commands supporting it."""
428 cmd = super(IsolateOutdir, self)._get_cmd(mode)
429 cmd.extend(('--outdir', self.outdir))
432 def _test_missing_trailing_slash(self, mode):
433 super(IsolateOutdir, self)._test_missing_trailing_slash(mode)
434 self._expect_no_tree()
436 def _test_non_existent(self, mode):
437 super(IsolateOutdir, self)._test_non_existent(mode)
438 self._expect_no_tree()
441 class Isolate_check(IsolateTempdir):
443 self._execute('check', 'fail.isolate', [], False)
444 self._expect_results(['fail.py'], None, None, None)
446 def test_missing_trailing_slash(self):
447 self._test_missing_trailing_slash('check')
449 def test_non_existent(self):
450 self._test_non_existent('check')
452 def test_all_items_invalid(self):
453 out = self._test_all_items_invalid('check')
454 self.assertEqual('', out)
456 def test_no_run(self):
457 self._execute('check', 'no_run.isolate', [], False)
458 self._expect_results([], None, None, None)
460 # TODO(csharp): Disabled until crbug.com/150823 is fixed.
461 def do_not_test_touch_only(self):
463 'check', 'touch_only.isolate', ['--extra-variable', 'FLAG', 'gyp'],
465 empty = os.path.join('files1', 'test_file1.txt')
466 self._expected_isolated(['touch_only.py', 'gyp'], None, empty)
468 def test_touch_root(self):
469 self._execute('check', 'touch_root.isolate', [], False)
470 self._expect_results(['touch_root.py'], None, None, None, ROOT_DIR)
472 def test_with_flag(self):
474 'check', 'with_flag.isolate', ['--extra-variable', 'FLAG', 'gyp'],
476 self._expect_results(
477 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None)
479 if sys.platform != 'win32':
480 def test_symlink_full(self):
481 self._execute('check', 'symlink_full.isolate', [], False)
482 self._expect_results(['symlink_full.py'], None, None, None)
484 def test_symlink_partial(self):
485 self._execute('check', 'symlink_partial.isolate', [], False)
486 self._expect_results(['symlink_partial.py'], None, None, None)
488 def test_symlink_outside_build_root(self):
489 self._execute('check', 'symlink_outside_build_root.isolate', [], False)
490 self._expect_results(['symlink_outside_build_root.py'], None, None, None)
493 class Isolate_hashtable(IsolateOutdir):
494 def _gen_expected_tree(self, empty_file):
497 for v in self._gen_files(False, empty_file, False).itervalues()
500 unicode(isolateserver.hash_file(self.isolated, ALGO)))
503 def _expected_hash_tree(self, empty_file):
504 """Verifies the files written in the temporary directory."""
506 sorted(self._gen_expected_tree(empty_file)),
507 map(unicode, self._result_tree()))
510 self._execute('hashtable', 'fail.isolate', [], False)
511 self._expected_hash_tree(None)
512 self._expect_results(['fail.py'], None, None, None)
514 def test_missing_trailing_slash(self):
515 self._test_missing_trailing_slash('hashtable')
517 def test_non_existent(self):
518 self._test_non_existent('hashtable')
520 def test_all_items_invalid(self):
521 out = self._test_all_items_invalid('hashtable')
523 '%s isolate_smoke_test.isolated\n' %
524 isolateserver.hash_file(self.isolated, ALGO))
525 self.assertEqual(expected, out)
526 self._expected_hash_tree(None)
528 def test_no_run(self):
529 self._execute('hashtable', 'no_run.isolate', [], False)
530 self._expected_hash_tree(None)
531 self._expect_results([], None, None, None)
533 def test_split(self):
538 '--path-variable', 'DEPTH', '.',
539 '--path-variable', 'PRODUCT_DIR', 'files1',
542 cwd=os.path.join(ROOT_DIR, 'tests', 'isolate'))
543 # Reimplement _expected_hash_tree():
544 tree = self._gen_expected_tree(None)
545 isolated_base = self.isolated[:-len('.isolated')]
547 unicode(isolateserver.hash_file(isolated_base + '.0.isolated', ALGO)),
548 unicode(isolateserver.hash_file(isolated_base + '.1.isolated', ALGO)),
550 tree.extend(isolated_hashes)
551 self.assertEqual(sorted(tree), map(unicode, self._result_tree()))
553 # Reimplement _expected_isolated():
554 files = self._gen_files(None, None, False)
557 u'command': [u'python', u'split.py'],
558 u'files': {u'split.py': files['split.py']},
559 u'includes': isolated_hashes,
560 u'relative_cwd': unicode(RELATIVE_CWD[self.case()]),
561 u'version': unicode(isolate.isolateserver.ISOLATED_FILE_VERSION),
563 self.assertEqual(expected, json.load(open(self.isolated, 'r')))
565 key = os.path.join(u'test', 'data', 'foo.txt')
568 u'files': {key: files[key]},
569 u'version': unicode(isolate.isolateserver.ISOLATED_FILE_VERSION),
572 expected, json.load(open(isolated_base + '.0.isolated', 'r')))
574 key = os.path.join(u'files1', 'subdir', '42.txt')
577 u'files': {key: files[key]},
578 u'version': unicode(isolate.isolateserver.ISOLATED_FILE_VERSION),
581 expected, json.load(open(isolated_base + '.1.isolated', 'r')))
583 # TODO(csharp): Disabled until crbug.com/150823 is fixed.
584 def do_not_test_touch_only(self):
586 'hashtable', 'touch_only.isolate', ['--extra-variable', 'FLAG', 'gyp'],
588 empty = os.path.join('files1', 'test_file1.txt')
589 self._expected_hash_tree(empty)
590 self._expected_isolated(['touch_only.py', 'gyp'], None, empty)
592 def test_touch_root(self):
593 self._execute('hashtable', 'touch_root.isolate', [], False)
594 self._expected_hash_tree(None)
595 self._expect_results(['touch_root.py'], None, None, None, ROOT_DIR)
597 def test_with_flag(self):
599 'hashtable', 'with_flag.isolate', ['--extra-variable', 'FLAG', 'gyp'],
601 self._expected_hash_tree(None)
602 self._expect_results(
603 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None)
605 if sys.platform != 'win32':
606 def test_symlink_full(self):
607 self._execute('hashtable', 'symlink_full.isolate', [], False)
608 # Construct our own tree.
611 for v in self._gen_files(False, None, False).itervalues() if 'h' in v
613 expected.append(isolateserver.hash_file(self.isolated, ALGO))
614 self.assertEqual(sorted(expected), self._result_tree())
615 self._expect_results(['symlink_full.py'], None, None, None)
617 def test_symlink_partial(self):
618 self._execute('hashtable', 'symlink_partial.isolate', [], False)
619 # Construct our own tree.
622 for v in self._gen_files(False, None, False).itervalues() if 'h' in v
624 expected.append(isolateserver.hash_file(self.isolated, ALGO))
625 self.assertEqual(sorted(expected), self._result_tree())
626 self._expect_results(['symlink_partial.py'], None, None, None)
628 def test_symlink_outside_build_root(self):
630 'hashtable', 'symlink_outside_build_root.isolate', [], False)
631 # Construct our own tree.
634 for v in self._gen_files(False, None, False).itervalues() if 'h' in v
636 expected.append(isolateserver.hash_file(self.isolated, ALGO))
637 self.assertEqual(sorted(expected), self._result_tree())
638 self._expect_results(['symlink_outside_build_root.py'], None, None, None)
641 class Isolate_remap(IsolateOutdir):
643 self._execute('remap', 'fail.isolate', [], False)
644 self._expected_tree()
645 self._expect_results(['fail.py'], None, None, None)
647 def test_missing_trailing_slash(self):
648 self._test_missing_trailing_slash('remap')
650 def test_non_existent(self):
651 self._test_non_existent('remap')
653 def test_all_items_invalid(self):
654 out = self._test_all_items_invalid('remap')
655 self.assertTrue(out.startswith('Remapping'))
656 self._expected_tree()
658 def test_no_run(self):
659 self._execute('remap', 'no_run.isolate', [], False)
660 self._expected_tree()
661 self._expect_results([], None, None, None)
663 # TODO(csharp): Disabled until crbug.com/150823 is fixed.
664 def do_not_test_touch_only(self):
666 'remap', 'touch_only.isolate', ['--extra-variable', 'FLAG', 'gyp'],
668 self._expected_tree()
669 empty = os.path.join('files1', 'test_file1.txt')
670 self._expect_results(
671 ['touch_only.py', 'gyp'], None, {u'FLAG': u'gyp'}, empty)
673 def test_touch_root(self):
674 self._execute('remap', 'touch_root.isolate', [], False)
675 self._expected_tree()
676 self._expect_results(['touch_root.py'], None, None, None, ROOT_DIR)
678 def test_with_flag(self):
680 'remap', 'with_flag.isolate', ['--extra-variable', 'FLAG', 'gyp'],
682 self._expected_tree()
683 self._expect_results(
684 ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None)
686 if sys.platform != 'win32':
687 def test_symlink_full(self):
688 self._execute('remap', 'symlink_full.isolate', [], False)
689 self._expected_tree()
690 self._expect_results(['symlink_full.py'], None, None, None)
692 def test_symlink_partial(self):
693 self._execute('remap', 'symlink_partial.isolate', [], False)
694 self._expected_tree()
695 self._expect_results(['symlink_partial.py'], None, None, None)
697 def test_symlink_outside_build_root(self):
698 self._execute('remap', 'symlink_outside_build_root.isolate', [], False)
699 self._expected_tree()
700 self._expect_results(['symlink_outside_build_root.py'], None, None, None)
703 class Isolate_run(IsolateTempdir):
706 self._execute('run', 'fail.isolate', [], False)
708 except subprocess.CalledProcessError:
710 self._expect_results(['fail.py'], None, None, None)
712 def test_missing_trailing_slash(self):
713 self._test_missing_trailing_slash('run')
715 def test_non_existent(self):
716 self._test_non_existent('run')
718 def test_all_items_invalid(self):
719 out = self._test_all_items_invalid('run')
720 self.assertEqual('', out)
722 def test_no_run(self):
724 self._execute('run', 'no_run.isolate', [], False)
726 except subprocess.CalledProcessError:
728 self._expect_no_result()
730 # TODO(csharp): Disabled until crbug.com/150823 is fixed.
731 def do_not_test_touch_only(self):
733 'run', 'touch_only.isolate', ['--extra-variable', 'FLAG', 'run'],
735 empty = os.path.join('files1', 'test_file1.txt')
736 self._expect_results(
737 ['touch_only.py', 'run'], None, {u'FLAG': u'run'}, empty)
739 def test_touch_root(self):
740 self._execute('run', 'touch_root.isolate', [], False)
741 self._expect_results(['touch_root.py'], None, None, None, ROOT_DIR)
743 def test_with_flag(self):
745 'run', 'with_flag.isolate', ['--extra-variable', 'FLAG', 'run'],
747 self._expect_results(
748 ['with_flag.py', 'run'], None, {u'FLAG': u'run'}, None)
750 if sys.platform != 'win32':
751 def test_symlink_full(self):
752 self._execute('run', 'symlink_full.isolate', [], False)
753 self._expect_results(['symlink_full.py'], None, None, None)
755 def test_symlink_partial(self):
756 self._execute('run', 'symlink_partial.isolate', [], False)
757 self._expect_results(['symlink_partial.py'], None, None, None)
759 def test_symlink_outside_build_root(self):
760 self._execute('run', 'symlink_outside_build_root.isolate', [], False)
761 self._expect_results(['symlink_outside_build_root.py'], None, None, None)
764 class Isolate_trace_read_merge(IsolateTempdir):
765 # Tests both trace, read and merge.
766 # Warning: merge updates .isolate files. But they are currently in their
767 # canonical format so they shouldn't be changed.
768 def _check_merge(self, filename):
769 filepath = file_path.get_native_path_case(
770 os.path.join(unicode(ROOT_DIR), 'tests', 'isolate', filename))
771 expected = 'Updating %s\n' % file_path.safe_relpath(filepath, self.tempdir)
772 with open(filepath, 'rb') as f:
773 old_content = f.read()
774 out = self._execute('merge', filename, [], True) or ''
775 self.assertEqual(expected, out)
776 with open(filepath, 'rb') as f:
777 new_content = f.read()
778 self.assertEqual(old_content, new_content)
780 @trace_test_util.check_can_trace
782 # Even if the process returns non-zero, the trace will still be good.
784 self._execute('trace', 'fail.isolate', ['-v'], True)
786 except subprocess.CalledProcessError, e:
787 self.assertEqual('', e.output)
788 self._expect_results(['fail.py'], None, None, None)
789 expected = _wrap_in_condition(
791 KEY_TRACKED: ['fail.py'],
793 out = self._execute('read', 'fail.isolate', [], True) or ''
794 self.assertEqual(expected.splitlines(), out.splitlines())
795 self._check_merge('fail.isolate')
797 def test_missing_trailing_slash(self):
798 self._test_missing_trailing_slash('trace')
800 def test_non_existent(self):
801 self._test_non_existent('trace')
803 @trace_test_util.check_can_trace
804 def test_all_items_invalid(self):
805 out = self._test_all_items_invalid('trace')
806 self.assertEqual('', out)
808 def test_no_run(self):
810 self._execute('trace', 'no_run.isolate', [], True)
812 except subprocess.CalledProcessError, e:
815 self._expect_no_result()
816 expected = 'No command to run.'
817 self.assertEqual('', out)
818 self.assertIn(expected, err)
820 # TODO(csharp): Disabled until crbug.com/150823 is fixed.
821 def do_not_test_touch_only(self):
823 'trace', 'touch_only.isolate', ['--extra-variable', 'FLAG', 'trace'],
825 self.assertEqual('', out)
826 empty = os.path.join('files1', 'test_file1.txt')
827 self._expect_results(
828 ['touch_only.py', 'trace'], None, {u'FLAG': u'trace'}, empty)
830 KEY_TRACKED: ['touch_only.py'],
831 # Note that .isolate format mandates / and not os.path.sep.
832 KEY_TOUCHED: ['files1/test_file1.txt'],
834 if sys.platform != 'linux2':
835 # TODO(maruel): Implement touch-only tracing on non-linux.
836 del expected[KEY_TOUCHED]
838 out = self._execute('read', 'touch_only.isolate', [], True)
839 self.assertEqual(_wrap_in_condition(expected), out)
840 self._check_merge('touch_only.isolate')
842 @trace_test_util.check_can_trace
843 def test_touch_root(self):
844 out = self._execute('trace', 'touch_root.isolate', [], True)
845 self.assertEqual('', out)
846 self._expect_results(['touch_root.py'], None, None, None, ROOT_DIR)
847 expected = _wrap_in_condition(
854 out = self._execute('read', 'touch_root.isolate', [], True)
855 self.assertEqual(expected, out)
856 self._check_merge('touch_root.isolate')
858 @trace_test_util.check_can_trace
859 def test_with_flag(self):
861 'trace', 'with_flag.isolate', ['--extra-variable', 'FLAG', 'trace'],
863 self.assertEqual('', out)
864 self._expect_results(
865 ['with_flag.py', 'trace'], None, {u'FLAG': u'trace'}, None)
867 KEY_TRACKED: ['with_flag.py'],
868 # Note that .isolate format mandates / and not os.path.sep.
869 KEY_UNTRACKED: ['files1/'],
871 out = self._execute('read', 'with_flag.isolate', [], True)
872 self.assertEqual(_wrap_in_condition(expected), out)
873 self._check_merge('with_flag.isolate')
875 if sys.platform != 'win32':
876 def test_symlink_full(self):
878 'trace', 'symlink_full.isolate', [], True)
879 self.assertEqual('', out)
880 self._expect_results(['symlink_full.py'], None, None, None)
882 KEY_TRACKED: ['symlink_full.py'],
883 # Note that .isolate format mandates / and not os.path.sep.
884 KEY_UNTRACKED: ['files2/'],
886 out = self._execute('read', 'symlink_full.isolate', [], True)
887 self.assertEqual(_wrap_in_condition(expected), out)
888 self._check_merge('symlink_full.isolate')
890 def test_symlink_partial(self):
892 'trace', 'symlink_partial.isolate', [], True)
893 self.assertEqual('', out)
894 self._expect_results(['symlink_partial.py'], None, None, None)
896 KEY_TRACKED: ['symlink_partial.py'],
897 KEY_UNTRACKED: ['files2/test_file2.txt'],
899 out = self._execute('read', 'symlink_partial.isolate', [], True)
900 self.assertEqual(_wrap_in_condition(expected), out)
901 self._check_merge('symlink_partial.isolate')
903 def test_symlink_outside_build_root(self):
905 'trace', 'symlink_outside_build_root.isolate', [], True)
906 self.assertEqual('', out)
907 self._expect_results(['symlink_outside_build_root.py'], None, None, None)
909 KEY_TRACKED: ['symlink_outside_build_root.py'],
910 KEY_UNTRACKED: ['link_outside_build_root/'],
913 'read', 'symlink_outside_build_root.isolate', [], True)
914 self.assertEqual(_wrap_in_condition(expected), out)
915 self._check_merge('symlink_outside_build_root.isolate')
918 class IsolateNoOutdir(IsolateTempdir):
919 # Test without the --outdir flag.
920 # So all the files are first copied in the tempdir and the test is run from
923 super(IsolateNoOutdir, self).setUp()
924 self.root = os.path.join(self.tempdir, 'root')
925 os.makedirs(os.path.join(self.root, 'tests', 'isolate'))
926 for i in ('touch_root.isolate', 'touch_root.py'):
928 os.path.join(ROOT_DIR, 'tests', 'isolate', i),
929 os.path.join(self.root, 'tests', 'isolate', i))
931 os.path.join(ROOT_DIR, 'isolate.py'),
932 os.path.join(self.root, 'isolate.py'))
934 def _execute(self, mode, args, need_output): # pylint: disable=W0221
935 """Executes isolate.py."""
937 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
939 '--isolated', self.isolated,
940 '--config-variable', 'OS', 'mac',
941 '--config-variable', 'chromeos', '0',
945 env = os.environ.copy()
946 if 'ISOLATE_DEBUG' in env:
947 del env['ISOLATE_DEBUG']
949 if need_output or not VERBOSE:
950 stdout = subprocess.PIPE
951 stderr = subprocess.STDOUT
953 cmd.extend(['-v'] * 3)
959 p = subprocess.Popen(
965 universal_newlines=True)
966 out, err = p.communicate()
968 raise CalledProcessError(p.returncode, cmd, out, err, cwd)
972 """Returns the execution mode corresponding to this test case."""
973 test_id = self.id().split('.')
974 self.assertEqual(3, len(test_id))
975 self.assertEqual('__main__', test_id[0])
976 return re.match('^test_([a-z]+)$', test_id[2]).group(1)
979 """Returns the filename corresponding to this test case."""
980 filename = os.path.join(self.root, 'tests', 'isolate', 'touch_root.isolate')
981 self.assertTrue(os.path.isfile(filename), filename)
984 def test_check(self):
985 self._execute('check', ['--isolate', self.filename()], False)
987 'isolate_smoke_test.isolated',
988 'isolate_smoke_test.isolated.state',
989 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'),
990 os.path.join('root', 'tests', 'isolate', 'touch_root.py'),
991 os.path.join('root', 'isolate.py'),
993 self.assertEqual(files, list_files_tree(self.tempdir))
995 def test_hashtable(self):
996 with self.assertRaises(CalledProcessError):
997 self._execute('hashtable', ['--isolate', self.filename()], False)
999 def test_remap(self):
1000 with self.assertRaises(CalledProcessError):
1001 self._execute('remap', ['--isolate', self.filename()], False)
1004 self._execute('run', ['--isolate', self.filename()], False)
1006 'isolate_smoke_test.isolated',
1007 'isolate_smoke_test.isolated.state',
1008 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'),
1009 os.path.join('root', 'tests', 'isolate', 'touch_root.py'),
1010 os.path.join('root', 'isolate.py'),
1012 self.assertEqual(files, list_files_tree(self.tempdir))
1014 @trace_test_util.check_can_trace
1015 def test_trace_read_merge(self):
1016 self._execute('trace', ['--isolate', self.filename()], False)
1017 # Read the trace before cleaning up. No need to specify self.filename()
1018 # because add the needed information is in the .state file.
1019 output = self._execute('read', [], True)
1026 self.assertEqual(_wrap_in_condition(expected), output)
1028 output = self._execute('merge', [], True)
1029 expected = 'Updating %s\n' % os.path.join(
1030 'root', 'tests', 'isolate', 'touch_root.isolate')
1031 self.assertEqual(expected, output)
1032 # In theory the file is going to be updated but in practice its content
1035 # Clean the directory from the logs, which are OS-specific.
1036 isolate.trace_inputs.get_api().clean_trace(
1037 os.path.join(self.tempdir, 'isolate_smoke_test.isolated.log'))
1039 'isolate_smoke_test.isolated',
1040 'isolate_smoke_test.isolated.state',
1041 os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'),
1042 os.path.join('root', 'tests', 'isolate', 'touch_root.py'),
1043 os.path.join('root', 'isolate.py'),
1045 self.assertEqual(files, list_files_tree(self.tempdir))
1048 class IsolateOther(IsolateTempdir):
1049 def test_run_mixed(self):
1050 # Test when a user mapped from a directory and then replay from another
1051 # directory. This is a very rare corner case.
1052 indir = os.path.join(self.tempdir, 'input')
1054 for i in ('simple.py', 'simple.isolate'):
1056 os.path.join(ROOT_DIR, 'tests', 'isolate', i),
1057 os.path.join(indir, i))
1058 proc = subprocess.Popen(
1060 sys.executable, 'isolate.py',
1062 '-i', os.path.join(indir, 'simple.isolate'),
1063 '-s', os.path.join(indir, 'simple.isolated'),
1064 '--config-variable', 'OS', 'mac',
1066 stdout=subprocess.PIPE,
1067 stderr=subprocess.STDOUT,
1069 stdout = proc.communicate()[0]
1070 self.assertEqual('', stdout)
1071 self.assertEqual(0, proc.returncode)
1073 'simple.isolate', 'simple.isolated', 'simple.isolated.state', 'simple.py',
1075 self.assertEqual(expected, sorted(os.listdir(indir)))
1077 # Remove the original directory.
1078 indir2 = indir + '2'
1079 os.rename(indir, indir2)
1081 # simple.isolated.state is required; it contains the variables.
1082 proc = subprocess.Popen(
1084 sys.executable, 'isolate.py', 'run',
1085 '-s', os.path.join(indir2, 'simple.isolated'),
1088 stdout=subprocess.PIPE,
1089 stderr=subprocess.STDOUT,
1091 universal_newlines=True)
1092 stdout = proc.communicate()[0]
1093 self.assertEqual(1, proc.returncode)
1094 self.assertTrue('simple.py is missing' in stdout)
1096 def test_empty_and_renamed(self):
1097 a_isolate = os.path.join(self.tempdir, 'a.isolate')
1098 with open(a_isolate, 'wb') as f:
1102 sys.executable, 'isolate.py', 'check',
1103 '-s', os.path.join(self.tempdir, 'out.isolated'),
1105 subprocess.check_call(cmd + ['-i', a_isolate])
1107 # Move the .isolate file aside and rerun the command with the new source but
1109 b_isolate = os.path.join(self.tempdir, 'b.isolate')
1110 os.rename(a_isolate, b_isolate)
1111 subprocess.check_call(cmd + ['-i', b_isolate])
1114 if __name__ == '__main__':
1115 VERBOSE = '-v' in sys.argv
1116 logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR)
1118 unittest.TestCase.maxDiff = None