summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormaruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-03 16:21:36 +0000
committermaruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-03 16:21:36 +0000
commitc885046e2c6c813f934661b9e924cf6ab79291e6 (patch)
treeec64ddaa19afa2092f1955f68b5c6e7e4c1c51d2
parentb8e59ebf3e9238757778bb877a3ec8205eb2d7a9 (diff)
downloadchromium_src-c885046e2c6c813f934661b9e924cf6ab79291e6.zip
chromium_src-c885046e2c6c813f934661b9e924cf6ab79291e6.tar.gz
chromium_src-c885046e2c6c813f934661b9e924cf6ab79291e6.tar.bz2
Add functionality to only specify the hash of the result file (manifest) to load
This removes the need to copy the manifest around. Improve --help output by categorizing the options. Add smoke test for run_test_from_archive.py TBR=cmp@chromium.org NOTRY=true BUG= TEST= Review URL: https://chromiumcodereview.appspot.com/10701004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@145308 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xtools/isolate/data/run_test_from_archive/gtest_fake.py84
-rwxr-xr-xtools/isolate/run_test_from_archive.py55
-rwxr-xr-xtools/isolate/run_test_from_archive_smoke_test.py159
3 files changed, 284 insertions, 14 deletions
diff --git a/tools/isolate/data/run_test_from_archive/gtest_fake.py b/tools/isolate/data/run_test_from_archive/gtest_fake.py
new file mode 100755
index 0000000..e9c3560
--- /dev/null
+++ b/tools/isolate/data/run_test_from_archive/gtest_fake.py
@@ -0,0 +1,84 @@
+#!/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.
+
+"""Simulate a google-test executable.
+
+http://code.google.com/p/googletest/
+"""
+
+import optparse
+import sys
+
+
+TESTS = {
+ 'Foo': ['Bar1', 'Bar2', 'Bar3'],
+ 'Baz': ['Fail'],
+}
+TOTAL = sum(len(v) for v in TESTS.itervalues())
+
+
+def get_test_output(test_name):
+ fixture, case = test_name.split('.', 1)
+ return (
+ '[==========] Running 1 test from 1 test case.\n'
+ '[----------] Global test environment set-up.\n'
+ '[----------] 1 test from %(fixture)s\n'
+ '[ RUN ] %(fixture)s.%(case)s\n'
+ '[ OK ] %(fixture)s.%(case)s (0 ms)\n'
+ '[----------] 1 test from %(fixture)s (0 ms total)\n'
+ '\n') % {
+ 'fixture': fixture,
+ 'case': case,
+ }
+
+
+def get_footer(number):
+ return (
+ '[----------] Global test environment tear-down\n'
+ '[==========] %(number)d test from %(total)d test case ran. (0 ms total)\n'
+ '[ PASSED ] %(number)d test.\n'
+ '\n'
+ ' YOU HAVE 5 DISABLED TESTS\n'
+ '\n'
+ ' YOU HAVE 2 tests with ignored failures (FAILS prefix)\n') % {
+ 'number': number,
+ 'total': TOTAL,
+ }
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('--gtest_list_tests', action='store_true')
+ parser.add_option('--gtest_filter')
+ options, args = parser.parse_args()
+ if args:
+ parser.error('Failed to process args %s' % args)
+
+ if options.gtest_list_tests:
+ for fixture, cases in TESTS.iteritems():
+ print '%s.' % fixture
+ for case in cases:
+ print ' ' + case
+ print ' YOU HAVE 2 tests with ignored failures (FAILS prefix)'
+ print ''
+ return 0
+
+ if options.gtest_filter:
+ # Simulate running one test.
+ print 'Note: Google Test filter = %s\n' % options.gtest_filter
+ print get_test_output(options.gtest_filter)
+ print get_footer(1)
+ # Make Baz.Fail fail.
+ return options.gtest_filter == 'Baz.Fail'
+
+ for fixture, cases in TESTS.iteritems():
+ for case in cases:
+ print get_test_output('%s.%s' % (fixture, case))
+ print get_footer(TOTAL)
+ return 6
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/isolate/run_test_from_archive.py b/tools/isolate/run_test_from_archive.py
index 7b5611b..002555b 100755
--- a/tools/isolate/run_test_from_archive.py
+++ b/tools/isolate/run_test_from_archive.py
@@ -13,6 +13,7 @@ import json
import logging
import optparse
import os
+import posixpath
import re
import shutil
import stat
@@ -345,21 +346,35 @@ def run_tha_test(manifest, cache_dir, remote, max_cache_size, min_free_space):
base_temp_dir = os.path.dirname(cache_dir)
outdir = tempfile.mkdtemp(prefix='run_tha_test', dir=base_temp_dir)
+ if not 'files' in manifest:
+ print >> sys.stderr, 'No file to map'
+ return 1
+ if not 'command' in manifest:
+ print >> sys.stderr, 'No command to map run'
+ return 1
+
try:
with Profiler('GetFiles') as _prof:
for filepath, properties in manifest['files'].iteritems():
- infile = properties['sha-1']
outfile = os.path.join(outdir, filepath)
- cache.retrieve(infile)
outfiledir = os.path.dirname(outfile)
if not os.path.isdir(outfiledir):
os.makedirs(outfiledir)
- link_file(outfile, cache.path(infile), HARDLINK)
+ if 'sha-1' in properties:
+ # A normal file.
+ infile = properties['sha-1']
+ cache.retrieve(infile)
+ link_file(outfile, cache.path(infile), HARDLINK)
+ elif 'link' in properties:
+ # A symlink.
+ os.symlink(properties['link'], outfile)
+ else:
+ raise ValueError('Unexpected entry: %s' % properties)
if 'mode' in properties:
# It's not set on Windows.
os.chmod(outfile, properties['mode'])
- cwd = os.path.join(outdir, manifest['relative_cwd'])
+ cwd = os.path.join(outdir, manifest.get('relative_cwd', ''))
if not os.path.isdir(cwd):
os.makedirs(cwd)
if manifest.get('read_only'):
@@ -383,32 +398,41 @@ def main():
parser = optparse.OptionParser(
usage='%prog <options>', description=sys.modules[__name__].__doc__)
parser.add_option(
- '-v', '--verbose', action='count', default=1, help='Use multiple times')
- parser.add_option(
+ '-v', '--verbose', action='count', default=0, help='Use multiple times')
+ parser.add_option('--no-run', action='store_true', help='Skip the run part')
+
+ group = optparse.OptionGroup(parser, 'Data source')
+ group.add_option(
'-m', '--manifest',
metavar='FILE',
help='File/url describing what to map or run')
- parser.add_option('--no-run', action='store_true', help='Skip the run part')
- parser.add_option(
+ group.add_option(
+ '-H', '--hash',
+ help='Hash of the manifest to grab from the hash table')
+ parser.add_option_group(group)
+
+ group.add_option(
+ '-r', '--remote', metavar='URL', help='Remote where to get the items')
+ group = optparse.OptionGroup(parser, 'Cache management')
+ group.add_option(
'--cache',
default='cache',
metavar='DIR',
help='Cache directory, default=%default')
- parser.add_option(
- '-r', '--remote', metavar='URL', help='Remote where to get the items')
- parser.add_option(
+ group.add_option(
'--max-cache-size',
type='int',
metavar='NNN',
default=20*1024*1024*1024,
help='Trim if the cache gets larger than this value, default=%default')
- parser.add_option(
+ group.add_option(
'--min-free-space',
type='int',
metavar='NNN',
default=1*1024*1024*1024,
help='Trim if disk free space becomes lower than this value, '
'default=%default')
+ parser.add_option_group(group)
options, args = parser.parse_args()
level = [logging.ERROR, logging.INFO, logging.DEBUG][min(2, options.verbose)]
@@ -416,13 +440,16 @@ def main():
level=level,
format='%(levelname)5s %(module)15s(%(lineno)3d): %(message)s')
- if not options.manifest:
- parser.error('--manifest is required.')
+ if bool(options.manifest) == bool(options.hash):
+ parser.error('One and only one of --manifest or --hash is required.')
if not options.remote:
parser.error('--remote is required.')
if args:
parser.error('Unsupported args %s' % ' '.join(args))
+ if options.hash:
+ # First calculate the reference to it.
+ options.manifest = posixpath.join(options.remote, options.hash)
manifest = json.load(open_remote(options.manifest))
return run_tha_test(
manifest, os.path.abspath(options.cache), options.remote,
diff --git a/tools/isolate/run_test_from_archive_smoke_test.py b/tools/isolate/run_test_from_archive_smoke_test.py
new file mode 100755
index 0000000..686249e
--- /dev/null
+++ b/tools/isolate/run_test_from_archive_smoke_test.py
@@ -0,0 +1,159 @@
+#!/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 hashlib
+import json
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+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, 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, _dirs, files in os.walk(directory):
+ actual.extend(os.path.join(root, f)[len(directory)+1:] for f in files)
+ return sorted(actual)
+
+
+def calc_sha1(filepath):
+ """Calculates the SHA-1 hash for a file."""
+ return hashlib.sha1(open(filepath, 'rb').read()).hexdigest()
+
+
+def write_content(filepath, content):
+ with open(filepath, 'wb') as f:
+ f.write(content)
+
+
+def write_json(filepath, data):
+ with open(filepath, 'wb') as f:
+ json.dump(data, f, sort_keys=True, indent=2)
+
+
+class RunTestFromArchive(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+ self.table = os.path.join(self.tempdir, 'table')
+ os.mkdir(self.table)
+ self.cache = os.path.join(self.tempdir, 'cache')
+
+ self.test = os.path.join(
+ ROOT_DIR, 'data', 'run_test_from_archive', 'gtest_fake.py')
+ self.test_sha1 = calc_sha1(self.test)
+
+ def tearDown(self):
+ logging.debug(self.tempdir)
+ shutil.rmtree(self.tempdir)
+
+ def _result_tree(self):
+ return list_files_tree(self.tempdir)
+
+ def _store_result(self, result_data):
+ """Stores a .results file in the hash table."""
+ result_text = json.dumps(result_data, sort_keys=True, indent=2)
+ result_sha1 = hashlib.sha1(result_text).hexdigest()
+ write_content(os.path.join(self.table, result_sha1), result_text)
+ return result_sha1
+
+ def test_result(self):
+ # Store the executable in the hash table.
+ shutil.copyfile(self.test, os.path.join(self.table, self.test_sha1))
+ result_data = {
+ 'files': {
+ 'gtest_fake.py': {
+ 'sha-1': self.test_sha1,
+ },
+ },
+ 'command': ['python', 'gtest_fake.py'],
+ }
+ result_file = os.path.join(self.tempdir, 'foo.results')
+ write_json(result_file, result_data)
+
+ cmd = [
+ sys.executable, os.path.join(ROOT_DIR, 'run_test_from_archive.py'),
+ '--manifest', result_file,
+ '--cache', self.cache,
+ '--remote', self.table,
+ ]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = proc.communicate()
+ self.assertEquals(1070, len(out))
+ self.assertEquals('', err)
+ self.assertEquals(6, proc.returncode)
+
+ def test_hash(self):
+ # Loads the manifest from the store as a hash, then run the child.
+ # Store the executable in the hash table.
+ shutil.copyfile(self.test, os.path.join(self.table, self.test_sha1))
+ # Store a .results file in the hash table. The file name is the content's
+ # sha1, so first generate the content.
+ result_data = {
+ 'files': {
+ 'gtest_fake.py': {
+ 'sha-1': self.test_sha1,
+ },
+ },
+ 'command': ['python', 'gtest_fake.py'],
+ }
+ result_sha1 = self._store_result(result_data)
+
+ cmd = [
+ sys.executable, os.path.join(ROOT_DIR, 'run_test_from_archive.py'),
+ '--hash', result_sha1,
+ '--cache', self.cache,
+ '--remote', self.table,
+ ]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = proc.communicate()
+ self.assertEquals(1070, len(out))
+ self.assertEquals('', err)
+ self.assertEquals(6, proc.returncode)
+
+ def test_fail_empty_manifest(self):
+ result_sha1 = self._store_result({})
+ cmd = [
+ sys.executable, os.path.join(ROOT_DIR, 'run_test_from_archive.py'),
+ '--hash', result_sha1,
+ '--cache', self.cache,
+ '--remote', self.table,
+ ]
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = proc.communicate()
+ self.assertEquals('', out)
+ self.assertEquals('No file to map\n', err)
+ self.assertEquals(1, proc.returncode)
+
+
+if __name__ == '__main__':
+ VERBOSE = '-v' in sys.argv
+ logging.basicConfig(level=logging.DEBUG if VERBOSE else logging.ERROR)
+ unittest.main()