blob: 2e10c4eacea1ee3b0fa5419b82c789643e26f8f6 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2013 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.
"""Traces each test cases of a google-test executable individually and generates
or updates an .isolate file.
If the trace hangs up, you can still take advantage of the traces up to the
points it got to by Ctrl-C'ing out, then running isolate.py merge -r
out/release/foo_test.isolated. the reason it works is because both isolate.py
and isolate_test_cases.py uses the same filename for the trace by default.
"""
import logging
import os
import subprocess
import sys
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if not ROOT_DIR in sys.path:
sys.path.insert(0, ROOT_DIR)
import isolate
import run_test_cases
import trace_inputs
import trace_test_cases
from utils import tools
def isolate_test_cases(
cmd, test_cases, jobs, isolated_file, isolate_file,
root_dir, reldir, variables, trace_blacklist):
assert os.path.isabs(root_dir) and os.path.isdir(root_dir), root_dir
logname = isolated_file + '.log'
basename = isolated_file.rsplit('.', 1)[0]
cwd_dir = os.path.join(root_dir, reldir)
# Do the actual tracing.
results = trace_test_cases.trace_test_cases(
cmd, cwd_dir, test_cases, jobs, logname)
api = trace_inputs.get_api()
blacklist = trace_inputs.gen_blacklist(trace_blacklist)
logs = dict(
(i.pop('trace'), i) for i in api.parse_log(logname, blacklist, None))
exception = None
try:
inputs = []
for items in results:
item = items[-1]
assert item['valid']
# Load the results;
log_dict = logs[item['tracename']]
if log_dict.get('exception'):
exception = exception or log_dict['exception']
logging.error('Got exception: %s', exception)
continue
files = log_dict['results'].strip_root(root_dir).files
tracked, touched = isolate.split_touched(files)
value = isolate.generate_isolate(
tracked,
[],
touched,
root_dir,
variables,
reldir,
blacklist)
# item['test_case'] could be an invalid file name.
out = basename + '.' + item['tracename'] + '.isolate'
with open(out, 'w') as f:
isolate.pretty_print(value, f)
inputs.append(out)
# Merges back. Note that it is possible to blow up the command line
# argument length here but writing the files is still useful. Convert to
# importing the module instead if necessary.
merge_cmd = [
sys.executable,
os.path.join(ROOT_DIR, 'isolate_merge.py'),
isolate_file,
'-o', isolate_file,
]
merge_cmd.extend(inputs)
logging.info(merge_cmd)
proc = subprocess.Popen(merge_cmd)
proc.communicate()
return proc.returncode
finally:
if exception:
raise exception[0], exception[1], exception[2]
def test_xvfb(command, rel_dir):
"""Calls back ourself if not running inside Xvfb and it's on the command line
to run.
Otherwise the X session will die while trying to start too many Xvfb
instances.
"""
if os.environ.get('_CHROMIUM_INSIDE_XVFB') == '1':
return
for index, item in enumerate(command):
if item.endswith('xvfb.py'):
# Note this has inside knowledge about src/testing/xvfb.py.
print('Restarting itself under Xvfb')
prefix = command[index:index+2]
prefix[0] = os.path.normpath(os.path.join(rel_dir, prefix[0]))
prefix[1] = os.path.normpath(os.path.join(rel_dir, prefix[1]))
cmd = tools.fix_python_path(prefix + sys.argv)
sys.exit(subprocess.call(cmd))
def safely_load_isolated(parser, options):
"""Loads a .isolated.state to extract the executable information.
Returns the CompleteState instance, the command and the list of test cases.
"""
config = isolate.CompleteState.load_files(options.isolated)
logging.debug(
'root_dir: %s relative_cwd: %s isolated: %s',
config.root_dir, config.saved_state.relative_cwd, options.isolated)
reldir = os.path.join(config.root_dir, config.saved_state.relative_cwd)
command = config.saved_state.command
test_cases = []
if command:
command = tools.fix_python_path(command)
test_xvfb(command, reldir)
test_cases = parser.process_gtest_options(command, reldir, options)
return config, command, test_cases
def main():
"""CLI frontend to validate arguments."""
tools.disable_buffering()
parser = run_test_cases.OptionParserTestCases(
usage='%prog <options> --isolated <.isolated>')
parser.format_description = lambda *_: parser.description
isolate.add_variable_option(parser)
isolate.add_trace_option(parser)
# TODO(maruel): Add support for options.timeout.
parser.remove_option('--timeout')
options, args = parser.parse_args()
if args:
parser.error('Unsupported arg: %s' % args)
isolate.parse_isolated_option(parser, options, os.getcwd(), True)
isolate.parse_variable_option(options)
try:
config, command, test_cases = safely_load_isolated(parser, options)
if not command:
parser.error('A command must be defined')
if not test_cases:
parser.error('No test case to run with command: %s' % ' '.join(command))
config.saved_state.variables.update(options.variables)
return isolate_test_cases(
command,
test_cases,
options.jobs,
config.isolated_filepath,
config.saved_state.isolate_filepath,
config.root_dir,
config.saved_state.relative_cwd,
config.saved_state.variables,
options.trace_blacklist)
except isolate.ExecutionError, e:
print >> sys.stderr, str(e)
return 1
if __name__ == '__main__':
sys.exit(main())