2 # Copyright 2013 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 that
4 # can be found in the LICENSE file.
6 """Traces one, multiple or all test cases of a google-test executable
7 individually and generates or updates an .isolate file.
9 If the trace hangs up, you can still take advantage of the traces up to the
10 points it got to by Ctrl-C'ing out, then running isolate.py merge -r
11 out/release/foo_test.isolated. the reason it works is because both isolate.py
12 and isolate_test_cases.py uses the same filename for the trace by default.
20 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
21 if not ROOT_DIR in sys.path:
22 sys.path.insert(0, ROOT_DIR)
26 from googletest import run_test_cases
27 from googletest import trace_test_cases
29 from utils import tools
32 def isolate_test_cases(
33 cmd, test_cases, jobs, isolated_file, isolate_file,
34 root_dir, reldir, path_variables, config_variables, extra_variables,
36 assert os.path.isabs(root_dir) and os.path.isdir(root_dir), root_dir
38 logname = isolated_file + '.log'
39 basename = isolated_file.rsplit('.', 1)[0]
40 cwd_dir = os.path.join(root_dir, reldir)
41 # Do the actual tracing.
42 results = trace_test_cases.trace_test_cases(
43 cmd, cwd_dir, test_cases, jobs, logname)
44 api = trace_inputs.get_api()
45 blacklist = tools.gen_blacklist(trace_blacklist)
47 (i.pop('trace'), i) for i in api.parse_log(logname, blacklist, None))
55 log_dict = logs[item['tracename']]
56 if log_dict.get('exception'):
57 exception = exception or log_dict['exception']
58 logging.error('Got exception: %s', exception)
60 files = log_dict['results'].strip_root(root_dir).files
61 tracked, touched = isolate.isolate_format.split_touched(files)
62 value = isolate.generate_isolate(
72 # item['test_case'] could be an invalid file name.
73 out = basename + '.' + item['tracename'] + '.isolate'
74 with open(out, 'w') as f:
75 isolate.isolate_format.pretty_print(value, f)
78 # Merges back. Note that it is possible to blow up the command line
79 # argument length here but writing the files is still useful. Convert to
80 # importing the module instead if necessary.
83 os.path.join(ROOT_DIR, 'isolate_merge.py'),
87 merge_cmd.extend(inputs)
88 logging.info(merge_cmd)
89 proc = subprocess.Popen(merge_cmd)
91 return proc.returncode
94 raise exception[0], exception[1], exception[2]
97 def test_xvfb(command, rel_dir):
98 """Calls back ourself if not running inside Xvfb and it's on the command line
101 Otherwise the X session will die while trying to start too many Xvfb
104 if os.environ.get('_CHROMIUM_INSIDE_XVFB') == '1':
106 for index, item in enumerate(command):
107 if item.endswith('xvfb.py'):
108 # Note this has inside knowledge about src/testing/xvfb.py.
109 print('Restarting itself under Xvfb')
110 prefix = command[index:index+2]
111 prefix[0] = os.path.normpath(os.path.join(rel_dir, prefix[0]))
112 prefix[1] = os.path.normpath(os.path.join(rel_dir, prefix[1]))
113 cmd = tools.fix_python_path(prefix + sys.argv)
114 sys.exit(subprocess.call(cmd))
117 def safely_load_isolated(parser, options):
118 """Loads a .isolated.state to extract the executable information.
120 Returns the CompleteState instance, the command and the list of test cases.
122 config = isolate.CompleteState.load_files(options.isolated)
124 'root_dir: %s relative_cwd: %s isolated: %s',
125 config.root_dir, config.saved_state.relative_cwd, options.isolated)
126 reldir = os.path.join(config.root_dir, config.saved_state.relative_cwd)
127 command = config.saved_state.command
130 command = tools.fix_python_path(command)
131 test_xvfb(command, reldir)
132 test_cases = parser.process_gtest_options(command, reldir, options)
133 return config, command, test_cases
137 """CLI frontend to validate arguments."""
138 tools.disable_buffering()
139 parser = run_test_cases.OptionParserTestCases(
140 usage='%prog <options> --isolated <.isolated>')
141 parser.format_description = lambda *_: parser.description
142 isolate.add_variable_option(parser)
143 isolate.add_trace_option(parser)
145 # TODO(maruel): Add support for options.timeout.
146 parser.remove_option('--timeout')
148 options, args = parser.parse_args()
150 parser.error('Unsupported arg: %s' % args)
151 isolate.parse_isolated_option(parser, options, os.getcwd(), True)
152 isolate.parse_variable_option(options)
155 config, command, test_cases = safely_load_isolated(parser, options)
157 parser.error('A command must be defined')
159 parser.error('No test case to run with command: %s' % ' '.join(command))
161 config.saved_state.config_variables.update(options.config_variables)
162 config.saved_state.extra_variables.update(options.extra_variables)
163 config.saved_state.path_variables.update(options.path_variables)
164 return isolate_test_cases(
168 config.isolated_filepath,
169 config.saved_state.isolate_filepath,
171 config.saved_state.relative_cwd,
172 config.saved_state.path_variables,
173 config.saved_state.config_variables,
174 config.saved_state.extra_variables,
175 options.trace_blacklist)
176 except isolate.ExecutionError, e:
177 print >> sys.stderr, str(e)
181 if __name__ == '__main__':