| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import cStringIO |
| import hashlib |
| import json |
| import logging |
| import os |
| import re |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| import tempfile |
| import unittest |
| |
| ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| sys.path.insert(0, ROOT_DIR) |
| |
| import isolate |
| import isolateserver |
| from utils import file_path |
| |
| |
| VERBOSE = False |
| |
| ALGO = hashlib.sha1 |
| HASH_NULL = ALGO().hexdigest() |
| |
| |
| # Keep the list hard coded. |
| EXPECTED_MODES = ( |
| 'archive', |
| 'check', |
| 'help', |
| 'merge', |
| 'read', |
| 'remap', |
| 'rewrite', |
| 'run', |
| 'trace', |
| ) |
| |
| # These are per test case, not per mode. |
| RELATIVE_CWD = { |
| 'all_items_invalid': '.', |
| 'fail': '.', |
| 'missing_trailing_slash': '.', |
| 'no_run': '.', |
| 'non_existent': '.', |
| 'split': '.', |
| 'symlink_full': '.', |
| 'symlink_partial': '.', |
| 'symlink_outside_build_root': '.', |
| 'touch_only': '.', |
| 'touch_root': os.path.join('tests', 'isolate'), |
| 'with_flag': '.', |
| } |
| |
| DEPENDENCIES = { |
| 'all_items_invalid' : ['empty.py'], |
| 'fail': ['fail.py'], |
| 'missing_trailing_slash': [], |
| 'no_run': [ |
| 'no_run.isolate', |
| os.path.join('files1', 'subdir', '42.txt'), |
| os.path.join('files1', 'test_file1.txt'), |
| os.path.join('files1', 'test_file2.txt'), |
| ], |
| 'non_existent': [], |
| 'split': [ |
| os.path.join('files1', 'subdir', '42.txt'), |
| os.path.join('test', 'data', 'foo.txt'), |
| 'split.py', |
| ], |
| 'symlink_full': [ |
| os.path.join('files1', 'subdir', '42.txt'), |
| os.path.join('files1', 'test_file1.txt'), |
| os.path.join('files1', 'test_file2.txt'), |
| # files2 is a symlink to files1. |
| 'files2', |
| 'symlink_full.py', |
| ], |
| 'symlink_partial': [ |
| os.path.join('files1', 'test_file2.txt'), |
| # files2 is a symlink to files1. |
| 'files2', |
| 'symlink_partial.py', |
| ], |
| 'symlink_outside_build_root': [ |
| os.path.join('link_outside_build_root', 'test_file3.txt'), |
| 'symlink_outside_build_root.py', |
| ], |
| 'touch_only': [ |
| 'touch_only.py', |
| os.path.join('files1', 'test_file1.txt'), |
| ], |
| 'touch_root': [ |
| os.path.join('tests', 'isolate', 'touch_root.py'), |
| 'isolate.py', |
| ], |
| 'with_flag': [ |
| 'with_flag.py', |
| os.path.join('files1', 'subdir', '42.txt'), |
| os.path.join('files1', 'test_file1.txt'), |
| os.path.join('files1', 'test_file2.txt'), |
| ], |
| } |
| |
| |
| class CalledProcessError(subprocess.CalledProcessError): |
| """Makes 2.6 version act like 2.7""" |
| def __init__(self, returncode, cmd, output, stderr, cwd): |
| super(CalledProcessError, self).__init__(returncode, cmd) |
| self.output = output |
| self.stderr = stderr |
| self.cwd = cwd |
| |
| def __str__(self): |
| return super(CalledProcessError, self).__str__() + ( |
| '\n' |
| 'cwd=%s\n%s\n%s\n%s') % ( |
| self.cwd, |
| self.output, |
| self.stderr, |
| ' '.join(self.cmd)) |
| |
| |
| def list_files_tree(directory): |
| """Returns the list of all the files in a tree.""" |
| actual = [] |
| for root, dirnames, filenames in os.walk(directory): |
| actual.extend(os.path.join(root, f)[len(directory)+1:] for f in filenames) |
| for dirname in dirnames: |
| full = os.path.join(root, dirname) |
| # Manually include symlinks. |
| if os.path.islink(full): |
| actual.append(full[len(directory)+1:]) |
| return sorted(actual) |
| |
| |
| class IsolateBase(unittest.TestCase): |
| def setUp(self): |
| # The tests assume the current directory is the file's directory. |
| os.chdir(ROOT_DIR) |
| self.tempdir = tempfile.mkdtemp() |
| self.isolated = os.path.join(self.tempdir, 'isolate_smoke_test.isolated') |
| self.outdir = os.path.join(self.tempdir, 'isolated') |
| |
| def tearDown(self): |
| logging.debug(self.tempdir) |
| shutil.rmtree(self.tempdir) |
| |
| @staticmethod |
| def _isolate_dict_to_string(values): |
| buf = cStringIO.StringIO() |
| isolate.pretty_print(values, buf) |
| return buf.getvalue() |
| |
| @classmethod |
| def _wrap_in_condition(cls, variables): |
| """Wraps a variables dict inside the current OS condition. |
| |
| Returns the equivalent string. |
| """ |
| flavor = isolate.get_flavor() |
| chromeos_value = int(flavor == 'linux') |
| return cls._isolate_dict_to_string( |
| { |
| 'conditions': [ |
| ['OS=="%s" and chromeos==%d' % (flavor, chromeos_value), { |
| 'variables': variables |
| }], |
| ], |
| }) |
| |
| |
| class IsolateModeBase(IsolateBase): |
| def _expect_no_tree(self): |
| self.assertFalse(os.path.exists(self.outdir)) |
| |
| def _result_tree(self): |
| return list_files_tree(self.outdir) |
| |
| def _expected_tree(self): |
| """Verifies the files written in the temporary directory.""" |
| self.assertEqual(sorted(DEPENDENCIES[self.case()]), self._result_tree()) |
| |
| @staticmethod |
| def _fix_file_mode(filename, read_only): |
| """4 modes are supported, 0750 (rwx), 0640 (rw), 0550 (rx), 0440 (r).""" |
| min_mode = 0440 |
| if not read_only: |
| min_mode |= 0200 |
| return (min_mode | 0110) if filename.endswith('.py') else min_mode |
| |
| def _gen_files(self, read_only, empty_file, with_time): |
| """Returns a dict of files like calling isolate.process_input() on each |
| file. |
| |
| Arguments: |
| - read_only: Mark all the 'm' modes without the writeable bit. |
| - empty_file: Add a specific empty file (size 0). |
| - with_time: Include 't' timestamps. For saved state .state files. |
| """ |
| root_dir = ROOT_DIR |
| if RELATIVE_CWD[self.case()] == '.': |
| root_dir = os.path.join(root_dir, 'tests', 'isolate') |
| |
| files = dict((unicode(f), {}) for f in DEPENDENCIES[self.case()]) |
| |
| for relfile, v in files.iteritems(): |
| filepath = os.path.join(root_dir, relfile) |
| filestats = os.lstat(filepath) |
| is_link = stat.S_ISLNK(filestats.st_mode) |
| if not is_link: |
| v[u's'] = filestats.st_size |
| if isolate.get_flavor() != 'win': |
| v[u'm'] = self._fix_file_mode(relfile, read_only) |
| if with_time: |
| # Used to skip recalculating the hash. Use the most recent update |
| # time. |
| v[u't'] = int(round(filestats.st_mtime)) |
| if is_link: |
| v[u'l'] = os.readlink(filepath) # pylint: disable=E1101 |
| else: |
| # Upgrade the value to unicode so diffing the structure in case of |
| # test failure is easier, since the basestring type must match, |
| # str!=unicode. |
| v[u'h'] = unicode(isolateserver.hash_file(filepath, ALGO)) |
| |
| if empty_file: |
| item = files[empty_file] |
| item['h'] = unicode(HASH_NULL) |
| if sys.platform != 'win32': |
| item['m'] = 288 |
| item['s'] = 0 |
| if with_time: |
| item['T'] = True |
| item.pop('t', None) |
| return files |
| |
| def _expected_isolated(self, args, read_only, empty_file): |
| """Verifies self.isolated contains the expected data.""" |
| expected = { |
| u'algo': u'sha-1', |
| u'files': self._gen_files(read_only, empty_file, False), |
| u'os': unicode(isolate.get_flavor()), |
| u'relative_cwd': unicode(RELATIVE_CWD[self.case()]), |
| u'version': u'1.0', |
| } |
| if read_only is not None: |
| expected[u'read_only'] = read_only |
| if args: |
| expected[u'command'] = [u'python'] + [unicode(x) for x in args] |
| self.assertEqual(expected, json.load(open(self.isolated, 'r'))) |
| |
| def _expected_saved_state(self, args, read_only, empty_file, extra_vars): |
| flavor = isolate.get_flavor() |
| chromeos_value = int(flavor == 'linux') |
| expected = { |
| u'algo': u'sha-1', |
| u'child_isolated_files': [], |
| u'command': [], |
| u'files': self._gen_files(read_only, empty_file, True), |
| u'isolate_file': isolate.safe_relpath( |
| file_path.get_native_path_case(unicode(self.filename())), |
| unicode(os.path.dirname(self.isolated))), |
| u'relative_cwd': unicode(RELATIVE_CWD[self.case()]), |
| u'variables': { |
| u'EXECUTABLE_SUFFIX': u'.exe' if flavor == 'win' else u'', |
| u'OS': unicode(flavor), |
| u'chromeos': chromeos_value, |
| }, |
| u'version': u'1.0', |
| } |
| if args: |
| expected[u'command'] = [u'python'] + [unicode(x) for x in args] |
| expected['variables'].update(extra_vars or {}) |
| self.assertEqual(expected, json.load(open(self.saved_state(), 'r'))) |
| |
| def _expect_results(self, args, read_only, extra_vars, empty_file): |
| self._expected_isolated(args, read_only, empty_file) |
| self._expected_saved_state(args, read_only, empty_file, extra_vars) |
| # Also verifies run_isolated.py will be able to read it. |
| isolate.isolateserver.load_isolated( |
| open(self.isolated, 'r').read(), |
| isolate.run_isolated.get_flavor(), ALGO) |
| |
| def _expect_no_result(self): |
| self.assertFalse(os.path.exists(self.isolated)) |
| |
| def _execute(self, mode, case, args, need_output, cwd=ROOT_DIR): |
| """Executes isolate.py.""" |
| self.assertEqual( |
| case, |
| self.case() + '.isolate', |
| 'Rename the test case to test_%s()' % case) |
| chromeos_value = int(isolate.get_flavor() == 'linux') |
| cmd = [ |
| sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), |
| mode, |
| '--isolated', self.isolated, |
| '--outdir', self.outdir, |
| '--isolate', self.filename(), |
| '-V', 'chromeos', str(chromeos_value), |
| ] |
| cmd.extend(args) |
| |
| env = os.environ.copy() |
| if 'ISOLATE_DEBUG' in env: |
| del env['ISOLATE_DEBUG'] |
| |
| if need_output or not VERBOSE: |
| stdout = subprocess.PIPE |
| stderr = subprocess.PIPE |
| else: |
| cmd.extend(['-v'] * 3) |
| stdout = None |
| stderr = None |
| |
| logging.debug(cmd) |
| p = subprocess.Popen( |
| cmd, |
| stdout=stdout, |
| stderr=stderr, |
| cwd=cwd, |
| env=env, |
| universal_newlines=True) |
| out, err = p.communicate() |
| if p.returncode: |
| raise CalledProcessError(p.returncode, cmd, out, err, cwd) |
| |
| # Do not check on Windows since a lot of spew is generated there. |
| if sys.platform != 'win32': |
| self.assertTrue(err in (None, ''), err) |
| return out |
| |
| def case(self): |
| """Returns the filename corresponding to this test case.""" |
| test_id = self.id().split('.') |
| return re.match('^test_([a-z_]+)$', test_id[2]).group(1) |
| |
| def filename(self): |
| """Returns the filename corresponding to this test case.""" |
| filename = os.path.join( |
| ROOT_DIR, 'tests', 'isolate', self.case() + '.isolate') |
| self.assertTrue(os.path.isfile(filename), filename) |
| return filename |
| |
| def saved_state(self): |
| return isolate.isolatedfile_to_state(self.isolated) |
| |
| def _test_missing_trailing_slash(self, mode): |
| try: |
| self._execute(mode, 'missing_trailing_slash.isolate', [], True) |
| self.fail() |
| except subprocess.CalledProcessError as e: |
| self.assertEqual('', e.output) |
| out = e.stderr |
| self._expect_no_tree() |
| self._expect_no_result() |
| root = file_path.get_native_path_case(unicode(ROOT_DIR)) |
| expected = ( |
| 'Input directory %s must have a trailing slash' % |
| os.path.join(root, 'tests', 'isolate', 'files1') |
| ) |
| self.assertIn(expected, out) |
| |
| def _test_non_existent(self, mode): |
| try: |
| self._execute(mode, 'non_existent.isolate', [], True) |
| self.fail() |
| except subprocess.CalledProcessError as e: |
| self.assertEqual('', e.output) |
| out = e.stderr |
| self._expect_no_tree() |
| self._expect_no_result() |
| root = file_path.get_native_path_case(unicode(ROOT_DIR)) |
| expected = ( |
| 'Input file %s doesn\'t exist' % |
| os.path.join(root, 'tests', 'isolate', 'A_file_that_do_not_exist') |
| ) |
| self.assertIn(expected, out) |
| |
| def _test_all_items_invalid(self, mode): |
| out = self._execute(mode, 'all_items_invalid.isolate', |
| ['--ignore_broken_item'], True) |
| self._expect_results(['empty.py'], None, None, None) |
| |
| return out or '' |
| |
| |
| class Isolate(unittest.TestCase): |
| # Does not inherit from the other *Base classes. |
| def test_help_modes(self): |
| # Check coherency in the help and implemented modes. |
| p = subprocess.Popen( |
| [sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), '--help'], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| cwd=ROOT_DIR) |
| out = p.communicate()[0].splitlines() |
| self.assertEqual(0, p.returncode) |
| out = out[out.index('Commands are:') + 1:] |
| out = out[:out.index('')] |
| regexp = '^ (?:\x1b\\[\\d\\dm)(\\w+)\s*(:?\x1b\\[\\d\\dm) .+' |
| modes = [re.match(regexp, l) for l in out] |
| modes = [m.group(1) for m in modes if m] |
| self.assertEqual(sorted(EXPECTED_MODES), sorted(modes)) |
| |
| def test_modes(self): |
| # This is a bit redundant but make sure all combinations are tested. |
| files = sorted( |
| i[:-len('.isolate')] |
| for i in os.listdir(os.path.join(ROOT_DIR, 'tests', 'isolate')) |
| if i.endswith('.isolate') |
| ) |
| files.remove('simple') |
| self.assertEqual(sorted(RELATIVE_CWD), files) |
| self.assertEqual(sorted(DEPENDENCIES), files) |
| |
| if sys.platform == 'win32': |
| # Symlink stuff is unsupported there, remove them from the list. |
| files = [f for f in files if not f.startswith('symlink_')] |
| |
| files.remove('split') |
| # TODO(csharp): touch_only is disabled until crbug.com/150823 is fixed. |
| files.remove('touch_only') |
| |
| # modes read and trace are tested together. |
| modes_to_check = list(EXPECTED_MODES) |
| # Tested with test_help_modes. |
| modes_to_check.remove('help') |
| # Tested with trace_read_merge. |
| modes_to_check.remove('merge') |
| # Tested with trace_read_merge. |
| modes_to_check.remove('read') |
| # Tested in isolate_test.py. |
| modes_to_check.remove('rewrite') |
| # Tested with trace_read_merge. |
| modes_to_check.remove('trace') |
| modes_to_check.append('trace_read_merge') |
| for mode in modes_to_check: |
| expected_cases = set('test_%s' % f for f in files) |
| fixture_name = 'Isolate_%s' % mode |
| fixture = getattr(sys.modules[__name__], fixture_name) |
| actual_cases = set(i for i in dir(fixture) if i.startswith('test_')) |
| actual_cases.discard('split') |
| missing = expected_cases - actual_cases |
| self.assertFalse(missing, '%s.%s' % (fixture_name, missing)) |
| |
| |
| class Isolate_check(IsolateModeBase): |
| def test_fail(self): |
| self._execute('check', 'fail.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results(['fail.py'], None, None, None) |
| |
| def test_missing_trailing_slash(self): |
| self._test_missing_trailing_slash('check') |
| |
| def test_non_existent(self): |
| self._test_non_existent('check') |
| |
| def test_all_items_invalid(self): |
| out = self._test_all_items_invalid('check') |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| |
| def test_no_run(self): |
| self._execute('check', 'no_run.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results([], None, None, None) |
| |
| # TODO(csharp): Disabled until crbug.com/150823 is fixed. |
| def do_not_test_touch_only(self): |
| self._execute('check', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) |
| self._expect_no_tree() |
| empty = os.path.join('files1', 'test_file1.txt') |
| self._expected_isolated(['touch_only.py', 'gyp'], None, empty) |
| |
| def test_touch_root(self): |
| self._execute('check', 'touch_root.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results(['touch_root.py'], None, None, None) |
| |
| def test_with_flag(self): |
| self._execute('check', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) |
| self._expect_no_tree() |
| self._expect_results( |
| ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) |
| |
| if sys.platform != 'win32': |
| def test_symlink_full(self): |
| self._execute('check', 'symlink_full.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results(['symlink_full.py'], None, None, None) |
| |
| def test_symlink_partial(self): |
| self._execute('check', 'symlink_partial.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results(['symlink_partial.py'], None, None, None) |
| |
| def test_symlink_outside_build_root(self): |
| self._execute('check', 'symlink_outside_build_root.isolate', [], False) |
| self._expect_no_tree() |
| self._expect_results(['symlink_outside_build_root.py'], None, None, None) |
| |
| |
| class Isolate_archive(IsolateModeBase): |
| def _gen_expected_tree(self, empty_file): |
| expected = [ |
| unicode(v['h']) |
| for v in self._gen_files(False, empty_file, False).itervalues() |
| ] |
| expected.append( |
| unicode(isolateserver.hash_file(self.isolated, ALGO))) |
| return expected |
| |
| def _expected_hash_tree(self, empty_file): |
| """Verifies the files written in the temporary directory.""" |
| self.assertEqual( |
| sorted(self._gen_expected_tree(empty_file)), |
| map(unicode, self._result_tree())) |
| |
| def test_fail(self): |
| self._execute('archive', 'fail.isolate', [], False) |
| self._expected_hash_tree(None) |
| self._expect_results(['fail.py'], None, None, None) |
| |
| def test_missing_trailing_slash(self): |
| self._test_missing_trailing_slash('archive') |
| |
| def test_non_existent(self): |
| self._test_non_existent('archive') |
| |
| def test_all_items_invalid(self): |
| out = self._test_all_items_invalid('archive') |
| expected = ( |
| '%s isolate_smoke_test.isolated\n' % |
| isolateserver.hash_file(self.isolated, ALGO)) |
| self.assertEqual(expected, out) |
| self._expected_hash_tree(None) |
| |
| def test_no_run(self): |
| self._execute('archive', 'no_run.isolate', [], False) |
| self._expected_hash_tree(None) |
| self._expect_results([], None, None, None) |
| |
| def test_split(self): |
| self._execute( |
| 'archive', |
| 'split.isolate', |
| ['-V', 'DEPTH', '.', '-V', 'PRODUCT_DIR', 'files1'], |
| False, |
| cwd=os.path.join(ROOT_DIR, 'tests', 'isolate')) |
| # Reimplement _expected_hash_tree(): |
| tree = self._gen_expected_tree(None) |
| isolated_base = self.isolated[:-len('.isolated')] |
| isolated_hashes = [ |
| unicode(isolateserver.hash_file(isolated_base + '.0.isolated', ALGO)), |
| unicode(isolateserver.hash_file(isolated_base + '.1.isolated', ALGO)), |
| ] |
| tree.extend(isolated_hashes) |
| self.assertEqual(sorted(tree), map(unicode, self._result_tree())) |
| |
| # Reimplement _expected_isolated(): |
| files = self._gen_files(None, None, False) |
| expected = { |
| u'algo': u'sha-1', |
| u'command': [u'python', u'split.py'], |
| u'files': {u'split.py': files['split.py']}, |
| u'includes': isolated_hashes, |
| u'os': unicode(isolate.get_flavor()), |
| u'relative_cwd': unicode(RELATIVE_CWD[self.case()]), |
| u'version': u'1.0', |
| } |
| self.assertEqual(expected, json.load(open(self.isolated, 'r'))) |
| |
| key = os.path.join(u'test', 'data', 'foo.txt') |
| expected = { |
| u'algo': u'sha-1', |
| u'files': {key: files[key]}, |
| u'os': unicode(isolate.get_flavor()), |
| u'version': u'1.0', |
| } |
| self.assertEqual( |
| expected, json.load(open(isolated_base + '.0.isolated', 'r'))) |
| |
| key = os.path.join(u'files1', 'subdir', '42.txt') |
| expected = { |
| u'algo': u'sha-1', |
| u'files': {key: files[key]}, |
| u'os': unicode(isolate.get_flavor()), |
| u'version': u'1.0', |
| } |
| self.assertEqual( |
| expected, json.load(open(isolated_base + '.1.isolated', 'r'))) |
| |
| |
| # TODO(csharp): Disabled until crbug.com/150823 is fixed. |
| def do_not_test_touch_only(self): |
| self._execute( |
| 'archive', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) |
| empty = os.path.join('files1', 'test_file1.txt') |
| self._expected_hash_tree(empty) |
| self._expected_isolated(['touch_only.py', 'gyp'], None, empty) |
| |
| def test_touch_root(self): |
| self._execute('archive', 'touch_root.isolate', [], False) |
| self._expected_hash_tree(None) |
| self._expect_results(['touch_root.py'], None, None, None) |
| |
| def test_with_flag(self): |
| self._execute( |
| 'archive', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) |
| self._expected_hash_tree(None) |
| self._expect_results( |
| ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) |
| |
| if sys.platform != 'win32': |
| def test_symlink_full(self): |
| self._execute('archive', 'symlink_full.isolate', [], False) |
| # Construct our own tree. |
| expected = [ |
| str(v['h']) |
| for v in self._gen_files(False, None, False).itervalues() if 'h' in v |
| ] |
| expected.append(isolateserver.hash_file(self.isolated, ALGO)) |
| self.assertEqual(sorted(expected), self._result_tree()) |
| self._expect_results(['symlink_full.py'], None, None, None) |
| |
| def test_symlink_partial(self): |
| self._execute('archive', 'symlink_partial.isolate', [], False) |
| # Construct our own tree. |
| expected = [ |
| str(v['h']) |
| for v in self._gen_files(False, None, False).itervalues() if 'h' in v |
| ] |
| expected.append(isolateserver.hash_file(self.isolated, ALGO)) |
| self.assertEqual(sorted(expected), self._result_tree()) |
| self._expect_results(['symlink_partial.py'], None, None, None) |
| |
| def test_symlink_outside_build_root(self): |
| self._execute( |
| 'archive', 'symlink_outside_build_root.isolate', [], False) |
| # Construct our own tree. |
| expected = [ |
| str(v['h']) |
| for v in self._gen_files(False, None, False).itervalues() if 'h' in v |
| ] |
| expected.append(isolateserver.hash_file(self.isolated, ALGO)) |
| self.assertEqual(sorted(expected), self._result_tree()) |
| self._expect_results(['symlink_outside_build_root.py'], None, None, None) |
| |
| |
| class Isolate_remap(IsolateModeBase): |
| def test_fail(self): |
| self._execute('remap', 'fail.isolate', [], False) |
| self._expected_tree() |
| self._expect_results(['fail.py'], None, None, None) |
| |
| def test_missing_trailing_slash(self): |
| self._test_missing_trailing_slash('remap') |
| |
| def test_non_existent(self): |
| self._test_non_existent('remap') |
| |
| def test_all_items_invalid(self): |
| out = self._test_all_items_invalid('remap') |
| self.assertTrue(out.startswith('Remapping')) |
| self._expected_tree() |
| |
| def test_no_run(self): |
| self._execute('remap', 'no_run.isolate', [], False) |
| self._expected_tree() |
| self._expect_results([], None, None, None) |
| |
| # TODO(csharp): Disabled until crbug.com/150823 is fixed. |
| def do_not_test_touch_only(self): |
| self._execute('remap', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False) |
| self._expected_tree() |
| empty = os.path.join('files1', 'test_file1.txt') |
| self._expect_results( |
| ['touch_only.py', 'gyp'], None, {u'FLAG': u'gyp'}, empty) |
| |
| def test_touch_root(self): |
| self._execute('remap', 'touch_root.isolate', [], False) |
| self._expected_tree() |
| self._expect_results(['touch_root.py'], None, None, None) |
| |
| def test_with_flag(self): |
| self._execute('remap', 'with_flag.isolate', ['-V', 'FLAG', 'gyp'], False) |
| self._expected_tree() |
| self._expect_results( |
| ['with_flag.py', 'gyp'], None, {u'FLAG': u'gyp'}, None) |
| |
| if sys.platform != 'win32': |
| def test_symlink_full(self): |
| self._execute('remap', 'symlink_full.isolate', [], False) |
| self._expected_tree() |
| self._expect_results(['symlink_full.py'], None, None, None) |
| |
| def test_symlink_partial(self): |
| self._execute('remap', 'symlink_partial.isolate', [], False) |
| self._expected_tree() |
| self._expect_results(['symlink_partial.py'], None, None, None) |
| |
| def test_symlink_outside_build_root(self): |
| self._execute('remap', 'symlink_outside_build_root.isolate', [], False) |
| self._expected_tree() |
| self._expect_results(['symlink_outside_build_root.py'], None, None, None) |
| |
| |
| class Isolate_run(IsolateModeBase): |
| def _expect_empty_tree(self): |
| self.assertEqual([], self._result_tree()) |
| |
| def test_fail(self): |
| try: |
| self._execute('run', 'fail.isolate', [], False) |
| self.fail() |
| except subprocess.CalledProcessError: |
| pass |
| self._expect_empty_tree() |
| self._expect_results(['fail.py'], None, None, None) |
| |
| def test_missing_trailing_slash(self): |
| self._test_missing_trailing_slash('run') |
| |
| def test_non_existent(self): |
| self._test_non_existent('run') |
| |
| def test_all_items_invalid(self): |
| out = self._test_all_items_invalid('run') |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| |
| def test_no_run(self): |
| try: |
| self._execute('run', 'no_run.isolate', [], False) |
| self.fail() |
| except subprocess.CalledProcessError: |
| pass |
| self._expect_empty_tree() |
| self._expect_no_result() |
| |
| # TODO(csharp): Disabled until crbug.com/150823 is fixed. |
| def do_not_test_touch_only(self): |
| self._execute('run', 'touch_only.isolate', ['-V', 'FLAG', 'run'], False) |
| self._expect_empty_tree() |
| empty = os.path.join('files1', 'test_file1.txt') |
| self._expect_results( |
| ['touch_only.py', 'run'], None, {u'FLAG': u'run'}, empty) |
| |
| def test_touch_root(self): |
| self._execute('run', 'touch_root.isolate', [], False) |
| self._expect_empty_tree() |
| self._expect_results(['touch_root.py'], None, None, None) |
| |
| def test_with_flag(self): |
| self._execute('run', 'with_flag.isolate', ['-V', 'FLAG', 'run'], False) |
| # Not sure about the empty tree, should be deleted. |
| self._expect_empty_tree() |
| self._expect_results( |
| ['with_flag.py', 'run'], None, {u'FLAG': u'run'}, None) |
| |
| if sys.platform != 'win32': |
| def test_symlink_full(self): |
| self._execute('run', 'symlink_full.isolate', [], False) |
| self._expect_empty_tree() |
| self._expect_results(['symlink_full.py'], None, None, None) |
| |
| def test_symlink_partial(self): |
| self._execute('run', 'symlink_partial.isolate', [], False) |
| self._expect_empty_tree() |
| self._expect_results(['symlink_partial.py'], None, None, None) |
| |
| def test_symlink_outside_build_root(self): |
| self._execute('run', 'symlink_outside_build_root.isolate', [], False) |
| self._expect_empty_tree() |
| self._expect_results(['symlink_outside_build_root.py'], None, None, None) |
| |
| |
| class Isolate_trace_read_merge(IsolateModeBase): |
| # Tests both trace, read and merge. |
| # Warning: merge updates .isolate files. But they are currently in their |
| # canonical format so they shouldn't be changed. |
| def _check_merge(self, filename): |
| filepath = file_path.get_native_path_case( |
| os.path.join(unicode(ROOT_DIR), 'tests', 'isolate', filename)) |
| expected = 'Updating %s\n' % isolate.safe_relpath(filepath, self.tempdir) |
| with open(filepath, 'rb') as f: |
| old_content = f.read() |
| out = self._execute('merge', filename, [], True) or '' |
| self.assertEqual(expected, out) |
| with open(filepath, 'rb') as f: |
| new_content = f.read() |
| self.assertEqual(old_content, new_content) |
| |
| def test_fail(self): |
| # Even if the process returns non-zero, the trace will still be good. |
| try: |
| self._execute('trace', 'fail.isolate', ['-v'], True) |
| self.fail() |
| except subprocess.CalledProcessError, e: |
| self.assertEqual('', e.output) |
| self._expect_no_tree() |
| self._expect_results(['fail.py'], None, None, None) |
| expected = self._wrap_in_condition( |
| { |
| isolate.KEY_TRACKED: [ |
| 'fail.py', |
| ], |
| }) |
| out = self._execute('read', 'fail.isolate', [], True) or '' |
| self.assertEqual(expected.splitlines(), out.splitlines()) |
| self._check_merge('fail.isolate') |
| |
| def test_missing_trailing_slash(self): |
| self._test_missing_trailing_slash('trace') |
| |
| def test_non_existent(self): |
| self._test_non_existent('trace') |
| |
| def test_all_items_invalid(self): |
| out = self._test_all_items_invalid('trace') |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| |
| def test_no_run(self): |
| try: |
| self._execute('trace', 'no_run.isolate', [], True) |
| self.fail() |
| except subprocess.CalledProcessError, e: |
| out = e.output |
| err = e.stderr |
| self._expect_no_tree() |
| self._expect_no_result() |
| expected = 'No command to run.' |
| self.assertEqual('', out) |
| self.assertIn(expected, err) |
| |
| # TODO(csharp): Disabled until crbug.com/150823 is fixed. |
| def do_not_test_touch_only(self): |
| out = self._execute( |
| 'trace', 'touch_only.isolate', ['-V', 'FLAG', 'trace'], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| empty = os.path.join('files1', 'test_file1.txt') |
| self._expect_results( |
| ['touch_only.py', 'trace'], None, {u'FLAG': u'trace'}, empty) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| 'touch_only.py', |
| ], |
| isolate.KEY_TOUCHED: [ |
| # Note that .isolate format mandates / and not os.path.sep. |
| 'files1/test_file1.txt', |
| ], |
| } |
| if sys.platform != 'linux2': |
| # TODO(maruel): Implement touch-only tracing on non-linux. |
| del expected[isolate.KEY_TOUCHED] |
| |
| out = self._execute('read', 'touch_only.isolate', [], True) |
| self.assertEqual(self._wrap_in_condition(expected), out) |
| self._check_merge('touch_only.isolate') |
| |
| def test_touch_root(self): |
| out = self._execute('trace', 'touch_root.isolate', [], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| self._expect_results(['touch_root.py'], None, None, None) |
| expected = self._wrap_in_condition( |
| { |
| isolate.KEY_TRACKED: [ |
| '../../isolate.py', |
| 'touch_root.py', |
| ], |
| }) |
| out = self._execute('read', 'touch_root.isolate', [], True) |
| self.assertEqual(expected, out) |
| self._check_merge('touch_root.isolate') |
| |
| def test_with_flag(self): |
| out = self._execute( |
| 'trace', 'with_flag.isolate', ['-V', 'FLAG', 'trace'], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| self._expect_results( |
| ['with_flag.py', 'trace'], None, {u'FLAG': u'trace'}, None) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| 'with_flag.py', |
| ], |
| isolate.KEY_UNTRACKED: [ |
| # Note that .isolate format mandates / and not os.path.sep. |
| 'files1/', |
| ], |
| } |
| out = self._execute('read', 'with_flag.isolate', [], True) |
| self.assertEqual(self._wrap_in_condition(expected), out) |
| self._check_merge('with_flag.isolate') |
| |
| if sys.platform != 'win32': |
| def test_symlink_full(self): |
| out = self._execute( |
| 'trace', 'symlink_full.isolate', [], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| self._expect_results(['symlink_full.py'], None, None, None) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| 'symlink_full.py', |
| ], |
| isolate.KEY_UNTRACKED: [ |
| # Note that .isolate format mandates / and not os.path.sep. |
| 'files2/', |
| ], |
| } |
| out = self._execute('read', 'symlink_full.isolate', [], True) |
| self.assertEqual(self._wrap_in_condition(expected), out) |
| self._check_merge('symlink_full.isolate') |
| |
| def test_symlink_partial(self): |
| out = self._execute( |
| 'trace', 'symlink_partial.isolate', [], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| self._expect_results(['symlink_partial.py'], None, None, None) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| 'symlink_partial.py', |
| ], |
| isolate.KEY_UNTRACKED: [ |
| 'files2/test_file2.txt', |
| ], |
| } |
| out = self._execute('read', 'symlink_partial.isolate', [], True) |
| self.assertEqual(self._wrap_in_condition(expected), out) |
| self._check_merge('symlink_partial.isolate') |
| |
| def test_symlink_outside_build_root(self): |
| out = self._execute( |
| 'trace', 'symlink_outside_build_root.isolate', [], True) |
| self.assertEqual('', out) |
| self._expect_no_tree() |
| self._expect_results(['symlink_outside_build_root.py'], None, None, None) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| 'symlink_outside_build_root.py', |
| ], |
| isolate.KEY_UNTRACKED: [ |
| 'link_outside_build_root/', |
| ], |
| } |
| out = self._execute( |
| 'read', 'symlink_outside_build_root.isolate', [], True) |
| self.assertEqual(self._wrap_in_condition(expected), out) |
| self._check_merge('symlink_outside_build_root.isolate') |
| |
| |
| class IsolateNoOutdir(IsolateBase): |
| # Test without the --outdir flag. |
| # So all the files are first copied in the tempdir and the test is run from |
| # there. |
| def setUp(self): |
| super(IsolateNoOutdir, self).setUp() |
| self.root = os.path.join(self.tempdir, 'root') |
| os.makedirs(os.path.join(self.root, 'tests', 'isolate')) |
| for i in ('touch_root.isolate', 'touch_root.py'): |
| shutil.copy( |
| os.path.join(ROOT_DIR, 'tests', 'isolate', i), |
| os.path.join(self.root, 'tests', 'isolate', i)) |
| shutil.copy( |
| os.path.join(ROOT_DIR, 'isolate.py'), |
| os.path.join(self.root, 'isolate.py')) |
| |
| def _execute(self, mode, args, need_output): |
| """Executes isolate.py.""" |
| chromeos_value = int(isolate.get_flavor() == 'linux') |
| cmd = [ |
| sys.executable, os.path.join(ROOT_DIR, 'isolate.py'), |
| mode, |
| '--isolated', self.isolated, |
| '-V', 'chromeos', str(chromeos_value), |
| ] |
| cmd.extend(args) |
| |
| env = os.environ.copy() |
| if 'ISOLATE_DEBUG' in env: |
| del env['ISOLATE_DEBUG'] |
| |
| if need_output or not VERBOSE: |
| stdout = subprocess.PIPE |
| stderr = subprocess.STDOUT |
| else: |
| cmd.extend(['-v'] * 3) |
| stdout = None |
| stderr = None |
| |
| logging.debug(cmd) |
| cwd = self.tempdir |
| p = subprocess.Popen( |
| cmd, |
| stdout=stdout, |
| stderr=stderr, |
| cwd=cwd, |
| env=env, |
| universal_newlines=True) |
| out, err = p.communicate() |
| if p.returncode: |
| raise CalledProcessError(p.returncode, cmd, out, err, cwd) |
| return out |
| |
| def mode(self): |
| """Returns the execution mode corresponding to this test case.""" |
| test_id = self.id().split('.') |
| self.assertEqual(3, len(test_id)) |
| self.assertEqual('__main__', test_id[0]) |
| return re.match('^test_([a-z]+)$', test_id[2]).group(1) |
| |
| def filename(self): |
| """Returns the filename corresponding to this test case.""" |
| filename = os.path.join(self.root, 'tests', 'isolate', 'touch_root.isolate') |
| self.assertTrue(os.path.isfile(filename), filename) |
| return filename |
| |
| def test_check(self): |
| self._execute('check', ['--isolate', self.filename()], False) |
| files = sorted([ |
| 'isolate_smoke_test.isolated', |
| 'isolate_smoke_test.isolated.state', |
| os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), |
| os.path.join('root', 'tests', 'isolate', 'touch_root.py'), |
| os.path.join('root', 'isolate.py'), |
| ]) |
| self.assertEqual(files, list_files_tree(self.tempdir)) |
| |
| def test_archive(self): |
| self._execute('archive', ['--isolate', self.filename()], False) |
| files = sorted([ |
| os.path.join( |
| 'hashtable', |
| isolateserver.hash_file(os.path.join(ROOT_DIR, 'isolate.py'), ALGO)), |
| os.path.join( |
| 'hashtable', |
| isolateserver.hash_file( |
| os.path.join(ROOT_DIR, 'tests', 'isolate', 'touch_root.py'), |
| ALGO)), |
| os.path.join( |
| 'hashtable', |
| isolateserver.hash_file(os.path.join(self.isolated), ALGO)), |
| 'isolate_smoke_test.isolated', |
| 'isolate_smoke_test.isolated.state', |
| os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), |
| os.path.join('root', 'tests', 'isolate', 'touch_root.py'), |
| os.path.join('root', 'isolate.py'), |
| ]) |
| self.assertEqual(files, list_files_tree(self.tempdir)) |
| |
| def test_remap(self): |
| self._execute('remap', ['--isolate', self.filename()], False) |
| files = sorted([ |
| 'isolate_smoke_test.isolated', |
| 'isolate_smoke_test.isolated.state', |
| os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), |
| os.path.join('root', 'tests', 'isolate', 'touch_root.py'), |
| os.path.join('root', 'isolate.py'), |
| ]) |
| self.assertEqual(files, list_files_tree(self.tempdir)) |
| |
| def test_run(self): |
| self._execute('run', ['--isolate', self.filename()], False) |
| files = sorted([ |
| 'isolate_smoke_test.isolated', |
| 'isolate_smoke_test.isolated.state', |
| os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), |
| os.path.join('root', 'tests', 'isolate', 'touch_root.py'), |
| os.path.join('root', 'isolate.py'), |
| ]) |
| self.assertEqual(files, list_files_tree(self.tempdir)) |
| |
| def test_trace_read_merge(self): |
| self._execute('trace', ['--isolate', self.filename()], False) |
| # Read the trace before cleaning up. No need to specify self.filename() |
| # because add the needed information is in the .state file. |
| output = self._execute('read', [], True) |
| expected = { |
| isolate.KEY_TRACKED: [ |
| '../../isolate.py', |
| 'touch_root.py', |
| ], |
| } |
| self.assertEqual(self._wrap_in_condition(expected), output) |
| |
| output = self._execute('merge', [], True) |
| expected = 'Updating %s\n' % os.path.join( |
| 'root', 'tests', 'isolate', 'touch_root.isolate') |
| self.assertEqual(expected, output) |
| # In theory the file is going to be updated but in practice its content |
| # won't change. |
| |
| # Clean the directory from the logs, which are OS-specific. |
| isolate.trace_inputs.get_api().clean_trace( |
| os.path.join(self.tempdir, 'isolate_smoke_test.isolated.log')) |
| files = sorted([ |
| 'isolate_smoke_test.isolated', |
| 'isolate_smoke_test.isolated.state', |
| os.path.join('root', 'tests', 'isolate', 'touch_root.isolate'), |
| os.path.join('root', 'tests', 'isolate', 'touch_root.py'), |
| os.path.join('root', 'isolate.py'), |
| ]) |
| self.assertEqual(files, list_files_tree(self.tempdir)) |
| |
| |
| class IsolateOther(IsolateBase): |
| def test_run_mixed(self): |
| # Test when a user mapped from a directory and then replay from another |
| # directory. This is a very rare corner case. |
| indir = os.path.join(self.tempdir, 'input') |
| os.mkdir(indir) |
| for i in ('simple.py', 'simple.isolate'): |
| shutil.copy( |
| os.path.join(ROOT_DIR, 'tests', 'isolate', i), |
| os.path.join(indir, i)) |
| proc = subprocess.Popen( |
| [ |
| sys.executable, 'isolate.py', |
| 'check', |
| '-i', os.path.join(indir, 'simple.isolate'), |
| '-s', os.path.join(indir, 'simple.isolated'), |
| ], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| cwd=ROOT_DIR) |
| stdout = proc.communicate()[0] |
| self.assertEqual('', stdout) |
| self.assertEqual(0, proc.returncode) |
| expected = [ |
| 'simple.isolate', 'simple.isolated', 'simple.isolated.state', 'simple.py', |
| ] |
| self.assertEqual(expected, sorted(os.listdir(indir))) |
| |
| # Remove the original directory. |
| indir2 = indir + '2' |
| os.rename(indir, indir2) |
| |
| # simple.isolated.state is required; it contains the variables. |
| # This should still work. |
| proc = subprocess.Popen( |
| [ |
| sys.executable, 'isolate.py', 'run', |
| '-s', os.path.join(indir2, 'simple.isolated'), |
| '--skip-refresh', |
| ], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| cwd=ROOT_DIR, |
| universal_newlines=True) |
| stdout = proc.communicate()[0] |
| self.assertEqual('Simply works.\n', stdout) |
| self.assertEqual(0, proc.returncode) |
| |
| |
| if __name__ == '__main__': |
| VERBOSE = '-v' in sys.argv |
| logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR) |
| if VERBOSE: |
| unittest.TestCase.maxDiff = None |
| unittest.main() |