summaryrefslogtreecommitdiffstats
path: root/tools/isolate
diff options
context:
space:
mode:
Diffstat (limited to 'tools/isolate')
-rwxr-xr-xtools/isolate/isolate_test.py24
-rwxr-xr-xtools/isolate/merge_gyp.py301
-rwxr-xr-xtools/isolate/merge_gyp_test.py188
-rwxr-xr-xtools/isolate/trace_inputs.py98
-rwxr-xr-xtools/isolate/trace_inputs_smoke_test.py163
-rwxr-xr-xtools/isolate/trace_inputs_test.py194
6 files changed, 808 insertions, 160 deletions
diff --git a/tools/isolate/isolate_test.py b/tools/isolate/isolate_test.py
index 6d19212..3e05c226 100755
--- a/tools/isolate/isolate_test.py
+++ b/tools/isolate/isolate_test.py
@@ -3,6 +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 hashlib
import json
import logging
@@ -225,14 +226,21 @@ class Isolate(unittest.TestCase):
# cmd[0] is not generated from infiles[0] so it's not using a relative path.
self._expected_result(
False, ['isolate_test.py'], ['isolate_test.py', '--ok'], False)
- expected = (
- "{\n 'variables': {\n"
- " 'isolate_files': [\n"
- " '<(DEPTH)/isolate_test.py',\n"
- " ],\n"
- " 'isolate_dirs': [\n"
- " ],\n },\n},\n")
- self.assertEquals(expected, out)
+
+ expected_value = {
+ 'conditions': [
+ ['OS=="%s"' % self.isolate.trace_inputs.get_flavor(), {
+ 'variables': {
+ 'isolate_files': [
+ '<(DEPTH)/isolate_test.py',
+ ],
+ },
+ }],
+ ],
+ }
+ expected_buffer = cStringIO.StringIO()
+ self.isolate.trace_inputs.pretty_print(expected_value, expected_buffer)
+ self.assertEquals(expected_buffer.getvalue(), out)
def main():
diff --git a/tools/isolate/merge_gyp.py b/tools/isolate/merge_gyp.py
new file mode 100755
index 0000000..fd4a0d3
--- /dev/null
+++ b/tools/isolate/merge_gyp.py
@@ -0,0 +1,301 @@
+#!/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.
+
+"""Merges multiple OS-specific gyp dependency lists into one that works on all
+of them.
+
+
+The logic is relatively simple. Takes the current conditions, add more
+condition, find the strict subset. Done.
+"""
+
+import copy
+import logging
+import optparse
+import re
+import sys
+
+import trace_inputs
+
+
+def union(lhs, rhs):
+ """Merges two compatible datastructures composed of dict/list/set."""
+ assert lhs is not None or rhs is not None
+ if lhs is None:
+ return copy.deepcopy(rhs)
+ if rhs is None:
+ return copy.deepcopy(lhs)
+ assert type(lhs) == type(rhs), (lhs, rhs)
+ if isinstance(lhs, dict):
+ return dict((k, union(lhs.get(k), rhs.get(k))) for k in set(lhs).union(rhs))
+ elif isinstance(lhs, set):
+ # Do not go inside the set.
+ return lhs.union(rhs)
+ elif isinstance(lhs, list):
+ # Do not go inside the list.
+ return lhs + rhs
+ assert False, type(lhs)
+
+
+def process_variables(for_os, variables):
+ """Extracts files and dirs from the |variables| dict.
+
+ Returns a list of exactly two items. Each item is a dict that maps a string
+ to a set (of strings).
+
+ In the first item, the keys are file names, and the values are sets of OS
+ names, like "win" or "mac". In the second item, the keys are directory names,
+ and the values are sets of OS names too.
+ """
+ VALID_VARIABLES = ['isolate_files', 'isolate_dirs']
+
+ # Verify strictness.
+ assert isinstance(variables, dict), variables
+ assert set(VALID_VARIABLES).issuperset(set(variables)), variables.keys()
+ for items in variables.itervalues():
+ assert isinstance(items, list), items
+ assert all(isinstance(i, basestring) for i in items), items
+
+ # Returns [files, dirs]
+ return [
+ dict((name, set([for_os])) for name in variables.get(var, []))
+ for var in VALID_VARIABLES
+ ]
+
+
+def eval_content(content):
+ """Evaluates a GYP file and return the value defined in it."""
+ globs = {'__builtins__': None}
+ locs = {}
+ value = eval(content, globs, locs)
+ assert locs == {}, locs
+ assert globs == {'__builtins__': None}, globs
+ return value
+
+
+def _process_inner(for_os, inner, old_files, old_dirs, old_os):
+ """Processes the variables inside a condition.
+
+ Only meant to be called by parse_gyp_dict().
+
+ Args:
+ - for_os: OS where the references are tracked for.
+ - inner: Inner dictionary to process.
+ - old_files: Previous list of files to union with.
+ - old_dirs: Previous list of directories to union with.
+ - old_os: Previous list of OSes referenced to union with.
+
+ Returns:
+ - A tuple of (files, dirs, os) where each list is a union of the new
+ dependencies found for this OS, as referenced by for_os, and the previous
+ list.
+ """
+ assert isinstance(inner, dict), inner
+ assert set(['variables']).issuperset(set(inner)), inner.keys()
+ new_files, new_dirs = process_variables(for_os, inner.get('variables', {}))
+ if new_files or new_dirs:
+ old_os = old_os.union([for_os.lstrip('!')])
+ return union(old_files, new_files), union(old_dirs, new_dirs), old_os
+
+
+def parse_gyp_dict(value):
+ """Parses a gyp dict as returned by eval_content().
+
+ |value| is the loaded dictionary that was defined in the gyp file.
+
+ Returns a 3-tuple, where the first two items are the same as the items
+ returned by process_variable() in the same order, and the last item is a set
+ of strings of all OSs seen in the input dict.
+
+ The expected format is strict, anything diverting from the format below will
+ fail:
+ {
+ 'variables': {
+ 'isolate_files': [
+ ...
+ ],
+ 'isolate_dirs: [
+ ...
+ ],
+ },
+ 'conditions': [
+ ['OS=="<os>"', {
+ 'variables': {
+ ...
+ },
+ }, { # else
+ 'variables': {
+ ...
+ },
+ }],
+ ...
+ ],
+ }
+ """
+ assert isinstance(value, dict), value
+ VALID_ROOTS = ['variables', 'conditions']
+ assert set(VALID_ROOTS).issuperset(set(value)), value.keys()
+
+ # Global level variables.
+ oses = set()
+ files, dirs = process_variables(None, value.get('variables', {}))
+
+ # OS specific variables.
+ conditions = value.get('conditions', [])
+ assert isinstance(conditions, list), conditions
+ for condition in conditions:
+ assert isinstance(condition, list), condition
+ assert 2 <= len(condition) <= 3, condition
+ m = re.match(r'OS==\"([a-z]+)\"', condition[0])
+ assert m, condition[0]
+ condition_os = m.group(1)
+
+ files, dirs, oses = _process_inner(
+ condition_os, condition[1], files, dirs, oses)
+
+ if len(condition) == 3:
+ files, dirs, oses = _process_inner(
+ '!' + condition_os, condition[2], files, dirs, oses)
+
+ # TODO(maruel): _expand_negative() should be called here, because otherwise
+ # the OSes the negative condition represents is lost once the gyps are merged.
+ # This cause an invalid expansion in reduce_inputs() call.
+ return files, dirs, oses
+
+
+def parse_gyp_dicts(gyps):
+ """Parses each gyp file and returns the merged results.
+
+ It only loads what parse_gyp_dict() can process.
+
+ Return values:
+ files: dict(filename, set(OS where this filename is a dependency))
+ dirs: dict(dirame, set(OS where this dirname is a dependency))
+ oses: set(all the OSes referenced)
+ """
+ files = {}
+ dirs = {}
+ oses = set()
+ for gyp in gyps:
+ with open(gyp, 'rb') as gyp_file:
+ content = gyp_file.read()
+ gyp_files, gyp_dirs, gyp_oses = parse_gyp_dict(eval_content(content))
+ files = union(gyp_files, files)
+ dirs = union(gyp_dirs, dirs)
+ oses |= gyp_oses
+ return files, dirs, oses
+
+
+def _expand_negative(items, oses):
+ """Converts all '!foo' value in the set by oses.difference('foo')."""
+ assert None not in oses and len(oses) >= 2, oses
+ for name in items:
+ if None in items[name]:
+ # Shortcut any item having None in their set. An item listed in None means
+ # the item is a dependency on all OSes. As such, there is no need to list
+ # any OS.
+ items[name] = set([None])
+ continue
+ for neg in [o for o in items[name] if o.startswith('!')]:
+ # Replace it with the inverse.
+ items[name] = items[name].union(oses.difference([neg[1:]]))
+ items[name].remove(neg)
+ if items[name] == oses:
+ items[name] = set([None])
+
+
+def _compact_negative(items, oses):
+ """Converts all oses.difference('foo') to '!foo'.
+
+ It is doing the reverse of _expand_negative().
+ """
+ assert None not in oses and len(oses) >= 3, oses
+ for name in items:
+ missing = oses.difference(items[name])
+ if len(missing) == 1:
+ # Replace it with a negative.
+ items[name] = set(['!' + tuple(missing)[0]])
+
+
+def reduce_inputs(files, dirs, oses):
+ """Reduces the variables to their strictest minimum."""
+ # Construct the inverse map first.
+ # Look at each individual file and directory, map where they are used and
+ # reconstruct the inverse dictionary.
+ # First, expands all '!' builders into the reverse.
+ # TODO(maruel): This is too late to call _expand_negative(). The exact list
+ # negative OSes condition it represents is lost at that point.
+ _expand_negative(files, oses)
+ _expand_negative(dirs, oses)
+
+ # Do not convert back to negative if only 2 OSes were merged. It is easier to
+ # read this way.
+ if len(oses) > 2:
+ _compact_negative(files, oses)
+ _compact_negative(dirs, oses)
+
+ return files, dirs
+
+
+def convert_to_gyp(files, dirs):
+ """Regenerates back a gyp-like configuration dict from files and dirs
+ mappings.
+
+ Sort the lists.
+ """
+ # First, inverse the mapping to make it dict first.
+ config = {}
+ def to_cond(items, name):
+ for item, oses in items.iteritems():
+ for cond_os in oses:
+ condition_values = config.setdefault(
+ None if cond_os is None else cond_os.lstrip('!'),
+ [{}, {}])
+ # If condition is negative, use index 1, else use index 0.
+ condition_value = condition_values[int((cond_os or '').startswith('!'))]
+ # The list of items (files or dirs). Append the new item and keep the
+ # list sorted.
+ l = condition_value.setdefault('variables', {}).setdefault(name, [])
+ l.append(item)
+ l.sort()
+
+ to_cond(files, 'isolate_files')
+ to_cond(dirs, 'isolate_dirs')
+
+ out = {}
+ for o in sorted(config):
+ d = config[o]
+ if o is None:
+ assert not d[1]
+ out = union(out, d[0])
+ else:
+ c = out.setdefault('conditions', [])
+ if d[1]:
+ c.append(['OS=="%s"' % o] + d)
+ else:
+ c.append(['OS=="%s"' % o] + d[0:1])
+ return out
+
+
+def main():
+ parser = optparse.OptionParser(
+ usage='%prog <options> [file1] [file2] ...')
+ parser.add_option(
+ '-v', '--verbose', action='count', default=0, help='Use multiple times')
+
+ 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')
+
+ trace_inputs.pretty_print(
+ convert_to_gyp(*reduce_inputs(*parse_gyp_dicts(args))),
+ sys.stdout)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/isolate/merge_gyp_test.py b/tools/isolate/merge_gyp_test.py
new file mode 100755
index 0000000..9826c68
--- /dev/null
+++ b/tools/isolate/merge_gyp_test.py
@@ -0,0 +1,188 @@
+#!/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 unittest
+
+import merge_gyp
+
+
+class MergeGyp(unittest.TestCase):
+ def test_unknown_key(self):
+ try:
+ merge_gyp.process_variables(None, {'foo': [],})
+ self.fail()
+ except AssertionError:
+ pass
+
+ def test_unknown_var(self):
+ try:
+ merge_gyp.process_variables(None, {'variables': {'foo': [],}})
+ self.fail()
+ except AssertionError:
+ pass
+
+ def test_parse_gyp_dict_empty(self):
+ f, d, o = merge_gyp.parse_gyp_dict({})
+ self.assertEquals({}, f)
+ self.assertEquals({}, d)
+ self.assertEquals(set(), o)
+
+ def test_parse_gyp_dict(self):
+ value = {
+ 'variables': {
+ 'isolate_files': [
+ 'a',
+ ],
+ 'isolate_dirs': [
+ 'b',
+ ],
+ },
+ 'conditions': [
+ ['OS=="atari"', {
+ 'variables': {
+ 'isolate_files': [
+ 'c',
+ 'x',
+ ],
+ 'isolate_dirs': [
+ 'd',
+ ],
+ },
+ }, { # else
+ 'variables': {
+ 'isolate_files': [
+ 'e',
+ 'x',
+ ],
+ 'isolate_dirs': [
+ 'f',
+ ],
+ },
+ }],
+ ['OS=="amiga"', {
+ 'variables': {
+ 'isolate_files': [
+ 'g',
+ ],
+ },
+ }],
+ ['OS=="inexistent"', {
+ }],
+ ['OS=="coleco"', {
+ }, { # else
+ 'variables': {
+ 'isolate_dirs': [
+ 'h',
+ ],
+ },
+ }],
+ ],
+ }
+ expected_files = {
+ 'a': set([None]),
+ 'c': set(['atari']),
+ 'e': set(['!atari']),
+ 'g': set(['amiga']),
+ 'x': set(['!atari', 'atari']), # potential for reduction
+ }
+ expected_dirs = {
+ 'b': set([None]),
+ 'd': set(['atari']),
+ 'f': set(['!atari']),
+ 'h': set(['!coleco']),
+ }
+ # coleco is included even if only negative.
+ expected_oses = set(['atari', 'amiga', 'coleco'])
+ actual_files, actual_dirs, actual_oses = merge_gyp.parse_gyp_dict(value)
+ self.assertEquals(expected_files, actual_files)
+ self.assertEquals(expected_dirs, actual_dirs)
+ self.assertEquals(expected_oses, actual_oses)
+
+ def test_reduce_inputs(self):
+ value_files = {
+ 'a': set([None]),
+ 'c': set(['atari']),
+ 'e': set(['!atari']),
+ 'g': set(['amiga']),
+ 'x': set(['!atari', 'atari']),
+ }
+ value_dirs = {
+ 'b': set([None]),
+ 'd': set(['atari']),
+ 'f': set(['!atari']),
+ 'h': set(['!coleco']),
+ }
+ value_oses = set(['atari', 'amiga', 'coleco'])
+ expected_files = {
+ 'a': set([None]),
+ 'c': set(['atari']),
+ 'e': set(['!atari']),
+ 'g': set(['amiga']),
+ 'x': set([None]), # Reduced.
+ }
+ expected_dirs = {
+ 'b': set([None]),
+ 'd': set(['atari']),
+ 'f': set(['!atari']),
+ 'h': set(['!coleco']),
+ }
+ actual_files, actual_dirs = merge_gyp.reduce_inputs(
+ value_files, value_dirs, value_oses)
+ self.assertEquals(expected_files, actual_files)
+ self.assertEquals(expected_dirs, actual_dirs)
+
+ def test_convert_to_gyp(self):
+ files = {
+ 'a': set([None]),
+ 'x': set([None]),
+
+ 'g': set(['amiga']),
+
+ 'c': set(['atari']),
+ 'e': set(['!atari']),
+ }
+ dirs = {
+ 'b': set([None]),
+
+ 'd': set(['atari']),
+ 'f': set(['!atari']),
+
+ 'h': set(['!coleco']),
+ }
+ expected = {
+ 'variables': {
+ 'isolate_dirs': ['b'],
+ 'isolate_files': ['a', 'x'],
+ },
+ 'conditions': [
+ ['OS=="amiga"', {
+ 'variables': {
+ 'isolate_files': ['g'],
+ },
+ }],
+ ['OS=="atari"', {
+ 'variables': {
+ 'isolate_dirs': ['d'],
+ 'isolate_files': ['c'],
+ },
+ }, {
+ 'variables': {
+ 'isolate_dirs': ['f'],
+ 'isolate_files': ['e'],
+ },
+ }],
+ ['OS=="coleco"', {
+ }, {
+ 'variables': {
+ 'isolate_dirs': ['h'],
+ },
+ }],
+ ],
+ }
+ self.assertEquals(expected, merge_gyp.convert_to_gyp(files, dirs))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/isolate/trace_inputs.py b/tools/isolate/trace_inputs.py
index 16cce27..b52b80d 100755
--- a/tools/isolate/trace_inputs.py
+++ b/tools/isolate/trace_inputs.py
@@ -22,6 +22,19 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR))
+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)
@@ -635,6 +648,59 @@ def extract_directories(files, root):
return sorted(files)
+def pretty_print(variables, stdout):
+ """Outputs a gyp compatible list from the decoded variables."""
+ # Similar to pprint.print() but with NIH syndrome.
+
+ 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)
+ 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):
+ # Use reversed sorting since it happens we always want it that way.
+ for key in sorted(items, reverse=True):
+ 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')
+ else:
+ assert False
+
+ stdout.write('{\n')
+ loop_dict(' ', variables)
+ stdout.write('}\n')
+
+
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.
@@ -672,12 +738,13 @@ def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace):
if cwd_dir is None:
print(txt)
- if sys.platform == 'linux2':
+ flavor = get_flavor()
+ if flavor == 'linux':
api = Strace()
- elif sys.platform == 'darwin':
+ elif flavor == 'mac':
api = Dtrace()
else:
- print >> sys.stderr, 'Unsupported platform'
+ print >> sys.stderr, 'Unsupported platform %s' % sys.platform
return 1
if not os.path.isfile(logfile) or force_trace:
@@ -745,18 +812,19 @@ def trace_inputs(logfile, cmd, root_dir, cwd_dir, product_dir, force_trace):
corrected = [fix(f) for f in simplified]
files = [f for f in corrected if not f.endswith('/')]
dirs = [f for f in corrected if f.endswith('/')]
- # Constructs the python code manually.
- print(
- '{\n'
- ' \'variables\': {\n'
- ' \'isolate_files\': [\n') + (
- ''.join(' \'%s\',\n' % f for f in files)) + (
- ' ],\n'
- ' \'isolate_dirs\': [\n') + (
- ''.join(' \'%s\',\n' % f for f in dirs)) + (
- ' ],\n'
- ' },\n'
- '},')
+ variables = {}
+ if files:
+ variables['isolate_files'] = files
+ if dirs:
+ variables['isolate_dirs'] = dirs
+ value = {
+ 'conditions': [
+ ['OS=="%s"' % flavor, {
+ 'variables': variables,
+ }],
+ ],
+ }
+ pretty_print(value, sys.stdout)
return 0
diff --git a/tools/isolate/trace_inputs_smoke_test.py b/tools/isolate/trace_inputs_smoke_test.py
new file mode 100755
index 0000000..13bb1e9
--- /dev/null
+++ b/tools/isolate/trace_inputs_smoke_test.py
@@ -0,0 +1,163 @@
+#!/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 os
+import shutil
+import subprocess
+import sys
+import tempfile
+import unittest
+
+ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
+FILENAME = os.path.basename(__file__)
+VERBOSE = False
+
+
+class CalledProcessError(subprocess.CalledProcessError):
+ """Makes 2.6 version act like 2.7"""
+ def __init__(self, returncode, cmd, output, cwd):
+ super(CalledProcessError, self).__init__(returncode, cmd)
+ self.output = output
+ self.cwd = cwd
+
+ def __str__(self):
+ return super(CalledProcessError, self).__str__() + (
+ '\n'
+ 'cwd=%s\n%s') % (self.cwd, self.output)
+
+
+class TraceInputs(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ self.log = os.path.join(self.tempdir, 'log')
+ os.chdir(ROOT_DIR)
+
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+
+ def _execute(self, is_gyp):
+ cmd = [
+ sys.executable, os.path.join('..', 'trace_inputs.py'),
+ '--log', self.log,
+ '--root-dir', ROOT_DIR,
+ ]
+ if is_gyp:
+ cmd.extend(
+ [
+ '--cwd', 'data',
+ '--product', '.', # Not tested.
+ ])
+ cmd.append(os.path.join('..', FILENAME))
+ if is_gyp:
+ # When the gyp argument is not specified, the command is started from
+ # --root-dir directory.
+ cmd.append('--child-gyp')
+ else:
+ # When the gyp argument is specified, the command is started from --cwd
+ # directory.
+ cmd.append('--child')
+
+ cwd = 'data'
+ p = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd)
+ out = p.communicate()[0]
+ if p.returncode:
+ raise CalledProcessError(p.returncode, cmd, out, cwd)
+ return out
+
+ def test_trace(self):
+ if sys.platform not in ('linux2', 'darwin'):
+ print 'WARNING: unsupported: %s' % sys.platform
+ return
+ expected_end = [
+ "Interesting: 4 reduced to 3",
+ " data/trace_inputs/",
+ " trace_inputs.py",
+ " %s" % FILENAME,
+ ]
+ actual = self._execute(False).splitlines()
+ self.assertTrue(actual[0].startswith('Tracing... ['))
+ self.assertTrue(actual[1].startswith('Loading traces... '))
+ self.assertTrue(actual[2].startswith('Total: '))
+ 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):])
+
+ def test_trace_gyp(self):
+ if sys.platform not in ('linux2', 'darwin'):
+ print 'WARNING: unsupported: %s' % sys.platform
+ return
+ import trace_inputs
+ expected_value = {
+ 'conditions': [
+ ['OS=="%s"' % trace_inputs.get_flavor(), {
+ 'variables': {
+ 'isolate_files': [
+ '<(DEPTH)/trace_inputs.py',
+ '<(DEPTH)/%s' % FILENAME,
+ ],
+ 'isolate_dirs': [
+ 'trace_inputs/',
+ ],
+ },
+ }],
+ ],
+ }
+ expected_buffer = cStringIO.StringIO()
+ trace_inputs.pretty_print(expected_value, expected_buffer)
+
+ actual = self._execute(True)
+ self.assertEquals(expected_buffer.getvalue(), actual)
+
+
+def child():
+ """When the gyp argument is not specified, the command is started from
+ --root-dir directory.
+ """
+ print 'child'
+ # Force file opening with a non-normalized path.
+ open(os.path.join('data', '..', 'trace_inputs.py'), 'rb').close()
+ # Do not wait for the child to exit.
+ # Use relative directory.
+ subprocess.Popen(
+ ['python', 'child2.py'], cwd=os.path.join('data', 'trace_inputs'))
+ return 0
+
+
+def child_gyp():
+ """When the gyp argument is specified, the command is started from --cwd
+ directory.
+ """
+ print 'child_gyp'
+ # Force file opening.
+ open(os.path.join('..', 'trace_inputs.py'), 'rb').close()
+ # Do not wait for the child to exit.
+ # Use relative directory.
+ subprocess.Popen(['python', 'child2.py'], cwd='trace_inputs')
+ return 0
+
+
+def main():
+ global VERBOSE
+ VERBOSE = '-v' in sys.argv
+ level = logging.DEBUG if VERBOSE else logging.ERROR
+ logging.basicConfig(level=level)
+ if len(sys.argv) == 1:
+ unittest.main()
+
+ if sys.argv[1] == '--child':
+ return child()
+ if sys.argv[1] == '--child-gyp':
+ return child_gyp()
+
+ unittest.main()
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/isolate/trace_inputs_test.py b/tools/isolate/trace_inputs_test.py
index 0ef644c..3830b79 100755
--- a/tools/isolate/trace_inputs_test.py
+++ b/tools/isolate/trace_inputs_test.py
@@ -3,153 +3,73 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import logging
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
+import cStringIO
import unittest
-ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
-
-VERBOSE = False
-
-
-class CalledProcessError(subprocess.CalledProcessError):
- """Makes 2.6 version act like 2.7"""
- def __init__(self, returncode, cmd, output, cwd):
- super(CalledProcessError, self).__init__(returncode, cmd)
- self.output = output
- self.cwd = cwd
-
- def __str__(self):
- return super(CalledProcessError, self).__str__() + (
- '\n'
- 'cwd=%s\n%s') % (self.cwd, self.output)
+import trace_inputs
class TraceInputs(unittest.TestCase):
- def setUp(self):
- self.tempdir = tempfile.mkdtemp()
- self.log = os.path.join(self.tempdir, 'log')
- os.chdir(ROOT_DIR)
-
- def tearDown(self):
- shutil.rmtree(self.tempdir)
-
- def _execute(self, is_gyp):
- cmd = [
- sys.executable, os.path.join('..', 'trace_inputs.py'),
- '--log', self.log,
- '--root-dir', ROOT_DIR,
- ]
- if is_gyp:
- cmd.extend(
- [
- '--cwd', 'data',
- '--product', '.', # Not tested.
- ])
- cmd.append(os.path.join('..', 'trace_inputs_test.py'))
- if is_gyp:
- # When the gyp argument is not specified, the command is started from
- # --root-dir directory.
- cmd.append('--child-gyp')
- else:
- # When the gyp argument is specified, the command is started from --cwd
- # directory.
- cmd.append('--child')
-
- cwd = 'data'
- p = subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd)
- out = p.communicate()[0]
- if p.returncode:
- raise CalledProcessError(p.returncode, cmd, out, cwd)
- return out
-
- def test_trace(self):
- if sys.platform not in ('linux2', 'darwin'):
- print 'WARNING: unsupported: %s' % sys.platform
- return
- expected_end = [
- "Interesting: 4 reduced to 3",
- " data/trace_inputs/",
- " trace_inputs.py",
- " trace_inputs_test.py",
- ]
- actual = self._execute(False).splitlines()
- self.assertTrue(actual[0].startswith('Tracing... ['))
- self.assertTrue(actual[1].startswith('Loading traces... '))
- self.assertTrue(actual[2].startswith('Total: '))
- 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):])
-
- def test_trace_gyp(self):
- if sys.platform not in ('linux2', 'darwin'):
- print 'WARNING: unsupported: %s' % sys.platform
- return
+ 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': {
+ 'isolate': [
+ 'dir1',
+ 'dir2',
+ ],
+ },
+ }],
+ ['OS=\"bar\"', {
+ 'variables': {},
+ }, {
+ 'variables': {},
+ }],
+ ],
+ }
expected = (
"{\n"
" 'variables': {\n"
- " 'isolate_files': [\n"
- " '<(DEPTH)/trace_inputs.py',\n"
- " '<(DEPTH)/trace_inputs_test.py',\n"
- " ],\n"
- " 'isolate_dirs': [\n"
- " 'trace_inputs/',\n"
+ " 'bar': [\n"
+ " 'file1',\n"
+ " 'file2',\n"
" ],\n"
" },\n"
- "},\n")
- actual = self._execute(True)
- self.assertEquals(expected, actual)
-
-
-def child():
- """When the gyp argument is not specified, the command is started from
- --root-dir directory.
- """
- print 'child'
- # Force file opening with a non-normalized path.
- open(os.path.join('data', '..', 'trace_inputs.py'), 'rb').close()
- # Do not wait for the child to exit.
- # Use relative directory.
- subprocess.Popen(
- ['python', 'child2.py'], cwd=os.path.join('data', 'trace_inputs'))
- return 0
-
-
-def child_gyp():
- """When the gyp argument is specified, the command is started from --cwd
- directory.
- """
- print 'child_gyp'
- # Force file opening.
- open(os.path.join('..', 'trace_inputs.py'), 'rb').close()
- # Do not wait for the child to exit.
- # Use relative directory.
- subprocess.Popen(['python', 'child2.py'], cwd='trace_inputs')
- return 0
-
-
-def main():
- global VERBOSE
- VERBOSE = '-v' in sys.argv
- level = logging.DEBUG if VERBOSE else logging.ERROR
- logging.basicConfig(level=level)
- if len(sys.argv) == 1:
- unittest.main()
-
- if sys.argv[1] == '--child':
- return child()
- if sys.argv[1] == '--child-gyp':
- return child_gyp()
-
- unittest.main()
+ " 'conditions': [\n"
+ " ['OS=\"foo\"', {\n"
+ " 'variables': {\n"
+ " 'isolate': [\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__':
- sys.exit(main())
+ unittest.main()