Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(104)

Unified Diff: tests/isolate_smoke_test.py

Issue 11048019: Add everything from src/tools/isolate r159537. (Closed) Base URL: https://git.chromium.org/chromium/tools/swarm_client.git@master
Patch Set: Ensure --similarity is sticky Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tests/isolate/with_flag.py ('k') | tests/isolate_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tests/isolate_smoke_test.py
diff --git a/tests/isolate_smoke_test.py b/tests/isolate_smoke_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..04c3df8325a04ce50faf634e1ea3a574aa618ec3
--- /dev/null
+++ b/tests/isolate_smoke_test.py
@@ -0,0 +1,1001 @@
+#!/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
+
+
+VERBOSE = False
+SHA_1_NULL = hashlib.sha1().hexdigest()
+
+
+# Keep the list hard coded.
+EXPECTED_MODES = (
+ 'check',
+ 'hashtable',
+ 'help',
+ 'noop',
+ 'merge',
+ 'read',
+ 'remap',
+ 'run',
+ 'trace',
+)
+
+# These are per test case, not per mode.
+RELATIVE_CWD = {
+ 'fail': '.',
+ 'missing_trailing_slash': '.',
+ 'no_run': '.',
+ 'non_existent': '.',
+ 'symlink_full': '.',
+ 'symlink_partial': '.',
+ 'touch_only': '.',
+ 'touch_root': os.path.join('tests', 'isolate'),
+ 'with_flag': '.',
+}
+
+DEPENDENCIES = {
+ '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': [],
+ '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',
+ ],
+ '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)
+
+
+def calc_sha1(filepath):
+ """Calculates the SHA-1 hash for a file."""
+ return hashlib.sha1(open(filepath, 'rb').read()).hexdigest()
+
+
+class IsolateBase(unittest.TestCase):
+ # To be defined by the subclass, it defines the amount of meta data saved by
+ # isolate.py for each file. Should be one of (NO_INFO, STATS_ONLY, WITH_HASH).
+ LEVEL = None
+
+ def setUp(self):
+ # The tests assume the current directory is the file's directory.
+ os.chdir(ROOT_DIR)
+ self.tempdir = tempfile.mkdtemp()
+ self.result = os.path.join(self.tempdir, 'isolate_smoke_test.results')
+ 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.
+ """
+ return cls._isolate_dict_to_string(
+ {
+ 'conditions': [
+ ['OS=="%s"' % isolate.get_flavor(), {
+ '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.assertEquals(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):
+ """Returns a dict of files like calling isolate.process_input() on each
+ file.
+ """
+ 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)
+ if self.LEVEL >= isolate.STATS_ONLY:
+ filestats = os.lstat(filepath)
+ is_link = stat.S_ISLNK(filestats.st_mode)
+ if not is_link:
+ v[u'size'] = filestats.st_size
+ if isolate.get_flavor() != 'win':
+ v[u'mode'] = self._fix_file_mode(relfile, read_only)
+ else:
+ v[u'mode'] = 488
+ # Used the skip recalculating the hash. Use the most recent update
+ # time.
+ v[u'timestamp'] = int(round(filestats.st_mtime))
+ if is_link:
+ v['link'] = os.readlink(filepath)
+
+ if self.LEVEL >= isolate.WITH_HASH:
+ if not is_link:
+ # 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'sha-1'] = unicode(calc_sha1(filepath))
+
+ if empty_file:
+ item = files[empty_file]
+ item['sha-1'] = unicode(SHA_1_NULL)
+ if sys.platform != 'win32':
+ item['mode'] = 288
+ item['size'] = 0
+ item['touched_only'] = True
+ item.pop('timestamp', None)
+ return files
+
+ def _expected_result(self, args, read_only, empty_file):
+ """Verifies self.result contains the expected data."""
+ expected = {
+ u'files': self._gen_files(read_only, empty_file),
+ u'os': isolate.get_flavor(),
+ u'relative_cwd': unicode(RELATIVE_CWD[self.case()]),
+ }
+ 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]
+ else:
+ expected[u'command'] = []
+ self.assertEquals(expected, json.load(open(self.result, 'r')))
+
+ def _expected_saved_state(self, extra_vars):
+ flavor = isolate.get_flavor()
+ expected = {
+ u'isolate_file': unicode(self.filename()),
+ u'variables': {
+ u'EXECUTABLE_SUFFIX': '.exe' if flavor == 'win' else '',
+ u'OS': unicode(flavor),
+ },
+ }
+ expected['variables'].update(extra_vars or {})
+ self.assertEquals(expected, json.load(open(self.saved_state(), 'r')))
+
+ def _expect_results(self, args, read_only, extra_vars, empty_file):
+ self._expected_result(args, read_only, empty_file)
+ self._expected_saved_state(extra_vars)
+ # Also verifies run_swarm_step.py will be able to read it.
+ isolate.run_swarm_step.load_manifest(open(self.result, 'r').read())
+
+ def _expect_no_result(self):
+ self.assertFalse(os.path.exists(self.result))
+
+ def _execute(self, mode, case, args, need_output):
+ """Executes isolate.py."""
+ self.assertEquals(
+ case,
+ self.case() + '.isolate',
+ 'Rename the test case to test_%s()' % case)
+ cmd = [
+ sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
+ mode,
+ '--result', self.result,
+ '--outdir', self.outdir,
+ '--isolate', self.filename(),
+ ]
+ 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)
+ cwd = ROOT_DIR
+ 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.result_to_state(self.result)
+
+
+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.assertEquals(0, p.returncode)
+ out = out[out.index('') + 1:]
+ out = out[:out.index('')]
+ modes = [re.match(r'^ (\w+) .+', l) for l in out]
+ modes = tuple(m.group(1) for m in modes if m)
+ # noop doesn't do anything so no point in testing it.
+ self.assertEquals(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')
+ )
+ self.assertEquals(sorted(RELATIVE_CWD), files)
+ self.assertEquals(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_')]
+
+ # TODO(csharp): touched_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)
+ modes_to_check.remove('help')
+ modes_to_check.remove('merge')
+ modes_to_check.remove('noop')
+ modes_to_check.remove('read')
+ 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_'))
+ missing = expected_cases - actual_cases
+ self.assertFalse(missing, '%s.%s' % (fixture_name, missing))
+
+
+class Isolate_check(IsolateModeBase):
+ LEVEL = isolate.NO_INFO
+
+ 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):
+ try:
+ self._execute('check', 'missing_trailing_slash.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ def test_non_existent(self):
+ try:
+ self._execute('check', 'non_existent.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ 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_result(['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)
+
+
+class Isolate_hashtable(IsolateModeBase):
+ LEVEL = isolate.WITH_HASH
+
+ def _gen_expected_tree(self, empty_file):
+ expected = [
+ v['sha-1'] for v in self._gen_files(False, empty_file).itervalues()
+ ]
+ expected.append(calc_sha1(self.result))
+ return expected
+
+ def _expected_hash_tree(self, empty_file):
+ """Verifies the files written in the temporary directory."""
+ self.assertEquals(
+ sorted(self._gen_expected_tree(empty_file)), self._result_tree())
+
+ def test_fail(self):
+ self._execute('hashtable', 'fail.isolate', [], False)
+ self._expected_hash_tree(None)
+ self._expect_results(['fail.py'], None, None, None)
+
+ def test_missing_trailing_slash(self):
+ try:
+ self._execute('hashtable', 'missing_trailing_slash.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ def test_non_existent(self):
+ try:
+ self._execute('hashtable', 'non_existent.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ def test_no_run(self):
+ self._execute('hashtable', 'no_run.isolate', [], False)
+ self._expected_hash_tree(None)
+ self._expect_results([], None, None, None)
+
+ # TODO(csharp): Disabled until crbug.com/150823 is fixed.
+ def do_not_test_touch_only(self):
+ self._execute(
+ 'hashtable', 'touch_only.isolate', ['-V', 'FLAG', 'gyp'], False)
+ empty = os.path.join('files1', 'test_file1.txt')
+ self._expected_hash_tree(empty)
+ self._expected_result(['touch_only.py', 'gyp'], None, empty)
+
+ def test_touch_root(self):
+ self._execute('hashtable', '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(
+ 'hashtable', '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('hashtable', 'symlink_full.isolate', [], False)
+ # Construct our own tree.
+ expected = [
+ str(v['sha-1'])
+ for v in self._gen_files(False, None).itervalues() if 'sha-1' in v
+ ]
+ expected.append(calc_sha1(self.result))
+ self.assertEquals(sorted(expected), self._result_tree())
+ self._expect_results(['symlink_full.py'], None, None, None)
+
+ def test_symlink_partial(self):
+ self._execute('hashtable', 'symlink_partial.isolate', [], False)
+ # Construct our own tree.
+ expected = [
+ str(v['sha-1'])
+ for v in self._gen_files(False, None).itervalues() if 'sha-1' in v
+ ]
+ expected.append(calc_sha1(self.result))
+ self.assertEquals(sorted(expected), self._result_tree())
+ self._expect_results(['symlink_partial.py'], None, None, None)
+
+
+
+class Isolate_remap(IsolateModeBase):
+ LEVEL = isolate.STATS_ONLY
+
+ 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):
+ try:
+ self._execute('remap', 'missing_trailing_slash.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ def test_non_existent(self):
+ try:
+ self._execute('remap', 'non_existent.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ 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)
+
+
+class Isolate_run(IsolateModeBase):
+ LEVEL = isolate.STATS_ONLY
+
+ def _expect_empty_tree(self):
+ self.assertEquals([], 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):
+ try:
+ self._execute('run', 'missing_trailing_slash.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ def test_non_existent(self):
+ try:
+ self._execute('run', 'non_existent.isolate', [], False)
+ self.fail()
+ except subprocess.CalledProcessError:
+ pass
+ self._expect_no_tree()
+ self._expect_no_result()
+
+ 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)
+
+
+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.
+ LEVEL = isolate.STATS_ONLY
+
+ def _check_merge(self, filename):
+ filepath = isolate.trace_inputs.get_native_path_case(
+ os.path.join(ROOT_DIR, 'tests', 'isolate', filename))
+ expected = 'Updating %s\n' % filepath
+ with open(filepath, 'rb') as f:
+ old_content = f.read()
+ out = self._execute('merge', filename, [], True) or ''
+ self.assertEquals(expected, out)
+ with open(filepath, 'rb') as f:
+ new_content = f.read()
+ self.assertEquals(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.assertEquals('', 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.assertEquals(expected.splitlines(), out.splitlines())
+ self._check_merge('fail.isolate')
+
+ def test_missing_trailing_slash(self):
+ try:
+ self._execute('trace', 'missing_trailing_slash.isolate', [], True)
+ self.fail()
+ except subprocess.CalledProcessError, e:
+ self.assertEquals('', e.output)
+ out = e.stderr
+ self._expect_no_tree()
+ self._expect_no_result()
+ expected = (
+ '\n'
+ 'Error: Input directory %s must have a trailing slash\n' %
+ os.path.join(ROOT_DIR, 'tests', 'isolate', 'files1')
+ )
+ self.assertEquals(expected, out)
+
+ def test_non_existent(self):
+ try:
+ self._execute('trace', 'non_existent.isolate', [], True)
+ self.fail()
+ except subprocess.CalledProcessError, e:
+ self.assertEquals('', e.output)
+ out = e.stderr
+ self._expect_no_tree()
+ self._expect_no_result()
+ expected = (
+ '\n'
+ 'Error: Input file %s doesn\'t exist\n' %
+ os.path.join(ROOT_DIR, 'tests', 'isolate', 'A_file_that_do_not_exist')
+ )
+ self.assertEquals(expected, out)
+
+ 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 = '\nError: No command to run\n'
+ self.assertEquals('', out)
+ self.assertEquals(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.assertEquals('', 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.assertEquals(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.assertEquals('', 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.assertEquals(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.assertEquals('', 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.assertEquals(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.assertEquals('', 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.assertEquals(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.assertEquals('', 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.assertEquals(self._wrap_in_condition(expected), out)
+ self._check_merge('symlink_partial.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."""
+ cmd = [
+ sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
+ mode,
+ '--result', self.result,
+ ]
+ 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.assertEquals(3, len(test_id))
+ self.assertEquals('__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.results',
+ 'isolate_smoke_test.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.assertEquals(files, list_files_tree(self.tempdir))
+
+ def test_hashtable(self):
+ self._execute('hashtable', ['--isolate', self.filename()], False)
+ files = sorted([
+ os.path.join(
+ 'hashtable', calc_sha1(os.path.join(ROOT_DIR, 'isolate.py'))),
+ os.path.join(
+ 'hashtable',
+ calc_sha1(
+ os.path.join(ROOT_DIR, 'tests', 'isolate', 'touch_root.py'))),
+ os.path.join('hashtable', calc_sha1(os.path.join(self.result))),
+ 'isolate_smoke_test.results',
+ 'isolate_smoke_test.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.assertEquals(files, list_files_tree(self.tempdir))
+
+ def test_remap(self):
+ self._execute('remap', ['--isolate', self.filename()], False)
+ files = sorted([
+ 'isolate_smoke_test.results',
+ 'isolate_smoke_test.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.assertEquals(files, list_files_tree(self.tempdir))
+
+ def test_run(self):
+ self._execute('run', ['--isolate', self.filename()], False)
+ files = sorted([
+ 'isolate_smoke_test.results',
+ 'isolate_smoke_test.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.assertEquals(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.assertEquals(self._wrap_in_condition(expected), output)
+
+ output = self._execute('merge', [], True)
+ expected = 'Updating %s\n' % isolate.trace_inputs.get_native_path_case(
+ os.path.join(self.root, 'tests', 'isolate', 'touch_root.isolate'))
+ self.assertEquals(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.results.log'))
+ files = sorted([
+ 'isolate_smoke_test.results',
+ 'isolate_smoke_test.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.assertEquals(files, list_files_tree(self.tempdir))
+
+
+if __name__ == '__main__':
+ VERBOSE = '-v' in sys.argv
+ logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR)
+ unittest.main()
« no previous file with comments | « tests/isolate/with_flag.py ('k') | tests/isolate_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698