Upstream version 10.38.222.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / googletest / fix_test_cases.py
1 #!/usr/bin/env python
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.
5
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.
8
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
11 generate it.
12
13 If you want to trace a single test case, you want isolate_test_cases.py.
14 """
15
16 import json
17 import logging
18 import os
19 import subprocess
20 import sys
21 import tempfile
22
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)
26
27 import isolate
28 from googletest import isolate_test_cases
29 from googletest import run_test_cases
30 from utils import tools
31
32
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')
37     os.close(handle)
38     try:
39       return function(tempfilepath, *args, **kwargs)
40     finally:
41       try:
42         os.remove(tempfilepath)
43       except OSError, e:
44         print >> sys.stderr, 'Failed to remove %s: %s' % (tempfilepath, e)
45   return hook
46
47
48 def load_run_test_cases_results(run_test_cases_file):
49   """Loads a .run_test_cases result file.
50
51   Returns a tuple of two lists, (success, failures).
52   """
53   if not os.path.isfile(run_test_cases_file):
54     print >> sys.stderr, 'Failed to find %s' % run_test_cases_file
55     return None, None
56   with open(run_test_cases_file) as f:
57     try:
58       data = json.load(f)
59     except ValueError as e:
60       print >> sys.stderr, ('Unable to load json file, %s: %s' %
61                             (run_test_cases_file, str(e)))
62       return None, None
63   failure = [
64     test for test, runs in data['test_cases'].iteritems()
65     if not any(run['returncode'] == 0 for run in runs)
66   ]
67   success = [
68     test for test, runs in data['test_cases'].iteritems()
69     if any(run['returncode'] == 0 for run in runs)
70   ]
71   return success, failure
72
73
74 def add_verbosity(cmd, verbosity):
75   """Adds --verbose flags to |cmd| depending on verbosity."""
76   if verbosity:
77     cmd.append('--verbose')
78   if verbosity > 1:
79     cmd.append('--verbose')
80
81
82 # This function requires 2 temporary files.
83 @with_tempfile
84 @with_tempfile
85 def run_tests(
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))
90   cmd = [
91     sys.executable, os.path.join(ROOT_DIR, 'isolate.py'),
92     'run',
93     '--isolated', isolated,
94   ]
95   # Make sure isolate.py is verbose.
96   add_verbosity(cmd, verbosity)
97   cmd += [
98     '--',
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
103     # tests 3 times.
104     # Do not use --run-all, iterate multiple times instead.
105     '--retries', '0',
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',
113   ]
114   # Make sure run_test_cases.py is verbose.
115   add_verbosity(cmd, verbosity)
116   logging.debug(cmd)
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
122
123
124 @with_tempfile
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))
129   cmd = [
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
135     # checkout.
136   ]
137   for i in trace_blacklist:
138     cmd.extend(('--trace-blacklist', i))
139   add_verbosity(cmd, verbosity)
140   logging.debug(cmd)
141   return subprocess.call(cmd)
142
143
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.
146
147   Returns True on success.
148
149   Makes sure the test passes afterward.
150   """
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:
156     if i in os.environ:
157       print >> sys.stderr, 'Please unset %s' % i
158       return False
159
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'
164     return 1
165
166   previous_failures = set()
167   had_failure = False
168   while remaining_test_cases:
169     # pylint is confused about with_tempfile.
170     # pylint: disable=E1120
171     print(
172         '\nTotal: %5d; Remaining: %5d' % (
173           len(all_test_cases), len(remaining_test_cases)))
174     success, failures = run_tests(isolated, remaining_test_cases, verbosity)
175     if success is None:
176       if had_failure:
177         print >> sys.stderr, 'Failed to run test cases'
178         return 1
179       # Maybe there's even enough things mapped to start the child process.
180       logging.info('Failed to run, trace one test case.')
181       had_failure = True
182       success = []
183       failures = [remaining_test_cases[0]]
184     print(
185         '\nTotal: %5d; Tried to run: %5d; Ran: %5d; Succeeded: %5d; Failed: %5d'
186         % (
187           len(all_test_cases),
188           len(remaining_test_cases),
189           len(success) + len(failures),
190           len(success),
191           len(failures)))
192
193     if not failures:
194       print('I\'m done. Have a nice day!')
195       return True
196
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.')
201       return False
202
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
206     ]
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)
211
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.')
216       return False
217     previous_failures.update(failures)
218
219
220 def main():
221   tools.disable_buffering()
222   parser = run_test_cases.OptionParserTestCases(
223       usage='%prog <options> -s <something.isolated>')
224   isolate.add_trace_option(parser)
225   parser.add_option(
226       '-s', '--isolated',
227       help='The isolated file')
228   options, args = parser.parse_args()
229
230   if args:
231     parser.error('Unsupported arg: %s' % args)
232   isolate.parse_isolated_option(parser, options, os.getcwd(), True)
233
234   _, command, test_cases = isolate_test_cases.safely_load_isolated(
235       parser, options)
236   if not command:
237     parser.error('A command must be defined')
238   if not test_cases:
239     parser.error('No test case to run')
240   return not fix_all(
241       options.isolated, test_cases, options.trace_blacklist, options.verbose)
242
243
244 if __name__ == '__main__':
245   sys.exit(main())