| Index: tools/isolate/trace_test_cases.py
|
| diff --git a/tools/isolate/trace_test_cases.py b/tools/isolate/trace_test_cases.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..41abb9b188768269dfba0cd5277204f485b2804d
|
| --- /dev/null
|
| +++ b/tools/isolate/trace_test_cases.py
|
| @@ -0,0 +1,209 @@
|
| +#!/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.
|
| +
|
| +"""A manual version of trace_inputs.py that is specialized in tracing each
|
| +google-test test case individually.
|
| +
|
| +This is mainly written to work around bugs in strace w.r.t. browser_tests.
|
| +"""
|
| +
|
| +import fnmatch
|
| +import json
|
| +import multiprocessing
|
| +import optparse
|
| +import os
|
| +import sys
|
| +import tempfile
|
| +import time
|
| +
|
| +import list_test_cases
|
| +import trace_inputs
|
| +
|
| +BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| +ROOT_DIR = os.path.dirname(os.path.dirname(BASE_DIR))
|
| +
|
| +
|
| +def trace_test_case(
|
| + test_case, executable, root_dir, cwd_dir, product_dir, leak):
|
| + """Traces a single test case and returns the .isolate compatible variable
|
| + dict.
|
| + """
|
| + # Resolve any symlink
|
| + root_dir = os.path.realpath(root_dir)
|
| +
|
| + api = trace_inputs.get_api()
|
| + cmd = [executable, '--gtest_filter=%s' % test_case]
|
| +
|
| + if not leak:
|
| + f, logname = tempfile.mkstemp(prefix='trace')
|
| + os.close(f)
|
| + else:
|
| + logname = '%s.%s.log' % (executable, test_case.replace('/', '-'))
|
| + f = None
|
| +
|
| + try:
|
| + for i in range(3):
|
| + start = time.time()
|
| + returncode, output = trace_inputs.trace(
|
| + logname, cmd, os.path.join(root_dir, cwd_dir), api, True)
|
| + if returncode and i != 2:
|
| + print '\nFailed while running: %s' % ' '.join(cmd)
|
| + print 'Trying again!'
|
| + continue
|
| + duration = time.time() - start
|
| + try:
|
| + _, _, _, _, simplified = trace_inputs.load_trace(logname, root_dir, api)
|
| + break
|
| + except Exception:
|
| + print '\nFailed loading the trace for: %s' % ' '.join(cmd)
|
| + print 'Trying again!'
|
| +
|
| + variables = trace_inputs.generate_dict(simplified, cwd_dir, product_dir)
|
| + return {
|
| + 'case': test_case,
|
| + 'variables': variables,
|
| + 'result': returncode,
|
| + 'duration': duration,
|
| + 'output': output,
|
| + }
|
| + finally:
|
| + if f:
|
| + os.remove(logname)
|
| +
|
| +
|
| +def task(args):
|
| + """Adaptor for multiprocessing.Pool().imap_unordered().
|
| +
|
| + It is executed asynchronously.
|
| + """
|
| + return trace_test_case(*args)
|
| +
|
| +
|
| +def get_test_cases(executable, skip):
|
| + """Returns the filtered list of test cases.
|
| +
|
| + This is done synchronously.
|
| + """
|
| + try:
|
| + out = list_test_cases.gtest_list_tests(executable)
|
| + except list_test_cases.Failure, e:
|
| + print e.args[0]
|
| + return None
|
| +
|
| + tests = list_test_cases.parse_gtest_cases(out)
|
| + tests = [t for t in tests if not any(fnmatch.fnmatch(t, s) for s in skip)]
|
| + print 'Found %d test cases in %s' % (len(tests), os.path.basename(executable))
|
| + return tests
|
| +
|
| +
|
| +def trace_test_cases(
|
| + executable, root_dir, cwd_dir, product_dir, leak, skip, jobs, timeout):
|
| + """Traces test cases one by one."""
|
| + tests = get_test_cases(executable, skip)
|
| + if not tests:
|
| + return
|
| +
|
| + last_line = ''
|
| + out = {}
|
| + index = 0
|
| + pool = multiprocessing.Pool(processes=jobs)
|
| + start = time.time()
|
| + try:
|
| + g = ((t, executable, root_dir, cwd_dir, product_dir, leak) for t in tests)
|
| + it = pool.imap_unordered(task, g)
|
| + while True:
|
| + try:
|
| + result = it.next(timeout=timeout)
|
| + except StopIteration:
|
| + break
|
| + case = result.pop('case')
|
| + index += 1
|
| + line = '%d of %d (%.1f%%), %.1fs: %s' % (
|
| + index,
|
| + len(tests),
|
| + index * 100. / len(tests),
|
| + time.time() - start,
|
| + case)
|
| + sys.stdout.write(
|
| + '\r%s%s' % (line, ' ' * max(0, len(last_line) - len(line))))
|
| + sys.stdout.flush()
|
| + last_line = line
|
| + # TODO(maruel): Retry failed tests.
|
| + out[case] = result
|
| + return 0
|
| + except multiprocessing.TimeoutError, e:
|
| + print 'Got a timeout while processing a task item %s' % e
|
| + # Be sure to stop the pool on exception.
|
| + pool.terminate()
|
| + return 1
|
| + except Exception, e:
|
| + # Be sure to stop the pool on exception.
|
| + pool.terminate()
|
| + raise
|
| + finally:
|
| + with open('%s.test_cases' % executable, 'w') as f:
|
| + json.dump(out, f, indent=2, sort_keys=True)
|
| + pool.close()
|
| + pool.join()
|
| +
|
| +
|
| +def main():
|
| + """CLI frontend to validate arguments."""
|
| + parser = optparse.OptionParser(
|
| + usage='%prog <options> [gtest]')
|
| + parser.allow_interspersed_args = False
|
| + parser.add_option(
|
| + '-c', '--cwd',
|
| + default='chrome',
|
| + 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\'')
|
| + parser.add_option(
|
| + '-p', '--product-dir',
|
| + default='out/Release',
|
| + help='Directory for PRODUCT_DIR. Default: %default')
|
| + parser.add_option(
|
| + '--root-dir',
|
| + default=ROOT_DIR,
|
| + help='Root directory to base everything off. Default: %default')
|
| + parser.add_option(
|
| + '-l', '--leak',
|
| + action='store_true',
|
| + help='Leak trace files')
|
| + parser.add_option(
|
| + '-s', '--skip',
|
| + default=[],
|
| + action='append',
|
| + help='filter to apply to test cases to skip, wildcard-style')
|
| + parser.add_option(
|
| + '-j', '--jobs',
|
| + type='int',
|
| + help='number of parallel jobs')
|
| + parser.add_option(
|
| + '-t', '--timeout',
|
| + default=120,
|
| + type='int',
|
| + help='number of parallel jobs')
|
| + options, args = parser.parse_args()
|
| +
|
| + if len(args) != 1:
|
| + parser.error(
|
| + 'Please provide the executable line to run, if you need fancy things '
|
| + 'like xvfb, start this script from inside xvfb, it\'ll be faster.')
|
| + executable = os.path.join(options.root_dir, options.product_dir, args[0])
|
| + return trace_test_cases(
|
| + executable,
|
| + options.root_dir,
|
| + options.cwd,
|
| + options.product_dir,
|
| + options.leak,
|
| + options.skip,
|
| + options.jobs,
|
| + options.timeout)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|