diff options
author | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-04 23:58:17 +0000 |
---|---|---|
committer | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-04 23:58:17 +0000 |
commit | 568427d2d4c6f5600ff8c6d2af5c81c2e785297e (patch) | |
tree | fa5a8d90ad7fd38dfc9d7e74f6a289773c00cf8c | |
parent | 49c29f2cbfc0c06b2cd34331a3d1fd2d8013a189 (diff) | |
download | chromium_src-568427d2d4c6f5600ff8c6d2af5c81c2e785297e.zip chromium_src-568427d2d4c6f5600ff8c6d2af5c81c2e785297e.tar.gz chromium_src-568427d2d4c6f5600ff8c6d2af5c81c2e785297e.tar.bz2 |
Make trace_inputs.py a real stand alone application with inner commands.
Remove any knowledge about '.isolate' from trace_inputs.py and move the code
into isolate_common.py.
NOTRY=true
R=cmp@chromium.org
BUG=
TEST=
Review URL: https://chromiumcodereview.appspot.com/10459040
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@140435 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-x | tools/isolate/isolate.py | 45 | ||||
-rw-r--r-- | tools/isolate/isolate_common.py | 152 | ||||
-rwxr-xr-x | tools/isolate/isolate_common_test.py | 97 | ||||
-rwxr-xr-x | tools/isolate/isolate_smoke_test.py | 94 | ||||
-rwxr-xr-x | tools/isolate/merge_isolate.py | 6 | ||||
-rwxr-xr-x | tools/isolate/read_trace.py | 5 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs.py | 425 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs_smoke_test.py | 211 | ||||
-rwxr-xr-x | tools/isolate/trace_inputs_test.py | 81 | ||||
-rwxr-xr-x | tools/isolate/trace_test_cases.py | 3 |
10 files changed, 642 insertions, 477 deletions
diff --git a/tools/isolate/isolate.py b/tools/isolate/isolate.py index c5f51ae..8220873 100755 --- a/tools/isolate/isolate.py +++ b/tools/isolate/isolate.py @@ -31,6 +31,7 @@ import subprocess import sys import tempfile +import isolate_common import merge_isolate import trace_inputs import run_test_from_archive @@ -123,7 +124,7 @@ def load_isolate(content, error): # Load the .isolate file, process its conditions, retrieve the command and # dependencies. configs = merge_isolate.load_gyp(merge_isolate.eval_content(content)) - flavor = trace_inputs.get_flavor() + flavor = isolate_common.get_flavor() config = configs.per_os.get(flavor) or configs.per_os.get(None) if not config: error('Failed to load configuration for \'%s\'' % flavor) @@ -152,7 +153,7 @@ def process_input(filepath, prevdict, level, read_only): out = {} if level >= STATS_ONLY: filestats = os.stat(filepath) - if trace_inputs.get_flavor() != 'win': + if isolate_common.get_flavor() != 'win': filemode = stat.S_IMODE(filestats.st_mode) # Remove write access for group and all access to 'others'. filemode &= ~(stat.S_IWGRP | stat.S_IRWXO) @@ -588,13 +589,37 @@ def MODEtrace(_outdir, state): if not state.result.command: print 'No command to run' return 1 - return trace_inputs.trace_inputs( - state.result_file + '.log', - state.result.command, - state.root_dir, - state.result.relative_cwd, - product_dir, - False) + api = trace_inputs.get_api() + logfile = state.result_file + '.log' + try: + result = 0 + if not os.path.isfile(logfile): + result, _ = api.gen_trace( + state.result.command, + os.path.join(state.root_dir, state.result.relative_cwd), + logfile, + True) + + _, simplified = trace_inputs.load_trace(logfile, state.root_dir, api) + variables = isolate_common.generate_dict( + (f.path for f in simplified), + state.result.relative_cwd, + product_dir) + # Outputs in a way that is easy to merge with merge_isolate.py. + value = { + 'conditions': [ + ['OS=="%s"' % isolate_common.get_flavor(), { + 'variables': variables, + }], + ], + } + isolate_common.pretty_print(value, sys.stdout) + return result + except trace_inputs.TracingFailure, e: + print >> sys.stderr, ( + '\nTracing failed for: %s' % ' '.join(state.result.command)) + print >> sys.stderr, str(e) + return 1 # Must be declared after all the functions. @@ -669,7 +694,7 @@ def isolate(result_file, isolate_file, mode, variables, out_dir, error): def main(): """Handles CLI and normalizes the input arguments to pass them to isolate(). """ - default_variables = [('OS', trace_inputs.get_flavor())] + default_variables = [('OS', isolate_common.get_flavor())] if sys.platform in ('win32', 'cygwin'): default_variables.append(('EXECUTABLE_SUFFIX', '.exe')) else: diff --git a/tools/isolate/isolate_common.py b/tools/isolate/isolate_common.py new file mode 100644 index 0000000..3e51e25 --- /dev/null +++ b/tools/isolate/isolate_common.py @@ -0,0 +1,152 @@ +# coding=utf-8 +# 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. + +"""Common code to manage .isolate format. +""" + +import logging +import os +import posixpath +import sys + + +KEY_TRACKED = 'isolate_dependency_tracked' +KEY_UNTRACKED = 'isolate_dependency_untracked' + + +def posix_relpath(path, root): + """posix.relpath() that keeps trailing slash.""" + out = posixpath.relpath(path, root) + if path.endswith('/'): + out += '/' + return out + + +def cleanup_path(x): + """Cleans up a relative path. Converts any os.path.sep to '/' on Windows.""" + if x: + x = x.rstrip(os.path.sep).replace(os.path.sep, '/') + if x == '.': + x = '' + if x: + x += '/' + return x + + +def get_flavor(): + """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py.""" + flavors = { + 'cygwin': 'win', + 'win32': 'win', + 'darwin': 'mac', + 'sunos5': 'solaris', + 'freebsd7': 'freebsd', + 'freebsd8': 'freebsd', + } + return flavors.get(sys.platform, 'linux') + + +def generate_dict(files, cwd_dir, product_dir): + """Converts the list of files into a .isolate dictionary. + + Arguments: + - files: list of files to generate a dictionary out of. + - cwd_dir: directory to base all the files from, relative to root_dir. + - product_dir: directory to replace with <(PRODUCT_DIR), relative to root_dir. + """ + cwd_dir = cleanup_path(cwd_dir) + product_dir = cleanup_path(product_dir) + + def fix(f): + """Bases the file on the most restrictive variable.""" + logging.debug('fix(%s)' % f) + # Important, GYP stores the files with / and not \. + f = f.replace(os.path.sep, '/') + if product_dir and f.startswith(product_dir): + return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] + else: + # cwd_dir is usually the directory containing the gyp file. It may be + # empty if the whole directory containing the gyp file is needed. + return posix_relpath(f, cwd_dir) or './' + + corrected = [fix(f) for f in files] + tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] + untracked = [f for f in corrected if f.endswith('/') or ' ' in f] + variables = {} + if tracked: + variables[KEY_TRACKED] = tracked + if untracked: + variables[KEY_UNTRACKED] = untracked + return variables + + +def pretty_print(variables, stdout): + """Outputs a gyp compatible list from the decoded variables. + + Similar to pprint.print() but with NIH syndrome. + """ + # Order the dictionary keys by these keys in priority. + ORDER = ( + 'variables', 'condition', 'command', 'relative_cwd', 'read_only', + KEY_TRACKED, KEY_UNTRACKED) + + def sorting_key(x): + """Gives priority to 'most important' keys before the others.""" + if x in ORDER: + return str(ORDER.index(x)) + return x + + def loop_list(indent, items): + for item in items: + if isinstance(item, basestring): + stdout.write('%s\'%s\',\n' % (indent, item)) + elif isinstance(item, dict): + stdout.write('%s{\n' % indent) + loop_dict(indent + ' ', item) + stdout.write('%s},\n' % indent) + elif isinstance(item, list): + # A list inside a list will write the first item embedded. + stdout.write('%s[' % indent) + for index, i in enumerate(item): + if isinstance(i, basestring): + stdout.write( + '\'%s\', ' % i.replace('\\', '\\\\').replace('\'', '\\\'')) + elif isinstance(i, dict): + stdout.write('{\n') + loop_dict(indent + ' ', i) + if index != len(item) - 1: + x = ', ' + else: + x = '' + stdout.write('%s}%s' % (indent, x)) + else: + assert False + stdout.write('],\n') + else: + assert False + + def loop_dict(indent, items): + for key in sorted(items, key=sorting_key): + item = items[key] + stdout.write("%s'%s': " % (indent, key)) + if isinstance(item, dict): + stdout.write('{\n') + loop_dict(indent + ' ', item) + stdout.write(indent + '},\n') + elif isinstance(item, list): + stdout.write('[\n') + loop_list(indent + ' ', item) + stdout.write(indent + '],\n') + elif isinstance(item, basestring): + stdout.write( + '\'%s\',\n' % item.replace('\\', '\\\\').replace('\'', '\\\'')) + elif item in (True, False, None): + stdout.write('%s\n' % item) + else: + assert False, item + + stdout.write('{\n') + loop_dict(' ', variables) + stdout.write('}\n') diff --git a/tools/isolate/isolate_common_test.py b/tools/isolate/isolate_common_test.py new file mode 100755 index 0000000..eea87f0 --- /dev/null +++ b/tools/isolate/isolate_common_test.py @@ -0,0 +1,97 @@ +#!/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 logging +import unittest +import sys + +import isolate_common + + +class TraceInputs(unittest.TestCase): + def _test(self, value, expected): + actual = cStringIO.StringIO() + isolate_common.pretty_print(value, actual) + self.assertEquals(expected, actual.getvalue()) + + def test_pretty_print_empty(self): + self._test({}, '{\n}\n') + + def test_pretty_print_mid_size(self): + value = { + 'variables': { + 'bar': [ + 'file1', + 'file2', + ], + }, + 'conditions': [ + ['OS=\"foo\"', { + 'variables': { + isolate_common.KEY_UNTRACKED: [ + 'dir1', + 'dir2', + ], + isolate_common.KEY_TRACKED: [ + 'file4', + 'file3', + ], + 'command': ['python', '-c', 'print "H\\i\'"'], + 'read_only': True, + 'relative_cwd': 'isol\'at\\e', + }, + }], + ['OS=\"bar\"', { + 'variables': {}, + }, { + 'variables': {}, + }], + ], + } + expected = ( + "{\n" + " 'variables': {\n" + " 'bar': [\n" + " 'file1',\n" + " 'file2',\n" + " ],\n" + " },\n" + " 'conditions': [\n" + " ['OS=\"foo\"', {\n" + " 'variables': {\n" + " 'command': [\n" + " 'python',\n" + " '-c',\n" + " 'print \"H\\i\'\"',\n" + " ],\n" + " 'relative_cwd': 'isol\\'at\\\\e',\n" + " 'read_only': True\n" + " 'isolate_dependency_tracked': [\n" + " 'file4',\n" + " 'file3',\n" + " ],\n" + " 'isolate_dependency_untracked': [\n" + " 'dir1',\n" + " 'dir2',\n" + " ],\n" + " },\n" + " }],\n" + " ['OS=\"bar\"', {\n" + " 'variables': {\n" + " },\n" + " }, {\n" + " 'variables': {\n" + " },\n" + " }],\n" + " ],\n" + "}\n") + self._test(value, expected) + + +if __name__ == '__main__': + VERBOSE = '-v' in sys.argv + logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR) + unittest.main() diff --git a/tools/isolate/isolate_smoke_test.py b/tools/isolate/isolate_smoke_test.py index b38207c..3ca391b 100755 --- a/tools/isolate/isolate_smoke_test.py +++ b/tools/isolate/isolate_smoke_test.py @@ -54,15 +54,20 @@ DEPENDENCIES = { class CalledProcessError(subprocess.CalledProcessError): """Makes 2.6 version act like 2.7""" - def __init__(self, returncode, cmd, output, cwd): + 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') % (self.cwd, self.output) + 'cwd=%s\n%s\n%s\n%s') % ( + self.cwd, + self.output, + self.stderr, + ' '.join(self.cmd)) def list_files_tree(directory): @@ -120,7 +125,7 @@ class IsolateBase(unittest.TestCase): if self.LEVEL >= isolate.STATS_ONLY: for k, v in files.iteritems(): - if isolate.trace_inputs.get_flavor() != 'win': + if isolate.isolate_common.get_flavor() != 'win': v[u'mode'] = self._fix_file_mode(k, read_only) filestats = os.stat(os.path.join(root_dir, k)) v[u'size'] = filestats.st_size @@ -148,7 +153,7 @@ class IsolateBase(unittest.TestCase): self.assertEquals(expected, json.load(open(self.result, 'r'))) def _expected_saved_state(self, extra_vars): - flavor = isolate.trace_inputs.get_flavor() + flavor = isolate.isolate_common.get_flavor() expected = { u'isolate_file': unicode(self.filename()), u'variables': { @@ -189,7 +194,7 @@ class IsolateBase(unittest.TestCase): if need_output or not VERBOSE: stdout = subprocess.PIPE - stderr = subprocess.STDOUT + stderr = subprocess.PIPE else: cmd.extend(['-v'] * 3) stdout = None @@ -204,9 +209,13 @@ class IsolateBase(unittest.TestCase): cwd=cwd, env=env, universal_newlines=True) - out = p.communicate()[0] + out, err = p.communicate() if p.returncode: - raise CalledProcessError(p.returncode, cmd, out, cwd) + 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.assertEquals('', err) return out def mode(self): @@ -463,7 +472,7 @@ class Isolate_trace(IsolateBase): @staticmethod def _to_string(values): buf = cStringIO.StringIO() - isolate.trace_inputs.pretty_print(values, buf) + isolate.isolate_common.pretty_print(values, buf) return buf.getvalue() def test_fail(self): @@ -474,22 +483,29 @@ class Isolate_trace(IsolateBase): out = e.output self._expect_no_tree() self._expect_results(['fail.py'], None, None) - expected = 'Failing' + # Even if it returns an error, isolate.py still prints the trace. + expected = self._to_string( + { + 'conditions': [ + ['OS=="%s"' % isolate.isolate_common.get_flavor(), { + 'variables': { + isolate.isolate_common.KEY_TRACKED: [ + 'fail.py', + ], + }, + }], + ], + }) lines = out.strip().splitlines() - self.assertTrue(lines.pop(0).startswith('WARNING')) - self.assertTrue(lines.pop(0).startswith('WARNING')) - if sys.platform == 'win32': - # Includes spew from tracerpt.exe. - self.assertEquals(expected, lines[0], (lines, expected)) - else: - self.assertEquals([expected], lines) + self.assertEquals(expected.splitlines(), lines) def test_missing_trailing_slash(self): try: self._execute('trace', 'missing_trailing_slash.isolate', [], True) self.fail() except subprocess.CalledProcessError, e: - out = e.output + self.assertEquals('', e.output) + out = e.stderr self._expect_no_tree() self._expect_no_result() expected = ( @@ -504,7 +520,8 @@ class Isolate_trace(IsolateBase): self._execute('trace', 'non_existent.isolate', [], True) self.fail() except subprocess.CalledProcessError, e: - out = e.output + self.assertEquals('', e.output) + out = e.stderr self._expect_no_tree() self._expect_no_result() expected = ( @@ -529,19 +546,20 @@ class Isolate_trace(IsolateBase): out = self._execute('trace', 'touch_root.isolate', [], True) self._expect_no_tree() self._expect_results(['touch_root.py'], None, None) - expected = { - 'conditions': [ - ['OS=="%s"' % isolate.trace_inputs.get_flavor(), { - 'variables': { - isolate.trace_inputs.KEY_TRACKED: [ - 'touch_root.py', - '../../isolate.py', - ], - }, - }], - ], - } - self.assertEquals(self._to_string(expected), out) + expected = self._to_string( + { + 'conditions': [ + ['OS=="%s"' % isolate.isolate_common.get_flavor(), { + 'variables': { + isolate.isolate_common.KEY_TRACKED: [ + 'touch_root.py', + '../../isolate.py', + ], + }, + }], + ], + }) + self.assertEquals(expected, out) def test_with_flag(self): out = self._execute( @@ -550,12 +568,12 @@ class Isolate_trace(IsolateBase): self._expect_results(['with_flag.py', 'trace'], None, {u'FLAG': u'trace'}) expected = { 'conditions': [ - ['OS=="%s"' % isolate.trace_inputs.get_flavor(), { + ['OS=="%s"' % isolate.isolate_common.get_flavor(), { 'variables': { - isolate.trace_inputs.KEY_TRACKED: [ + isolate.isolate_common.KEY_TRACKED: [ 'with_flag.py', ], - isolate.trace_inputs.KEY_UNTRACKED: [ + isolate.isolate_common.KEY_UNTRACKED: [ # Note that .isolate format mandates / and not os.path.sep. 'files1/', ], @@ -619,9 +637,9 @@ class IsolateNoOutdir(IsolateBase): cwd=cwd, env=env, universal_newlines=True) - out = p.communicate()[0] + out, err = p.communicate() if p.returncode: - raise CalledProcessError(p.returncode, cmd, out, cwd) + raise CalledProcessError(p.returncode, cmd, out, err, cwd) return out def mode(self): @@ -655,7 +673,7 @@ class IsolateNoOutdir(IsolateBase): def test_hashtable(self): self._execute('hashtable', 'touch_root.isolate', [], False) - files = [ + files = sorted([ os.path.join( 'hashtable', calc_sha1(os.path.join(ROOT_DIR, 'isolate.py'))), os.path.join( @@ -667,7 +685,7 @@ class IsolateNoOutdir(IsolateBase): os.path.join('root', 'data', 'isolate', 'touch_root.isolate'), os.path.join('root', 'data', 'isolate', 'touch_root.py'), os.path.join('root', 'isolate.py'), - ] + ]) self.assertEquals(files, list_files_tree(self.tempdir)) def test_remap(self): diff --git a/tools/isolate/merge_isolate.py b/tools/isolate/merge_isolate.py index df6f92b..187556b 100755 --- a/tools/isolate/merge_isolate.py +++ b/tools/isolate/merge_isolate.py @@ -17,9 +17,7 @@ import optparse import re import sys -import trace_inputs -# Create shortcuts. -from trace_inputs import KEY_TRACKED, KEY_UNTRACKED +from isolate_common import pretty_print, KEY_TRACKED, KEY_UNTRACKED def union(lhs, rhs): @@ -378,7 +376,7 @@ def main(args=None): level=level, format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s') - trace_inputs.pretty_print( + pretty_print( convert_map_to_gyp( *reduce_inputs( *invert_map( diff --git a/tools/isolate/read_trace.py b/tools/isolate/read_trace.py index 642ca28..152d2f8 100755 --- a/tools/isolate/read_trace.py +++ b/tools/isolate/read_trace.py @@ -10,6 +10,7 @@ import optparse import os import sys +import isolate_common import trace_inputs BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -21,8 +22,8 @@ def read_trace(logname, root_dir, cwd_dir, product_dir): root_dir = os.path.realpath(root_dir) api = trace_inputs.get_api() _, _, _, _, simplified, _ = trace_inputs.load_trace(logname, root_dir, api) - variables = trace_inputs.generate_dict(simplified, cwd_dir, product_dir) - trace_inputs.pretty_print(variables, sys.stdout) + variables = isolate_common.generate_dict(simplified, cwd_dir, product_dir) + isolate_common.pretty_print(variables, sys.stdout) def main(): diff --git a/tools/isolate/trace_inputs.py b/tools/isolate/trace_inputs.py index 0b40766..8e1c11f 100755 --- a/tools/isolate/trace_inputs.py +++ b/tools/isolate/trace_inputs.py @@ -20,12 +20,12 @@ from the log. import codecs import csv +import getpass import glob import json import logging import optparse import os -import posixpath import re import subprocess import sys @@ -44,9 +44,6 @@ elif sys.platform == 'darwin': BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR)) -KEY_TRACKED = 'isolate_dependency_tracked' -KEY_UNTRACKED = 'isolate_dependency_untracked' - class TracingFailure(Exception): """An exception occured during tracing.""" @@ -251,23 +248,6 @@ else: # OSes other than Windows and OSX. return path -def get_flavor(): - """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py.""" - flavors = { - 'cygwin': 'win', - 'win32': 'win', - 'darwin': 'mac', - 'sunos5': 'solaris', - 'freebsd7': 'freebsd', - 'freebsd8': 'freebsd', - } - return flavors.get(sys.platform, 'linux') - - -def isEnabledFor(level): - return logging.getLogger().isEnabledFor(level) - - def fix_python_path(cmd): """Returns the fixed command line to call the right python executable.""" out = cmd[:] @@ -278,25 +258,6 @@ def fix_python_path(cmd): return out -def posix_relpath(path, root): - """posix.relpath() that keeps trailing slash.""" - out = posixpath.relpath(path, root) - if path.endswith('/'): - out += '/' - return out - - -def cleanup_path(x): - """Cleans up a relative path. Converts any os.path.sep to '/' on Windows.""" - if x: - x = x.rstrip(os.path.sep).replace(os.path.sep, '/') - if x == '.': - x = '' - if x: - x += '/' - return x - - def process_quoted_arguments(text): """Extracts quoted arguments on a string and return the arguments as a list. @@ -637,6 +598,8 @@ class Results(object): """Returns a clone with all the files outside the directory |root| removed and converts all the path to be relative paths. """ + # Resolve any symlink + root = os.path.realpath(root) root = get_native_path_case(root).rstrip(os.path.sep) + os.path.sep logging.debug('strip_root(%s)' % root) return Results(self.process.strip_root(root)) @@ -2191,7 +2154,7 @@ class LogmanTrace(ApiBase): stderr=subprocess.STDOUT) @staticmethod - def _convert_log(logname, logformat, stdout, stderr): + def _convert_log(logname, logformat): """Converts the ETL trace to text representation. Normally, 'csv' is sufficient. If complex scripts are used (like eastern @@ -2229,8 +2192,12 @@ class LogmanTrace(ApiBase): raise ValueError('Unexpected log format \'%s\'' % logformat) logging.debug('Running: %s' % cmd_convert) # This can takes tens of minutes for large logs. + # Redirects all output to stderr. subprocess.check_call( - cmd_convert, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) + cmd_convert, + stdin=subprocess.PIPE, + stdout=sys.stderr, + stderr=sys.stderr) @classmethod def gen_trace(cls, cmd, cwd, logname, output): @@ -2273,7 +2240,7 @@ class LogmanTrace(ApiBase): cls._stop_log() # 4. Convert the traces to text representation. - cls._convert_log(logname, 'csv', stdout, stderr) + cls._convert_log(logname, 'csv') # 5. Save metadata. json.dump({ @@ -2338,87 +2305,20 @@ class LogmanTrace(ApiBase): return context.to_results() -def pretty_print(variables, stdout): - """Outputs a gyp compatible list from the decoded variables. - - Similar to pprint.print() but with NIH syndrome. - """ - # Order the dictionary keys by these keys in priority. - ORDER = ( - 'variables', 'condition', 'command', 'relative_cwd', 'read_only', - KEY_TRACKED, KEY_UNTRACKED) - - def sorting_key(x): - """Gives priority to 'most important' keys before the others.""" - if x in ORDER: - return str(ORDER.index(x)) - return x - - def loop_list(indent, items): - for item in items: - if isinstance(item, basestring): - stdout.write('%s\'%s\',\n' % (indent, item)) - elif isinstance(item, dict): - stdout.write('%s{\n' % indent) - loop_dict(indent + ' ', item) - stdout.write('%s},\n' % indent) - elif isinstance(item, list): - # A list inside a list will write the first item embedded. - stdout.write('%s[' % indent) - for index, i in enumerate(item): - if isinstance(i, basestring): - stdout.write( - '\'%s\', ' % i.replace('\\', '\\\\').replace('\'', '\\\'')) - elif isinstance(i, dict): - stdout.write('{\n') - loop_dict(indent + ' ', i) - if index != len(item) - 1: - x = ', ' - else: - x = '' - stdout.write('%s}%s' % (indent, x)) - else: - assert False - stdout.write('],\n') - else: - assert False - - def loop_dict(indent, items): - for key in sorted(items, key=sorting_key): - item = items[key] - stdout.write("%s'%s': " % (indent, key)) - if isinstance(item, dict): - stdout.write('{\n') - loop_dict(indent + ' ', item) - stdout.write(indent + '},\n') - elif isinstance(item, list): - stdout.write('[\n') - loop_list(indent + ' ', item) - stdout.write(indent + '],\n') - elif isinstance(item, basestring): - stdout.write( - '\'%s\',\n' % item.replace('\\', '\\\\').replace('\'', '\\\'')) - elif item in (True, False, None): - stdout.write('%s\n' % item) - else: - assert False, item - - stdout.write('{\n') - loop_dict(' ', variables) - stdout.write('}\n') - - def get_api(): - flavor = get_flavor() - if flavor == 'linux': - return Strace() - elif flavor == 'mac': - return Dtrace() - elif sys.platform == 'win32': - return LogmanTrace() - else: - print >> sys.stderr, 'Unsupported platform %s' % sys.platform - sys.exit(1) + """Returns the correct implementation for the current OS.""" + if sys.platform == 'cygwin': + raise NotImplementedError( + 'Not implemented for cygwin, start the script from Win32 python') + flavors = { + 'win32': LogmanTrace, + 'darwin': Dtrace, + 'sunos5': Dtrace, + 'freebsd7': Dtrace, + 'freebsd8': Dtrace, + } + # Defaults to strace. + return flavors.get(sys.platform, Strace)() def get_blacklist(api): @@ -2432,40 +2332,6 @@ def get_blacklist(api): svn_path in f) -def generate_dict(files, cwd_dir, product_dir): - """Converts the list of files into a .isolate dictionary. - - Arguments: - - files: list of files to generate a dictionary out of. - - cwd_dir: directory to base all the files from, relative to root_dir. - - product_dir: directory to replace with <(PRODUCT_DIR), relative to root_dir. - """ - cwd_dir = cleanup_path(cwd_dir) - product_dir = cleanup_path(product_dir) - - def fix(f): - """Bases the file on the most restrictive variable.""" - logging.debug('fix(%s)' % f) - # Important, GYP stores the files with / and not \. - f = f.replace(os.path.sep, '/') - if product_dir and f.startswith(product_dir): - return '<(PRODUCT_DIR)/%s' % f[len(product_dir):] - else: - # cwd_dir is usually the directory containing the gyp file. It may be - # empty if the whole directory containing the gyp file is needed. - return posix_relpath(f, cwd_dir) or './' - - corrected = [fix(f) for f in files] - tracked = [f for f in corrected if not f.endswith('/') and ' ' not in f] - untracked = [f for f in corrected if f.endswith('/') or ' ' in f] - variables = {} - if tracked: - variables[KEY_TRACKED] = tracked - if untracked: - variables[KEY_UNTRACKED] = untracked - return variables - - def trace(logfile, cmd, cwd, api, output): """Traces an executable. Returns (returncode, output) from api. @@ -2492,130 +2358,161 @@ def load_trace(logfile, root_dir, api): - api: a tracing api instance. """ results = api.parse_log(logfile, get_blacklist(api)) - results = results.strip_root(root_dir) + if root_dir: + results = results.strip_root(root_dir) simplified = extract_directories(results.files) return results, simplified -def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace): - """Tries to load the logs if available. If not, trace the test. - - Symlinks are not processed at all. - - Arguments: - - logfile: Absolute path to the OS-specific trace. - - cmd: Command list to run. - - root_dir: Base directory where the files we care about live. - - cwd_dir: Cwd to use to start the process, relative to the root_dir - directory. - - product_dir: Directory containing the executables built by the build - process, relative to the root_dir directory. It is used to - properly replace paths with <(PRODUCT_DIR) for gyp output. - - force_trace: Will force to trace unconditionally even if a trace already - exist. - """ - logging.debug( - 'trace_inputs(%s, %s, %s, %s, %s, %s)' % ( - logfile, cmd, root_dir, cwd_dir, product_dir, force_trace)) - - def print_if(txt): - if cwd_dir is None: - print txt - - # It is important to have unambiguous path. - assert os.path.isabs(root_dir), root_dir - assert os.path.isabs(logfile), logfile - assert not cwd_dir or not os.path.isabs(cwd_dir), cwd_dir - assert not product_dir or not os.path.isabs(product_dir), product_dir - +def CMDclean(parser, args): + """Cleans up traces.""" + options, args = parser.parse_args(args) api = get_api() - # Resolve any symlink - root_dir = os.path.realpath(root_dir) - if not os.path.isfile(logfile) or force_trace: - print_if('Tracing... %s' % cmd) - # Use the proper relative directory. - cwd = root_dir if not cwd_dir else os.path.join(root_dir, cwd_dir) - silent = not isEnabledFor(logging.WARNING) - returncode, _ = trace(logfile, cmd, cwd, api, silent) - if returncode and not force_trace: - return returncode - - print_if('Loading traces... %s' % logfile) - results, simplified = load_trace(logfile, root_dir, api) - - print_if('Total: %d' % len(results.files)) - print_if('Non existent: %d' % len(results.non_existent)) - for f in results.non_existent: - print_if(' %s' % f.path) - print_if( - 'Interesting: %d reduced to %d' % ( - len(results.existent), len(simplified))) - for f in simplified: - print_if(' %s' % f.path) - - if cwd_dir is not None: - value = { - 'conditions': [ - ['OS=="%s"' % get_flavor(), { - 'variables': generate_dict( - [f.path for f in simplified], cwd_dir, product_dir), - }], - ], - } - pretty_print(value, sys.stdout) + api.clean_trace(options.log) return 0 -def main(): - parser = optparse.OptionParser( - usage='%prog <options> [cmd line...]') +def CMDtrace(parser, args): + """Traces an executable.""" parser.allow_interspersed_args = False parser.add_option( - '-v', '--verbose', action='count', default=0, help='Use multiple times') - parser.add_option('-l', '--log', help='Log file') - parser.add_option( - '-c', '--cwd', - help='Signal to start the process from this relative directory. When ' - 'specified, outputs the inputs files in a way compatible for ' - 'gyp processing. Should be set to the relative path containing the ' - 'gyp file, e.g. \'chrome\' or \'net\'') + '-q', '--quiet', action='store_true', + help='Redirects traced executable output to /dev/null') + options, args = parser.parse_args(args) + + api = get_api() + try: + return trace(options.log, args, os.getcwd(), api, options.quiet)[0] + except TracingFailure, e: + print >> sys.stderr, 'Failed to trace executable' + print >> sys.stderr, e + return 1 + + +def CMDread(parser, args): + """Reads the logs and prints the result.""" parser.add_option( - '-p', '--product-dir', default='out/Release', - help='Directory for PRODUCT_DIR. Default: %default') + '-V', '--variable', + nargs=2, + action='append', + dest='variables', + metavar='VAR_NAME directory', + help=('Variables to replace relative directories against. Example: ' + '"-v HOME /home/%s" will replace all occurence of your home dir ' + 'with <(HOME)') % getpass.getuser()) parser.add_option( - '--root-dir', default=ROOT_DIR, - help='Root directory to base everything off. Default: %default') + '--root-dir', + help='Root directory to base everything off it. Anything outside of this ' + 'this directory will not be reported') parser.add_option( - '-f', '--force', - action='store_true', - default=False, - help='Force to retrace the file') - - options, args = parser.parse_args() - level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)] - logging.basicConfig( - level=level, - format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s') - - if not options.log: - parser.error('Must supply a log file with -l') - if not args: - if not os.path.isfile(options.log) or options.force: - parser.error('Must supply a command to run') - else: - args[0] = os.path.abspath(args[0]) + '-j', '--json', action='store_true', + help='Outputs raw result data as json') + options, args = parser.parse_args(args) if options.root_dir: options.root_dir = os.path.abspath(options.root_dir) - return trace_inputs( - os.path.abspath(options.log), - args, - options.root_dir, - options.cwd, - options.product_dir, - options.force) + api = get_api() + try: + results, simplified = load_trace(options.log, options.root_dir, api) + + if options.json: + json.dump(results.flatten(), sys.stdout) + else: + print('Total: %d' % len(results.files)) + print('Non existent: %d' % len(results.non_existent)) + for f in results.non_existent: + print(' %s' % f.path) + print( + 'Interesting: %d reduced to %d' % ( + len(results.existent), len(simplified))) + for f in simplified: + print(' %s' % f.path) + return 0 + except TracingFailure, e: + print >> sys.stderr, 'Failed to read trace' + print >> sys.stderr, e + return 1 + + +def CMDhelp(parser, args): + """Prints list of commands or help for a specific command""" + _, args = parser.parse_args(args) + if len(args) == 1: + # The command was "%prog help command", replaces ourself with + # "%prog command --help" so help is correctly printed out. + return main(args + ['--help']) + parser.print_help() + return 0 + + +def get_command_handler(name): + """Returns the command handler or CMDhelp if it doesn't exist.""" + return getattr(sys.modules[__name__], 'CMD%s' % name, CMDhelp) + + +def gen_parser(command): + """Returns the default OptionParser instance for the corresponding command + handler. + """ + more = getattr(command, 'usage_more', '') + command_display = command_name = command.__name__[3:] + if command_name == 'help': + command_display = '<command>' + description = None + else: + # OptParser.description prefer nicely non-formatted strings. + description = re.sub('[\r\n ]{2,}', ' ', command.__doc__) + + parser = optparse.OptionParser( + usage='usage: %%prog %s [options] %s' % (command_display, more), + description=description) + + # Default options. + parser.add_option( + '-v', '--verbose', action='count', default=0, + help='Use multiple times to increase verbosity') + if command_name != 'help': + parser.add_option( + '-l', '--log', help='Log file to generate or read, required') + + old_parser_args = parser.parse_args + def parse_args_with_logging(args=None): + """Processes the default options. + + Enforces and sets options.log to an absolute path. + Configures logging. + """ + options, args = old_parser_args(args) + level = [ + logging.ERROR, logging.INFO, logging.DEBUG, + ][min(2, options.verbose)] + logging.basicConfig( + level=level, + format='%(levelname)5s %(module)15s(%(lineno)3d):%(message)s') + if command_name != 'help': + if not options.log: + parser.error('Must supply a log file with -l') + options.log = os.path.abspath(options.log) + return options, args + parser.parse_args = parse_args_with_logging + return parser + + +def main(argv): + # Generates the command help late so all commands are listed. + CMDhelp.usage_more = ( + '\n\nCommands are:\n' + + '\n'.join( + ' %-10s %s' % ( + fn[3:], get_command_handler(fn[3:]).__doc__.split('\n')[0].strip()) + for fn in dir(sys.modules[__name__]) + if fn.startswith('CMD'))) + + command = get_command_handler(argv[0] if argv else None) + parser = gen_parser(command) + return command(parser, argv[1:]) if __name__ == '__main__': - sys.exit(main()) + sys.exit(main(sys.argv[1:])) diff --git a/tools/isolate/trace_inputs_smoke_test.py b/tools/isolate/trace_inputs_smoke_test.py index bfd36a1..75b7965 100755 --- a/tools/isolate/trace_inputs_smoke_test.py +++ b/tools/isolate/trace_inputs_smoke_test.py @@ -3,7 +3,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import cStringIO +import json import logging import os import shutil @@ -35,7 +35,47 @@ class TraceInputsBase(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp(prefix='trace_smoke_test') self.log = os.path.join(self.tempdir, 'log') - os.chdir(ROOT_DIR) + self.trace_inputs_path = os.path.join(ROOT_DIR, 'trace_inputs.py') + + # Wraps up all the differences between OSes here. + # - Windows doesn't track initial_cwd. + # - OSX replaces /usr/bin/python with /usr/bin/python2.7. + self.cwd = os.path.join(ROOT_DIR, u'data') + self.initial_cwd = self.cwd + if sys.platform == 'win32': + # Not supported on Windows. + self.initial_cwd = None + + # There's 3 kinds of references to python, self.executable, + # self.real_executable and self.naked_executable. It depends how python was + # started. + self.executable = sys.executable + if sys.platform == 'darwin': + # /usr/bin/python is a thunk executable that decides which version of + # python gets executed. + suffix = '.'.join(map(str, sys.version_info[0:2])) + if os.access(self.executable + suffix, os.X_OK): + # So it'll look like /usr/bin/python2.7 + self.executable += suffix + + import trace_inputs + self.real_executable = trace_inputs.get_native_path_case( + self.executable) + trace_inputs = None + + if sys.platform == 'darwin': + # Interestingly, only OSX does resolve the symlink manually before + # starting the executable. + if os.path.islink(self.real_executable): + self.real_executable = os.path.normpath( + os.path.join( + os.path.dirname(self.real_executable), + os.readlink(self.real_executable))) + + # self.naked_executable will only be naked on Windows. + self.naked_executable = sys.executable + if sys.platform == 'win32': + self.naked_executable = os.path.basename(sys.executable) def tearDown(self): if VERBOSE: @@ -44,7 +84,8 @@ class TraceInputsBase(unittest.TestCase): shutil.rmtree(self.tempdir) @staticmethod - def command(from_data): + def get_child_command(from_data): + """Returns command to run the child1.py.""" cmd = [sys.executable] if from_data: # When the gyp argument is specified, the command is started from --cwd @@ -58,22 +99,22 @@ class TraceInputsBase(unittest.TestCase): class TraceInputs(TraceInputsBase): - def _execute(self, command): + def _execute(self, mode, command, cwd): cmd = [ - sys.executable, os.path.join('..', '..', 'trace_inputs.py'), + sys.executable, + self.trace_inputs_path, + mode, '--log', self.log, - '--root-dir', ROOT_DIR, ] if VERBOSE: cmd.extend(['-v'] * 3) cmd.extend(command) - # The current directory doesn't matter, the traced process will be called - # from the correct cwd. - cwd = os.path.join('data', 'trace_inputs') - # Ignore stderr. logging.info('Command: %s' % ' '.join(cmd)) p = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=cwd, universal_newlines=True) out, err = p.communicate() if VERBOSE: @@ -82,55 +123,94 @@ class TraceInputs(TraceInputsBase): raise CalledProcessError(p.returncode, cmd, out + err, cwd) return out or '' + def _trace(self, from_data): + if from_data: + cwd = os.path.join(ROOT_DIR, 'data') + else: + cwd = ROOT_DIR + return self._execute('trace', self.get_child_command(from_data), cwd=cwd) + def test_trace(self): - expected_end = [ + expected = '\n'.join(( + 'Total: 5', + 'Non existent: 0', 'Interesting: 5 reduced to 3', ' data/trace_inputs/'.replace('/', os.path.sep), ' trace_inputs.py', ' %s' % FILENAME, - ] - actual = self._execute(self.command(False)).splitlines() - self.assertTrue(actual[0].startswith('Tracing... ['), actual) - self.assertTrue(actual[1].startswith('Loading traces... '), actual) - self.assertTrue(actual[2].startswith('Total: '), actual) - if sys.platform == 'win32': - # On windows, python searches the current path for python stdlib like - # subprocess.py and others, I'm not sure why. - self.assertTrue(actual[3].startswith('Non existent: '), actual[3]) - else: - self.assertEquals('Non existent: 0', actual[3]) - # Ignore any Unexpected part. - # TODO(maruel): Make sure there is no Unexpected part, even in the case of - # virtualenv usage. - self.assertEquals(expected_end, actual[-len(expected_end):]) + )) + '\n' + trace_expected = '\n'.join(( + 'child from %s' % ROOT_DIR, + 'child2', + )) + '\n' + trace_actual = self._trace(False) + actual = self._execute('read', ['--root-dir', ROOT_DIR], cwd=ROOT_DIR) + self.assertEquals(expected, actual) + self.assertEquals(trace_expected, trace_actual) - def test_trace_gyp(self): - import trace_inputs - expected_value = { - 'conditions': [ - ['OS=="%s"' % trace_inputs.get_flavor(), { - 'variables': { - 'isolate_dependency_tracked': [ - # It is run from data/trace_inputs. - '../trace_inputs.py', - '../%s' % FILENAME, - ], - 'isolate_dependency_untracked': [ - 'trace_inputs/', + @staticmethod + def _size(*args): + return os.stat(os.path.join(ROOT_DIR, *args)).st_size + + def test_trace_json(self): + expected = { + u'root': { + u'children': [ + { + u'children': [], + u'command': [u'python', u'child2.py'], + u'executable': self.naked_executable, + u'files': [ + { + u'path': os.path.join(u'data', 'trace_inputs', 'child2.py'), + u'size': self._size('data', 'trace_inputs', 'child2.py'), + }, + { + u'path': os.path.join(u'data', 'trace_inputs', 'test_file.txt'), + u'size': self._size('data', 'trace_inputs', 'test_file.txt'), + }, ], + u'initial_cwd': self.initial_cwd, + #u'pid': 123, }, - }], - ], + ], + u'command': [ + self.executable, + os.path.join(u'trace_inputs', 'child1.py'), + u'--child-gyp', + ], + u'executable': self.real_executable, + u'files': [ + { + u'path': os.path.join(u'data', 'trace_inputs', 'child1.py'), + u'size': self._size('data', 'trace_inputs', 'child1.py'), + }, + { + u'path': u'trace_inputs.py', + u'size': self._size('trace_inputs.py'), + }, + { + u'path': u'trace_inputs_smoke_test.py', + u'size': self._size('trace_inputs_smoke_test.py'), + }, + ], + u'initial_cwd': self.initial_cwd, + #u'pid': 123, + }, } - expected_buffer = cStringIO.StringIO() - trace_inputs.pretty_print(expected_value, expected_buffer) - - cmd = [ - '--cwd', 'data', - '--product', '.', # Not tested. - ] + self.command(True) - actual = self._execute(cmd) - self.assertEquals(expected_buffer.getvalue(), actual) + trace_expected = '\n'.join(( + 'child_gyp from %s' % os.path.join(ROOT_DIR, 'data'), + 'child2', + )) + '\n' + trace_actual = self._trace(True) + actual_text = self._execute( + 'read', ['--root-dir', ROOT_DIR, '--json'], cwd=ROOT_DIR) + actual_json = json.loads(actual_text) + # Removes the pids. + self.assertTrue(actual_json['root'].pop('pid')) + self.assertTrue(actual_json['root']['children'][0].pop('pid')) + self.assertEquals(expected, actual_json) + self.assertEquals(trace_expected, trace_actual) class TraceInputsImport(TraceInputsBase): @@ -138,31 +218,6 @@ class TraceInputsImport(TraceInputsBase): super(TraceInputsImport, self).setUp() import trace_inputs self.trace_inputs = trace_inputs - self.cwd = os.path.join(ROOT_DIR, u'data') - self.initial_cwd = self.cwd - if sys.platform == 'win32': - # Still not supported on Windows. - self.initial_cwd = None - self.executable = sys.executable - if sys.platform == 'darwin': - # /usr/bin/python is a thunk executable that decides which version of - # python gets executed. - suffix = '.'.join(map(str, sys.version_info[0:2])) - if os.access(self.executable + suffix, os.X_OK): - self.executable += suffix - self.real_executable = self.trace_inputs.get_native_path_case( - self.executable) - if sys.platform == 'darwin': - # Interestingly, only OSX does resolve the symlink manually before - # starting the executable. - if os.path.islink(self.real_executable): - self.real_executable = os.path.normpath( - os.path.join( - os.path.dirname(self.real_executable), - os.readlink(self.real_executable))) - self.naked_executable = sys.executable - if sys.platform == 'win32': - self.naked_executable = os.path.basename(sys.executable) def tearDown(self): del self.trace_inputs @@ -185,7 +240,7 @@ class TraceInputsImport(TraceInputsBase): # Deliberately start the trace from the wrong path. Starts it from the # directory 'data' so 'data/data/trace_inputs/child1.py' is not accessible, # so child2.py process is not started. - results, simplified = self._execute(self.command(False)) + results, simplified = self._execute(self.get_child_command(False)) expected = { 'root': { 'children': [], @@ -250,7 +305,7 @@ class TraceInputsImport(TraceInputsBase): 'initial_cwd': self.initial_cwd, }, } - results, simplified = self._execute(self.command(True)) + results, simplified = self._execute(self.get_child_command(True)) actual = results.flatten() self.assertTrue(actual['root'].pop('pid')) self.assertTrue(actual['root']['children'][0].pop('pid')) diff --git a/tools/isolate/trace_inputs_test.py b/tools/isolate/trace_inputs_test.py index ccc03b2..d95be16 100755 --- a/tools/isolate/trace_inputs_test.py +++ b/tools/isolate/trace_inputs_test.py @@ -3,7 +3,6 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import cStringIO import logging import os import unittest @@ -16,84 +15,6 @@ import trace_inputs class TraceInputs(unittest.TestCase): - def _test(self, value, expected): - actual = cStringIO.StringIO() - trace_inputs.pretty_print(value, actual) - self.assertEquals(expected, actual.getvalue()) - - def test_pretty_print_empty(self): - self._test({}, '{\n}\n') - - def test_pretty_print_mid_size(self): - value = { - 'variables': { - 'bar': [ - 'file1', - 'file2', - ], - }, - 'conditions': [ - ['OS=\"foo\"', { - 'variables': { - trace_inputs.KEY_UNTRACKED: [ - 'dir1', - 'dir2', - ], - trace_inputs.KEY_TRACKED: [ - 'file4', - 'file3', - ], - 'command': ['python', '-c', 'print "H\\i\'"'], - 'read_only': True, - 'relative_cwd': 'isol\'at\\e', - }, - }], - ['OS=\"bar\"', { - 'variables': {}, - }, { - 'variables': {}, - }], - ], - } - expected = ( - "{\n" - " 'variables': {\n" - " 'bar': [\n" - " 'file1',\n" - " 'file2',\n" - " ],\n" - " },\n" - " 'conditions': [\n" - " ['OS=\"foo\"', {\n" - " 'variables': {\n" - " 'command': [\n" - " 'python',\n" - " '-c',\n" - " 'print \"H\\i\'\"',\n" - " ],\n" - " 'relative_cwd': 'isol\\'at\\\\e',\n" - " 'read_only': True\n" - " 'isolate_dependency_tracked': [\n" - " 'file4',\n" - " 'file3',\n" - " ],\n" - " 'isolate_dependency_untracked': [\n" - " 'dir1',\n" - " 'dir2',\n" - " ],\n" - " },\n" - " }],\n" - " ['OS=\"bar\"', {\n" - " 'variables': {\n" - " },\n" - " }, {\n" - " 'variables': {\n" - " },\n" - " }],\n" - " ],\n" - "}\n") - self._test(value, expected) - def test_process_quoted_arguments(self): test_cases = ( ('"foo"', ['foo']), @@ -110,7 +31,7 @@ def join_norm(*args): return unicode(os.path.normpath(os.path.join(*args))) -if trace_inputs.get_flavor() == 'linux': +if sys.platform != 'win32': class StraceInputs(unittest.TestCase): # Represents the root process pid (an arbitrary number). _ROOT_PID = 27 diff --git a/tools/isolate/trace_test_cases.py b/tools/isolate/trace_test_cases.py index 9cf2406..791d0cd 100755 --- a/tools/isolate/trace_test_cases.py +++ b/tools/isolate/trace_test_cases.py @@ -18,6 +18,7 @@ import sys import tempfile import time +import isolate_common import list_test_cases import trace_inputs @@ -61,7 +62,7 @@ def trace_test_case( print >> sys.stderr, '\nTracing failed for: %s' % ' '.join(cmd) print >> sys.stderr, str(e) if simplified: - variables = trace_inputs.generate_dict(simplified, cwd_dir, product_dir) + variables = isolate_common.generate_dict(simplified, cwd_dir, product_dir) else: variables = {} return { |