diff options
author | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-03 16:21:36 +0000 |
---|---|---|
committer | maruel@chromium.org <maruel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-03 16:21:36 +0000 |
commit | c885046e2c6c813f934661b9e924cf6ab79291e6 (patch) | |
tree | ec64ddaa19afa2092f1955f68b5c6e7e4c1c51d2 | |
parent | b8e59ebf3e9238757778bb877a3ec8205eb2d7a9 (diff) | |
download | chromium_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-x | tools/isolate/data/run_test_from_archive/gtest_fake.py | 84 | ||||
-rwxr-xr-x | tools/isolate/run_test_from_archive.py | 55 | ||||
-rwxr-xr-x | tools/isolate/run_test_from_archive_smoke_test.py | 159 |
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() |