Save previous runs from test.py and support re-running failed tests.
Change-Id: I8d440472ef3a0f78455a6d8afbdb2235c3b7bf78
diff --git a/tools/test.py b/tools/test.py
index 05c5453..d6753ed 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -9,6 +9,7 @@
import optparse
import os
+import shutil
import subprocess
import sys
import thread
@@ -39,6 +40,10 @@
BUCKET = 'r8-test-results'
+NUMBER_OF_TEST_REPORTS = 5
+REPORTS_PATH = os.path.join(utils.BUILD, 'reports')
+REPORT_INDEX = ['tests', 'test', 'index.html']
+
def ParseOptions():
result = optparse.OptionParser()
result.add_option('--no-internal', '--no_internal',
@@ -116,6 +121,12 @@
result.add_option('--r8lib-no-deps', '--r8lib_no_deps',
default=False, action='store_true',
help='Run the tests on r8lib without relocated dependencies.')
+ result.add_option('--failed',
+ default=False, action='store_true',
+ help='Run the tests that failed last execution.')
+ result.add_option('--fail-fast', '--fail_fast',
+ default=False, action='store_true',
+ help='Stop on first failure. Passes --fail-fast to gradle test runner.')
return result.parse_args()
def archive_failures():
@@ -206,6 +217,15 @@
# Add Gradle tasks
gradle_args.append('cleanTest')
gradle_args.append('test')
+ if options.fail_fast:
+ gradle_args.append('--fail-fast')
+ if options.failed:
+ args = compute_failed_tests(args)
+ if args is None:
+ return 1
+ if len(args) == 0:
+ print "No failing tests"
+ return 0
# Test filtering. Must always follow the 'test' task.
for testFilter in args:
gradle_args.append('--tests')
@@ -229,6 +249,8 @@
gradle_args.append('-Pupdate_test_timestamp=' + timestamp_file)
thread.start_new_thread(timeout_handler, (timestamp_file,))
+ rotate_test_reports()
+
# Now run tests on selected runtime(s).
vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
for art_vm in vms_to_test:
@@ -283,6 +305,69 @@
print_jstacks()
last_timestamp = new_timestamp
+def report_dir_path(index):
+ if index is 0:
+ return REPORTS_PATH
+ return '%s%d' % (REPORTS_PATH, index)
+
+def report_index_path(index):
+ return os.path.join(report_dir_path(index), *REPORT_INDEX)
+
+# Rotate test results so previous results are still accessible.
+def rotate_test_reports():
+ if not os.path.exists(report_dir_path(0)):
+ return
+ i = 1
+ while i < NUMBER_OF_TEST_REPORTS and os.path.exists(report_dir_path(i)):
+ i += 1
+ if i == NUMBER_OF_TEST_REPORTS and os.path.exists(report_dir_path(i)):
+ shutil.rmtree(report_dir_path(i))
+ while i > 0:
+ shutil.move(report_dir_path(i - 1), report_dir_path(i))
+ i -= 1
+
+def compute_failed_tests(args):
+ if len(args) > 1:
+ print "Running with --failed can take an optional path to a report index (or report number)."
+ return None
+ report = report_index_path(0)
+ # If the default report does not exist, fall back to the previous report as it may be a failed
+ # gradle run which has already moved the report to report1, but did not produce a new report.
+ if not os.path.exists(report):
+ report1 = report_index_path(1)
+ if os.path.exists(report1):
+ report = report1
+ if len(args) == 1:
+ try:
+ # try to parse the arg as a report index.
+ index = int(args[0])
+ report = report_index_path(index)
+ except ValueError:
+ # if integer parsing failed assume it is a report file path.
+ report = args[0]
+ if not os.path.exists(report):
+ print "Can't re-run failing, no report at:", report
+ return None
+ print "Reading failed tests in", report
+ failing = set()
+ inFailedSection = False
+ for line in file(report):
+ l = line.strip()
+ if l == "<h2>Failed tests</h2>":
+ inFailedSection = True
+ elif l.startswith("<h2>"):
+ inFailedSection = False
+ prefix = '<a href="classes/'
+ if inFailedSection and l.startswith(prefix):
+ href = l[len(prefix):l.index('">')]
+ # Ignore enties ending with .html which are test classes, not test methods.
+ if not href.endswith('.html'):
+ # Remove the .html and anchor separateor, also, a classMethod test is the static
+ # setup failing so rerun the full class of tests.
+ test = href.replace('.html','').replace('#', '.').replace('.classMethod', '')
+ failing.add(test)
+ return list(failing)
+
if __name__ == '__main__':
return_code = Main()
if return_code != 0: