| Index: fix_test_cases.py
|
| diff --git a/fix_test_cases.py b/fix_test_cases.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..98fcdbc905c41df018da2bec6ec18920a4486b55
|
| --- /dev/null
|
| +++ b/fix_test_cases.py
|
| @@ -0,0 +1,224 @@
|
| +#!/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.
|
| +
|
| +"""Runs a test, grab the failures and trace them."""
|
| +
|
| +import json
|
| +import os
|
| +import subprocess
|
| +import sys
|
| +import tempfile
|
| +
|
| +import run_test_cases
|
| +
|
| +
|
| +XVFB_PATH = os.path.join('..', '..', 'testing', 'xvfb.py')
|
| +
|
| +
|
| +if sys.platform == 'win32':
|
| + import msvcrt # pylint: disable=F0401
|
| +
|
| + def get_keyboard():
|
| + """Returns a letter from the keyboard if any.
|
| +
|
| + This function returns immediately.
|
| + """
|
| + if msvcrt.kbhit():
|
| + return ord(msvcrt.getch())
|
| +
|
| +else:
|
| + import select
|
| +
|
| + def get_keyboard():
|
| + """Returns a letter from the keyboard if any, as soon as he pressed enter.
|
| +
|
| + This function returns (almost) immediately.
|
| +
|
| + The library doesn't give a way to just get the initial letter.
|
| + """
|
| + if select.select([sys.stdin], [], [], 0.00001)[0]:
|
| + return sys.stdin.read(1)
|
| +
|
| +
|
| +def trace_and_merge(result, test):
|
| + """Traces a single test case and merges the result back into .isolate."""
|
| + env = os.environ.copy()
|
| + env['RUN_TEST_CASES_RUN_ALL'] = '1'
|
| +
|
| + print 'Starting trace of %s' % test
|
| + subprocess.call(
|
| + [
|
| + sys.executable, 'isolate.py', 'trace', '-r', result,
|
| + '--', '--gtest_filter=' + test,
|
| + ],
|
| + env=env)
|
| +
|
| + print 'Starting merge of %s' % test
|
| + return not subprocess.call(
|
| + [sys.executable, 'isolate.py', 'merge', '-r', result])
|
| +
|
| +
|
| +def run_all(result, shard_index, shard_count):
|
| + """Runs all the tests. Returns the tests that failed or None on failure.
|
| +
|
| + Assumes run_test_cases.py is implicitly called.
|
| + """
|
| + handle, result_file = tempfile.mkstemp(prefix='run_test_cases')
|
| + os.close(handle)
|
| + env = os.environ.copy()
|
| + env['RUN_TEST_CASES_RESULT_FILE'] = result_file
|
| + env['RUN_TEST_CASES_RUN_ALL'] = '1'
|
| + env['GTEST_SHARD_INDEX'] = str(shard_index)
|
| + env['GTEST_TOTAL_SHARDS'] = str(shard_count)
|
| + cmd = [sys.executable, 'isolate.py', 'run', '-r', result]
|
| + subprocess.call(cmd, env=env)
|
| + if not os.path.isfile(result_file):
|
| + print >> sys.stderr, 'Failed to find %s' % result_file
|
| + return None
|
| + with open(result_file) as f:
|
| + try:
|
| + data = json.load(f)
|
| + except ValueError as e:
|
| + print >> sys.stderr, ('Unable to load json file, %s: %s' %
|
| + (result_file, str(e)))
|
| + return None
|
| + os.remove(result_file)
|
| + return [
|
| + test for test, runs in data.iteritems()
|
| + if not any(not run['returncode'] for run in runs)
|
| + ]
|
| +
|
| +
|
| +def run(result, test):
|
| + """Runs a single test case in an isolated environment.
|
| +
|
| + Returns True if the test passed.
|
| + """
|
| + return not subprocess.call([
|
| + sys.executable, 'isolate.py', 'run', '-r', result,
|
| + '--', '--gtest_filter=' + test,
|
| + ])
|
| +
|
| +
|
| +def run_normally(executable, test):
|
| + return not subprocess.call([
|
| + sys.executable, XVFB_PATH, os.path.dirname(executable), executable,
|
| + '--gtest_filter=' + test])
|
| +
|
| +
|
| +def diff_and_commit(test):
|
| + """Prints the diff and commit."""
|
| + subprocess.call(['git', 'diff'])
|
| + subprocess.call(['git', 'commit', '-a', '-m', test])
|
| +
|
| +
|
| +def trace_and_verify(result, test):
|
| + """Traces a test case, updates .isolate and makes sure it passes afterward.
|
| +
|
| + Return None if the test was already passing, True on success.
|
| + """
|
| + trace_and_merge(result, test)
|
| + diff_and_commit(test)
|
| + print 'Verifying trace...'
|
| + return run(result, test)
|
| +
|
| +
|
| +def fix_all(result, shard_index, shard_count, executable):
|
| + """Runs all the test cases in a gtest executable and trace the failing tests.
|
| +
|
| + Returns True on success.
|
| +
|
| + Makes sure the test passes afterward.
|
| + """
|
| + # These could have adverse side-effects.
|
| + # TODO(maruel): Be more intelligent about it, for now be safe.
|
| + run_test_cases_env = ['RUN_TEST_CASES_RESULT_FILE', 'RUN_TEST_CASES_RUN_ALL']
|
| + for i in run_test_cases.KNOWN_GTEST_ENV_VARS + run_test_cases_env:
|
| + if i in os.environ:
|
| + print >> 'Please unset %s' % i
|
| + return False
|
| +
|
| + test_cases = run_all(result, shard_index, shard_count)
|
| + if test_cases is None:
|
| + return False
|
| +
|
| + print '\nFound %d broken test cases.' % len(test_cases)
|
| + if not test_cases:
|
| + return True
|
| +
|
| + failed_alone = []
|
| + failures = []
|
| + fixed_tests = []
|
| + try:
|
| + for index, test_case in enumerate(test_cases):
|
| + if get_keyboard():
|
| + # Return early.
|
| + return True
|
| +
|
| + try:
|
| + # Check if the test passes normally, because otherwise there is no
|
| + # reason to trace its failure.
|
| + if not run_normally(executable, test_case):
|
| + print '%s is broken when run alone, please fix the test.' % test_case
|
| + failed_alone.append(test_case)
|
| + continue
|
| +
|
| + if not trace_and_verify(result, test_case):
|
| + failures.append(test_case)
|
| + print 'Failed to fix %s' % test_case
|
| + else:
|
| + fixed_tests.append(test_case)
|
| + except: # pylint: disable=W0702
|
| + failures.append(test_case)
|
| + print 'Failed to fix %s' % test_case
|
| + print '%d/%d' % (index+1, len(test_cases))
|
| + finally:
|
| + print 'Test cases fixed (%d):' % len(fixed_tests)
|
| + for fixed_test in fixed_tests:
|
| + print ' %s' % fixed_test
|
| + print ''
|
| +
|
| + print 'Test cases still failing (%d):' % len(failures)
|
| + for failure in failures:
|
| + print ' %s' % failure
|
| +
|
| + if failed_alone:
|
| + print ('Test cases that failed normally when run alone (%d):' %
|
| + len(failed_alone))
|
| + for failed in failed_alone:
|
| + print failed
|
| + return not failures
|
| +
|
| +
|
| +def main():
|
| + parser = run_test_cases.OptionParserWithTestSharding(
|
| + usage='%prog <option> [test]')
|
| + parser.add_option('-d', '--dir', default='../../out/Release',
|
| + help='The directory containing the the test executable and '
|
| + 'result file. Defaults to %default')
|
| + options, args = parser.parse_args()
|
| +
|
| + if len(args) != 1:
|
| + parser.error('Use with the name of the test only, e.g. unit_tests')
|
| +
|
| + basename = args[0]
|
| + executable = os.path.join(options.dir, basename)
|
| + result = '%s.results' % executable
|
| + if sys.platform in('win32', 'cygwin'):
|
| + executable += '.exe'
|
| + if not os.path.isfile(executable):
|
| + print >> sys.stderr, (
|
| + '%s doesn\'t exist, please build %s_run' % (executable, basename))
|
| + return 1
|
| + if not os.path.isfile(result):
|
| + print >> sys.stderr, (
|
| + '%s doesn\'t exist, please build %s_run' % (result, basename))
|
| + return 1
|
| +
|
| + return not fix_all(result, options.index, options.shards, executable)
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
|
|