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 """Runs through isolate_test_cases.py ALL the tests cases in a google-test
7 executable, grabs the failures and traces them to generate a new .isolate.
9 This scripts requires a .isolated file. This file is generated from a .isolate
10 file. You can use 'GYP_DEFINES=test_isolation_mode=check ninja foo_test_run' to
13 If you want to trace a single test case, you want isolate_test_cases.py.
23 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24 if not ROOT_DIR in sys.path:
25 sys.path.insert(0, ROOT_DIR)
28 from googletest import isolate_test_cases
29 from googletest import run_test_cases
30 from utils import tools
33 def with_tempfile(function):
34 """Creates a temporary file and calls the inner function."""
35 def hook(*args, **kwargs):
36 handle, tempfilepath = tempfile.mkstemp(prefix='fix_test_cases')
39 return function(tempfilepath, *args, **kwargs)
42 os.remove(tempfilepath)
44 print >> sys.stderr, 'Failed to remove %s: %s' % (tempfilepath, e)
48 def load_run_test_cases_results(run_test_cases_file):
49 """Loads a .run_test_cases result file.
51 Returns a tuple of two lists, (success, failures).
53 if not os.path.isfile(run_test_cases_file):
54 print >> sys.stderr, 'Failed to find %s' % run_test_cases_file
56 with open(run_test_cases_file) as f:
59 except ValueError as e:
60 print >> sys.stderr, ('Unable to load json file, %s: %s' %
61 (run_test_cases_file, str(e)))
64 test for test, runs in data['test_cases'].iteritems()
65 if not any(run['returncode'] == 0 for run in runs)
68 test for test, runs in data['test_cases'].iteritems()
69 if any(run['returncode'] == 0 for run in runs)
71 return success, failure
74 def add_verbosity(cmd, verbosity):
75 """Adds --verbose flags to |cmd| depending on verbosity."""
77 cmd.append('--verbose')
79 cmd.append('--verbose')
82 # This function requires 2 temporary files.
86 tempfilepath_cases, tempfilepath_result, isolated, test_cases, verbosity):
87 """Runs all the test cases in an isolated environment."""
88 with open(tempfilepath_cases, 'w') as f:
89 f.write('\n'.join(test_cases))
91 sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
93 '--isolated', isolated,
95 # Make sure isolate.py is verbose.
96 add_verbosity(cmd, verbosity)
99 # This assumes run_test_cases.py is used.
100 '--result', tempfilepath_result,
101 '--test-case-file', tempfilepath_cases,
102 # Do not retry; it's faster to trace flaky test than retrying each failing
104 # Do not use --run-all, iterate multiple times instead.
106 # Trace at most 25 test cases at a time. While this may seem particularly
107 # small, it is because the tracer doesn't scale well on Windows and tends to
108 # generate multi-gigabytes data files, that needs to be read N-times the
109 # number of test cases. On linux and OSX it's not that much a big deal but
110 # if there's 25 test cases that are broken, it's likely that there's a core
111 # file missing anyway.
112 '--max-failures', '25',
114 # Make sure run_test_cases.py is verbose.
115 add_verbosity(cmd, verbosity)
117 retcode = subprocess.call(cmd)
118 success, failures = load_run_test_cases_results(tempfilepath_result)
119 # Returning non-zero must match having failures.
120 assert bool(retcode) == (failures is None or bool(failures))
121 return success, failures
125 def trace_some(tempfilepath, isolated, test_cases, trace_blacklist, verbosity):
126 """Traces the test cases."""
127 with open(tempfilepath, 'w') as f:
128 f.write('\n'.join(test_cases))
130 sys.executable, os.path.join(
131 ROOT_DIR, 'googletest', 'isolate_test_cases.py'),
132 '--isolated', isolated,
133 '--test-case-file', tempfilepath,
134 # Do not use --run-all here, we assume the test cases will pass inside the
137 for i in trace_blacklist:
138 cmd.extend(('--trace-blacklist', i))
139 add_verbosity(cmd, verbosity)
141 return subprocess.call(cmd)
144 def fix_all(isolated, all_test_cases, trace_blacklist, verbosity):
145 """Runs all the test cases in a gtest executable and trace the failing tests.
147 Returns True on success.
149 Makes sure the test passes afterward.
151 # These environment variables could have adverse side-effects.
152 # TODO(maruel): Be more intelligent about it, for now be safe.
153 env_blacklist = set(run_test_cases.KNOWN_GTEST_ENV_VARS) - set([
154 'GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS'])
155 for i in env_blacklist:
157 print >> sys.stderr, 'Please unset %s' % i
160 # Run until test cases remain to be tested.
161 remaining_test_cases = all_test_cases[:]
162 if not remaining_test_cases:
163 print >> sys.stderr, 'Didn\'t find any test case to run'
166 previous_failures = set()
168 while remaining_test_cases:
169 # pylint is confused about with_tempfile.
170 # pylint: disable=E1120
172 '\nTotal: %5d; Remaining: %5d' % (
173 len(all_test_cases), len(remaining_test_cases)))
174 success, failures = run_tests(isolated, remaining_test_cases, verbosity)
177 print >> sys.stderr, 'Failed to run test cases'
179 # Maybe there's even enough things mapped to start the child process.
180 logging.info('Failed to run, trace one test case.')
183 failures = [remaining_test_cases[0]]
185 '\nTotal: %5d; Tried to run: %5d; Ran: %5d; Succeeded: %5d; Failed: %5d'
188 len(remaining_test_cases),
189 len(success) + len(failures),
194 print('I\'m done. Have a nice day!')
197 previous_failures.difference_update(success)
198 # If all the failures had already failed at least once.
199 if previous_failures.issuperset(failures):
200 print('The last trace didn\'t help, aborting.')
203 # Test cases that passed to not need to be retried anymore.
204 remaining_test_cases = [
205 i for i in remaining_test_cases if i not in success and i not in failures
207 # Make sure the failures at put at the end. This way if some tests fails
208 # simply because they are broken, and not because of test isolation, the
209 # other tests will still be traced.
210 remaining_test_cases.extend(failures)
212 # Trace the test cases and update the .isolate file.
213 print('\nTracing the %d failing tests.' % len(failures))
214 if trace_some(isolated, failures, trace_blacklist, verbosity):
215 logging.info('The tracing itself failed.')
217 previous_failures.update(failures)
221 tools.disable_buffering()
222 parser = run_test_cases.OptionParserTestCases(
223 usage='%prog <options> -s <something.isolated>')
224 isolate.add_trace_option(parser)
227 help='The isolated file')
228 options, args = parser.parse_args()
231 parser.error('Unsupported arg: %s' % args)
232 isolate.parse_isolated_option(parser, options, os.getcwd(), True)
234 _, command, test_cases = isolate_test_cases.safely_load_isolated(
237 parser.error('A command must be defined')
239 parser.error('No test case to run')
241 options.isolated, test_cases, options.trace_blacklist, options.verbose)
244 if __name__ == '__main__':