Android CTS-related helper script improvements:
- add new options to 'test_android_cts':
+ --save-result saves the test_result.xml to specified file
+ --no-baseline skips baseline operations (and nonzero return on diff)
+ --clean-dex always removes all dex files before build
- add new script 'compare_cts_results' to compare multiple test_result.xml's
Bug:
Change-Id: I7ab98f7f24debde19bef661f5a8d76a2b6526c78
diff --git a/tools/compare_cts_results.py b/tools/compare_cts_results.py
new file mode 100755
index 0000000..95e29d1
--- /dev/null
+++ b/tools/compare_cts_results.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Compare multiple CTS test_result.xml files
+
+from __future__ import print_function
+from os.path import basename
+import argparse
+import os
+import re
+import sys
+
+class Module:
+ def __init__(self):
+ self.test_cases = {}
+ self.bf_covered_in_file = 0 # bitfield, one bit per file
+
+ def get_test_case_maybe_create(self, test_case_name):
+ return self.test_cases.setdefault(test_case_name, TestCase())
+
+ def set_file_index_present(self, file_idx):
+ self.bf_covered_in_file |= (1 << file_idx)
+
+ def report(self, module_name, files, diff_only):
+ bf_all_files = self.bf_covered_in_file
+ for test_case_name, test_case in self.test_cases.iteritems():
+ if test_case.bf_covered_in_file != bf_all_files:
+ report_missing_thing('test_case', module_name + '/' + test_case_name,
+ test_case.bf_covered_in_file, files)
+ for test_case_name, test_case in self.test_cases.iteritems():
+ test_case.report(module_name, test_case_name, files, diff_only)
+
+class TestCase:
+ def __init__(self):
+ self.tests = {}
+ self.bf_covered_in_file = 0 # bitfield, one bit per file
+
+ def get_test_maybe_create(self, test_name):
+ return self.tests.setdefault(test_name, Test())
+
+ def set_file_index_present(self, file_idx):
+ self.bf_covered_in_file |= (1 << file_idx)
+
+ def report(self, module_name, test_case_name, files, diff_only):
+ bf_all_files = self.bf_covered_in_file
+ for test_name, test in self.tests.iteritems():
+ do_report = test.bf_passing_in_file != bf_all_files
+ if diff_only:
+ do_report = do_report and test.bf_failing_in_file != bf_all_files
+ if do_report:
+ test.report(module_name, test_case_name, test_name, files)
+
+class Test:
+ def __init__(self):
+ self.bf_failing_in_file = 0 # bitfields, one bit per file
+ self.bf_passing_in_file = 0
+
+ def set_file_index_outcome(self, outcome_is_passed, file_idx):
+ bf_value = (1 << file_idx)
+ if outcome_is_passed:
+ self.bf_passing_in_file |= bf_value
+ else:
+ self.bf_failing_in_file |= bf_value
+
+ # Report test's status in all files: pass/fail/missing
+ def report(self, module_name, test_case_name, test_name, files):
+ print('Test: {}/{}/{}:'.format(module_name, test_case_name, test_name))
+ for file_idx, f in enumerate(files):
+ bf_value = 1 << file_idx
+ print('\t- {:20}'.format(basename(f)), end = '')
+ if self.bf_passing_in_file & bf_value:
+ print('PASS')
+ elif self.bf_failing_in_file & bf_value:
+ print(' FAIL')
+ else:
+ print(' -- -- (missing)')
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(
+ description = 'Compare multiple Android CTS test_result.xml files.')
+ parser.add_argument('files', nargs = '+',
+ help = 'List of (possibly renamed) test_result.xml files')
+ parser.add_argument('--diff-only',
+ action = 'store_true',
+ help = "Don't list tests that consistently fail in all result files,"
+ " list only differences.")
+ return parser.parse_args()
+
+# Read CTS test_result.xml from file and merge into result_tree
+def add_to_result_tree(result_tree, file_xml, file_idx):
+ re_module = re.compile('<Module name="([^"]*)"')
+ re_test_case = re.compile('<TestCase name="([^"]*)"')
+ re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
+ module = None
+ test_case = None
+ with open(file_xml) as f:
+ for line in f:
+ m = re_module.search(line)
+ if m:
+ module_name = m.groups()[0]
+ module = result_tree.setdefault(module_name, Module())
+ module.set_file_index_present(file_idx)
+ continue
+
+ m = re_test_case.search(line)
+ if m:
+ test_case_name = m.groups()[0]
+ test_case = module.get_test_case_maybe_create(test_case_name)
+ test_case.set_file_index_present(file_idx)
+ continue
+
+ m = re_test.search(line)
+ if m:
+ outcome = m.groups()[0]
+ test_name = m.groups()[1]
+ assert outcome in ["fail", "pass"]
+
+ v = test_case.get_test_maybe_create(test_name)
+ v.set_file_index_outcome(outcome == 'pass', file_idx)
+
+# main tree_report function
+def tree_report(result_tree, files, diff_only):
+ bf_all_files = (1 << len(files)) - 1
+ for module_name, module in result_tree.iteritems():
+ if module.bf_covered_in_file != bf_all_files:
+ report_missing_thing('module', module_name, module.bf_covered_in_file,
+ files)
+ for module_name, module in result_tree.iteritems():
+ module.report(module_name, files, diff_only)
+
+def report_missing_thing(thing_type, thing_name, bf_covered_in_file, files):
+ print('Missing {}: {}, from:'.format(thing_type, thing_name))
+ for file_idx, f in enumerate(files):
+ if not (bf_covered_in_file & (1 << file_idx)):
+ print('\t- ' + f)
+
+def Main():
+ m = Module()
+ m.get_test_case_maybe_create('qwe')
+
+ args = parse_arguments()
+
+ result_tree = {}
+ for file_idx, f in enumerate(args.files):
+ add_to_result_tree(result_tree, f, file_idx)
+
+ tree_report(result_tree, args.files, args.diff_only)
+
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
index 56fca2d..2f02212 100755
--- a/tools/test_android_cts.py
+++ b/tools/test_android_cts.py
@@ -72,6 +72,17 @@
metavar = 'FILE',
help = 'Enable logging d8 (compatdx) calls to the specified file. Works'
' only with --tool=d8')
+ parser.add_argument('--save-result',
+ metavar = 'FILE',
+ help = 'Save final test_result.xml to the specified file.')
+ parser.add_argument('--no-baseline',
+ action = 'store_true',
+ help = "Don't compare results to baseline hence don't return failure if"
+ ' they differ.')
+ parser.add_argument('--clean-dex',
+ action = 'store_true',
+ help = 'Remove AOSP/dex files always, before the build. By default they'
+ " are removed only if '--tool=d8' and they're older then the D8 tool")
return parser.parse_args()
# return False on error
@@ -177,7 +188,7 @@
print()
return differ
-def setup_and_clean():
+def setup_and_clean(tool_is_d8, clean_dex):
# Two output dirs, one for the android image and one for cts tests.
# The output is compiled with d8 and jack, respectively.
utils.makedirs_if_needed(AOSP_ROOT)
@@ -185,12 +196,18 @@
utils.makedirs_if_needed(OUT_CTS)
# remove dex files older than the current d8 tool
- d8jar_mtime = os.path.getmtime(D8_JAR)
- dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
- for x in os.walk(OUT_IMG)))
- for f in dex_files:
- if os.path.getmtime(f) <= d8jar_mtime:
- os.remove(f)
+ counter = 0
+ if tool_is_d8 or clean_dex:
+ if not clean_dex:
+ d8jar_mtime = os.path.getmtime(D8_JAR)
+ dex_files = (chain.from_iterable(glob(join(x[0], '*.dex'))
+ for x in os.walk(OUT_IMG)))
+ for f in dex_files:
+ if clean_dex or os.path.getmtime(f) <= d8jar_mtime:
+ os.remove(f)
+ counter += 1
+ if counter > 0:
+ print('Removed {} dex files.'.format(counter))
def checkout_aosp():
# checkout AOSP source
@@ -216,17 +233,18 @@
jack_option = 'ANDROID_COMPILE_WITH_JACK=' \
+ ('true' if args.tool == 'jack' else 'false')
- alt_jar_option = ''
+ # DX_ALT_JAR need to be cleared if not set, for 'make' to work properly
+ alt_jar_option = 'DX_ALT_JAR='
if args.tool == 'd8':
if args.d8log:
- alt_jar_option = 'DX_ALT_JAR=' + D8LOGGER_JAR
+ alt_jar_option += D8LOGGER_JAR
os.environ['D8LOGGER_OUTPUT'] = args.d8log
else:
- alt_jar_option = 'DX_ALT_JAR=' + COMPATDX_JAR
+ alt_jar_option += COMPATDX_JAR
gradle.RunGradle(['d8','d8logger', 'compatdx'])
- setup_and_clean()
+ setup_and_clean(args.tool == 'd8', args.clean_dex)
checkout_aosp()
@@ -239,6 +257,7 @@
if not remove_aosp_out():
return EXIT_FAILURE
+ print("-- Building CTS with 'make {} cts'.".format(J_OPTION))
os.symlink(OUT_CTS, AOSP_OUT)
check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, 'cts'],
cwd = AOSP_ROOT)
@@ -246,6 +265,8 @@
# activate OUT_IMG and build the Android image
if not remove_aosp_out():
return EXIT_FAILURE
+ print("-- Building Android image with 'make {} {} {}'." \
+ .format(J_OPTION, jack_option, alt_jar_option))
os.symlink(OUT_IMG, AOSP_OUT)
check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, jack_option,
alt_jar_option], cwd = AOSP_ROOT)
@@ -272,10 +293,12 @@
# print summaries
re_summary = re.compile('<Summary ')
- for (title, result_file) in [
- ('Summary from current test results: ', results_xml),
- ('Summary from baseline: ', CTS_BASELINE)
- ]:
+
+ summaries = [('Summary from current test results: ', results_xml)]
+ if not args.no_baseline:
+ summaries.append(('Summary from baseline: ', CTS_BASELINE))
+
+ for (title, result_file) in summaries:
print(title, result_file)
with open(result_file) as f:
for line in f:
@@ -283,12 +306,20 @@
print(line)
break
- print('Comparing test results to baseline:\n')
+ if args.no_baseline:
+ r = 0
+ else:
+ print('Comparing test results to baseline:\n')
- result_tree = read_test_result_into_tree(results_xml)
- baseline_tree = read_test_result_into_tree(CTS_BASELINE)
+ result_tree = read_test_result_into_tree(results_xml)
+ baseline_tree = read_test_result_into_tree(CTS_BASELINE)
- return EXIT_FAILURE if diff_tree_report(baseline_tree, result_tree) else 0
+ r = EXIT_FAILURE if diff_tree_report(baseline_tree, result_tree) else 0
+
+ if args.save_result:
+ copy2(results_xml, args.save_result)
+
+ return r
if __name__ == '__main__':
sys.exit(Main())