summaryrefslogtreecommitdiffstats
path: root/tools/isolate/isolate.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/isolate/isolate.py')
-rwxr-xr-xtools/isolate/isolate.py178
1 files changed, 113 insertions, 65 deletions
diff --git a/tools/isolate/isolate.py b/tools/isolate/isolate.py
index be4f076..d70296a 100755
--- a/tools/isolate/isolate.py
+++ b/tools/isolate/isolate.py
@@ -4,14 +4,21 @@
# found in the LICENSE file.
"""Does one of the following depending on the --mode argument:
- check verify all the inputs exist, touches the file specified with
- --result and exits.
- run recreates a tree with all the inputs files and run the executable
- in it.
- remap stores all the inputs files in a directory without running the
- executable.
+ check verify all the inputs exist, touches the file specified with
+ --result and exits.
+ hashtable puts a manifest file and hard links each of the inputs into the
+ output directory.
+ remap stores all the inputs files in a directory without running the
+ executable.
+ run recreates a tree with all the inputs files and run the executable
+ in it.
+
+See more information at
+http://dev.chromium.org/developers/testing/isolated-testing
"""
+import hashlib
+import json
import logging
import optparse
import os
@@ -24,6 +31,9 @@ import time
import tree_creator
+# Needs to be coherent with the file's docstring above.
+VALID_MODES = ('check', 'hashtable', 'remap', 'run')
+
def touch(filename):
"""Implements the equivalent of the 'touch' command."""
@@ -48,75 +58,104 @@ def rmtree(root):
shutil.rmtree(root)
-def isolate(outdir, root, resultfile, mode, read_only, args):
- """Main function to isolate a target with its dependencies."""
+def relpath(path, root):
+ """os.path.relpath() that keeps trailing slash."""
+ out = os.path.relpath(path, root)
+ if path.endswith('/'):
+ out += '/'
+ return out
+
+
+def separate_inputs_command(args, root):
+ """Strips off the command line from the inputs.
+
+ gyp provides input paths relative to cwd. Convert them to be relative to root.
+ """
cmd = []
if '--' in args:
- # Strip off the command line from the inputs.
i = args.index('--')
cmd = args[i+1:]
args = args[:i]
-
- # gyp provides paths relative to cwd. Convert them to be relative to
- # root.
cwd = os.getcwd()
+ return [relpath(os.path.join(cwd, arg), root) for arg in args], cmd
- def make_relpath(i):
- """Makes an input file a relative path but keeps any trailing slash."""
- out = os.path.relpath(os.path.join(cwd, i), root)
- if i.endswith('/'):
- out += '/'
- return out
- infiles = [make_relpath(i) for i in args]
+def isolate(outdir, resultfile, indir, infiles, mode, read_only, cmd):
+ """Main function to isolate a target with its dependencies.
- if not infiles:
- raise ValueError('Need at least one input file to map')
+ It's behavior depends on |mode|.
+ """
+ if mode == 'run':
+ return run(outdir, resultfile, indir, infiles, read_only, cmd)
+
+ if mode == 'hashtable':
+ return hashtable(outdir, resultfile, indir, infiles)
+
+ assert mode in ('check', 'remap'), mode
+ if mode == 'remap':
+ if not outdir:
+ outdir = tempfile.mkdtemp(prefix='isolate')
+ tree_creator.recreate_tree(
+ outdir, indir, infiles, tree_creator.HARDLINK)
+ if read_only:
+ tree_creator.make_writable(outdir, True)
- # Other modes ignore the command.
- if mode == 'run' and not cmd:
+ if resultfile:
+ # Signal the build tool that the test succeeded.
+ with open(resultfile, 'wb') as f:
+ for infile in infiles:
+ f.write(infile.encode('utf-8'))
+ f.write('\n')
+
+
+def run(outdir, resultfile, indir, infiles, read_only, cmd):
+ """Implements the 'run' mode."""
+ if not cmd:
print >> sys.stderr, 'Using first input %s as executable' % infiles[0]
cmd = [infiles[0]]
-
- tempdir = None
+ outdir = None
try:
- if not outdir and mode != 'check':
- tempdir = tempfile.mkdtemp(prefix='isolate')
- outdir = tempdir
- elif outdir:
- outdir = os.path.abspath(outdir)
-
+ outdir = tempfile.mkdtemp(prefix='isolate')
tree_creator.recreate_tree(
- outdir,
- os.path.abspath(root),
- infiles,
- tree_creator.DRY_RUN if mode == 'check' else tree_creator.HARDLINK,
- lambda x: re.match(r'.*\.(svn|pyc)$', x))
-
- if mode != 'check' and read_only:
+ outdir, indir, infiles, tree_creator.HARDLINK)
+ if read_only:
tree_creator.make_writable(outdir, True)
- if mode in ('check', 'remap'):
- result = 0
- else:
- # TODO(maruel): Remove me. Layering violation. Used by
- # base/base_paths_linux.cc
- env = os.environ.copy()
- env['CR_SOURCE_ROOT'] = outdir.encode()
- # Rebase the command to the right path.
- cmd[0] = os.path.join(outdir, cmd[0])
- logging.info('Running %s' % cmd)
- result = subprocess.call(cmd, cwd=outdir, env=env)
-
+ # TODO(maruel): Remove me. Layering violation. Used by
+ # base/base_paths_linux.cc
+ env = os.environ.copy()
+ env['CR_SOURCE_ROOT'] = outdir.encode()
+ # Rebase the command to the right path.
+ cmd[0] = os.path.join(outdir, cmd[0])
+ logging.info('Running %s' % cmd)
+ result = subprocess.call(cmd, cwd=outdir, env=env)
if not result and resultfile:
# Signal the build tool that the test succeeded.
touch(resultfile)
return result
finally:
- if tempdir and mode == 'isolate':
- if read_only:
- tree_creator.make_writable(tempdir, False)
- rmtree(tempdir)
+ if read_only:
+ tree_creator.make_writable(outdir, False)
+ rmtree(outdir)
+
+
+def hashtable(outdir, resultfile, indir, infiles):
+ """Implements the 'hashtable' mode."""
+ results = {}
+ for relfile in infiles:
+ infile = os.path.join(indir, relfile)
+ h = hashlib.sha1()
+ with open(infile, 'rb') as f:
+ h.update(f.read())
+ digest = h.hexdigest()
+ outfile = os.path.join(outdir, digest)
+ tree_creator.process_file(outfile, infile, tree_creator.HARDLINK)
+ results[relfile] = {'sha1': digest}
+ json.dump(
+ {
+ 'files': results,
+ },
+ open(resultfile, 'wb'))
def main():
@@ -128,16 +167,17 @@ def main():
parser.add_option(
'-v', '--verbose', action='count', default=0, help='Use multiple times')
parser.add_option(
- '--mode', choices=['remap', 'check', 'run'],
- help='Determines the action to be taken')
+ '--mode', choices=VALID_MODES,
+ help='Determines the action to be taken: %s' % ', '.join(VALID_MODES))
parser.add_option(
- '--result', metavar='X',
+ '--result', metavar='FILE',
help='File to be touched when the command succeeds')
- parser.add_option('--root', help='Base directory to fetch files, required')
parser.add_option(
- '--outdir', metavar='X',
- help='Directory used to recreate the tree. Defaults to a /tmp '
- 'subdirectory')
+ '--root', metavar='DIR', help='Base directory to fetch files, required')
+ parser.add_option(
+ '--outdir', metavar='DIR',
+ help='Directory used to recreate the tree or store the hash table. '
+ 'Defaults to a /tmp subdirectory for modes run and remap.')
parser.add_option(
'--read-only', action='store_true',
help='Make the temporary tree read-only')
@@ -151,16 +191,24 @@ def main():
if not options.root:
parser.error('--root is required.')
+ infiles, cmd = separate_inputs_command(args, options.root)
+ if not infiles:
+ parser.error('Need at least one input file to map')
+ # Preprocess the input files.
try:
+ infiles, root = tree_creator.preprocess_inputs(
+ options.root, infiles, lambda x: re.match(r'.*\.(svn|pyc)$', x))
return isolate(
options.outdir,
- options.root,
options.result,
+ root,
+ infiles,
options.mode,
options.read_only,
- args)
- except ValueError, e:
- parser.error(str(e))
+ cmd)
+ except tree_creator.MappingError, e:
+ print >> sys.stderr, str(e)
+ return 1
if __name__ == '__main__':