blob: 56fca2dd9135075dab8557b16f48e02ac51a4b55 [file] [log] [blame]
#!/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.
# Clone and build AOSP, using D8 instead of JACK or DX,
# then run the Android-CTS on the emulator and compare results
# to a baseline.
#
# This script uses the repo manifest file 'third_party/aosp_manifest.xml'
# which is a snapshot of the aosp repo set.
# The manifest file can be updated with the following commands:
#
# cd build/aosp
# repo manifest -o ../../third_party/aosp_manifest.xml -r
#
# The baseline is the `test_result.xml` file which is created with an AOSP
# build which uses the default (JACK) toolset.
#
# Use this script, with '--tool=jack' to reproduce the baseline results
#
from __future__ import print_function
from glob import glob
from itertools import chain
from os.path import join
from shutil import copy2
from subprocess import check_call, Popen
import argparse
import os
import re
import sys
import gradle
import utils
CTS_BASELINE = join(utils.REPO_ROOT,
'third_party/android_cts_baseline/test_result.xml')
AOSP_MANIFEST_XML = join(utils.REPO_ROOT, 'third_party',
'aosp_manifest.xml')
AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh')
D8_JAR = join(utils.REPO_ROOT, 'build/libs/d8.jar')
COMPATDX_JAR = join(utils.REPO_ROOT, 'build/libs/compatdx.jar')
D8LOGGER_JAR = join(utils.REPO_ROOT, 'build/libs/d8logger.jar')
AOSP_ROOT = join(utils.REPO_ROOT, 'build/aosp')
AOSP_MANIFEST_URL = 'https://android.googlesource.com/platform/manifest'
AOSP_PRESET = 'aosp_x86-eng'
AOSP_OUT = join(AOSP_ROOT, 'out')
OUT_IMG = join(AOSP_ROOT, 'out_img') # output dir for android img build
OUT_CTS = join(AOSP_ROOT, 'out_cts') # output dir for CTS build
RESULTS_DIR_BASE = join(OUT_CTS, 'host/linux-x86/cts/android-cts/results')
CTS_TRADEFED = join(OUT_CTS,
'host/linux-x86/cts/android-cts/tools/cts-tradefed')
J_OPTION = '-j8'
EXIT_FAILURE = 1
def parse_arguments():
parser = argparse.ArgumentParser(
description = 'Download the AOSP source tree, build an Android image'
' and the CTS targets and run CTS with the emulator on the image.')
parser.add_argument('--tool',
choices = ['jack', 'dx', 'd8'],
default = 'd8',
help='compiler tool to use')
parser.add_argument('--d8log',
metavar = 'FILE',
help = 'Enable logging d8 (compatdx) calls to the specified file. Works'
' only with --tool=d8')
return parser.parse_args()
# return False on error
def remove_aosp_out():
if os.path.exists(AOSP_OUT):
if os.path.islink(AOSP_OUT):
os.remove(AOSP_OUT)
else:
print("The AOSP out directory ('" + AOSP_OUT + "') is expected"
" to be a symlink", file = sys.stderr)
return False
return True
# Read the xml test result file into an in-memory tree:
# Extract only the Module/TestCase/Test names and outcome (True|False for
# PASS|FAIL):
#
# tree[module_name][testcase_name][test_name] = True|False
#
def read_test_result_into_tree(filename):
re_module = re.compile('<Module name="([^"]*)"')
re_testcase = re.compile('<TestCase name="([^"]*)"')
re_test = re.compile('<Test result="(pass|fail)" name="([^"]*)"')
tree = {}
module = None
testcase = None
with open(filename) as f:
for line in f:
m = re_module.search(line)
if m:
module_name = m.groups()[0]
tree[module_name] = {}
module = tree[module_name]
continue
m = re_testcase.search(line)
if m:
testcase_name = m.groups()[0]
module[testcase_name] = {}
testcase = module[testcase_name]
continue
m = re_test.search(line)
if m:
outcome = m.groups()[0]
test_name = m.groups()[1]
assert outcome in ["fail", "pass"]
testcase[test_name] = outcome == "pass"
return tree
# Report the items with the title
def report_key_diff(title, items, prefix = ''):
if len(items) > 0:
print(title, ":")
for x in items:
print("- {}{}".format(prefix, x))
print()
def diff_sets(base_minus_result_title, result_minus_base_title,
base_set, result_set, prefix = ''):
base_minus_result = base_set - result_set
result_minus_base = result_set - base_set
report_key_diff(base_minus_result_title, base_minus_result, prefix)
report_key_diff(result_minus_base_title, result_minus_base, prefix)
return len(base_minus_result) > 0 or len(result_minus_base) > 0
def diff_tree_report(baseline_tree, result_tree):
baseline_modules = set(baseline_tree.keys())
result_modules = set(result_tree.keys())
differ = diff_sets('Modules missing from current result',
'New modules appeared in current result',
baseline_modules, result_modules)
for module in (result_modules & baseline_modules):
baseline_module = baseline_tree[module]
result_module = result_tree[module]
baseline_testcases = set(baseline_module.keys())
result_testcases = set(result_module.keys())
differ = diff_sets('Test cases missing from current result',
'New test cases appeared in current result',
baseline_testcases, result_testcases, module + '/') \
or differ
for testcase in (result_testcases & baseline_testcases):
baseline_testcase = baseline_module[testcase]
result_testcase = result_module[testcase]
baseline_tests = set(baseline_testcase.keys())
result_tests = set(result_testcase.keys())
differ = diff_sets('Tests missing from current result',
'New tests appeared in current result',
baseline_tests, result_tests, module + '/' + testcase + '/') \
or differ
need_newline_at_end = False
for test in (result_tests & baseline_tests):
baseline_outcome = baseline_testcase[test]
result_outcome = result_testcase[test]
if baseline_outcome != result_outcome:
differ = True
print('Test: {}/{}/{}, change: {}'.format(
module, testcase, test,
'PASS -> FAIL' if baseline_outcome else 'FAIL -> PASS'))
need_newline_at_end = True
if need_newline_at_end:
print()
return differ
def setup_and_clean():
# 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)
utils.makedirs_if_needed(OUT_IMG)
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)
def checkout_aosp():
# checkout AOSP source
manifests_dir = join(AOSP_ROOT, '.repo', 'manifests')
utils.makedirs_if_needed(manifests_dir)
copy2(AOSP_MANIFEST_XML, manifests_dir)
check_call(['repo', 'init', '-u', AOSP_MANIFEST_URL, '-m',
'aosp_manifest.xml', '--depth=1'], cwd = AOSP_ROOT)
check_call(['repo', 'sync', '-dq', J_OPTION], cwd = AOSP_ROOT)
def Main():
args = parse_arguments()
if args.d8log and args.tool != 'd8':
print("The '--d8log' option works only with '--tool=d8'.",
file = sys.stderr)
return EXIT_FAILURE
assert args.tool in ['jack', 'dx', 'd8']
jack_option = 'ANDROID_COMPILE_WITH_JACK=' \
+ ('true' if args.tool == 'jack' else 'false')
alt_jar_option = ''
if args.tool == 'd8':
if args.d8log:
alt_jar_option = 'DX_ALT_JAR=' + D8LOGGER_JAR
os.environ['D8LOGGER_OUTPUT'] = args.d8log
else:
alt_jar_option = 'DX_ALT_JAR=' + COMPATDX_JAR
gradle.RunGradle(['d8','d8logger', 'compatdx'])
setup_and_clean()
checkout_aosp()
# activate OUT_CTS and build Android CTS
# AOSP has no clean way to set the output directory.
# In order to do incremental builds we apply the following symlink-based
# workaround.
# Note: this does not work on windows, but the AOSP
# doesn't build, either
if not remove_aosp_out():
return EXIT_FAILURE
os.symlink(OUT_CTS, AOSP_OUT)
check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, 'cts'],
cwd = AOSP_ROOT)
# activate OUT_IMG and build the Android image
if not remove_aosp_out():
return EXIT_FAILURE
os.symlink(OUT_IMG, AOSP_OUT)
check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, jack_option,
alt_jar_option], cwd = AOSP_ROOT)
emulator_proc = Popen([AOSP_HELPER_SH, AOSP_PRESET,
'emulator', '-partition-size', '4096', '-wipe-data'], cwd = AOSP_ROOT)
if emulator_proc.poll() is not None:
print("Can't start Android Emulator.", file = sys.stderr)
check_call([AOSP_HELPER_SH, AOSP_PRESET, 'run-cts',
CTS_TRADEFED, 'run', 'cts'], cwd = AOSP_ROOT)
emulator_proc.terminate()
# find the newest test_result.xml
result_dirs = \
[f for f in glob(join(RESULTS_DIR_BASE, '*')) if os.path.isdir(f)]
if len(result_dirs) == 0:
print("Can't find result directories in ", RESULTS_DIR_BASE)
return EXIT_FAILURE
result_dirs.sort(key = os.path.getmtime)
results_xml = join(result_dirs[-1], 'test_result.xml')
# print summaries
re_summary = re.compile('<Summary ')
for (title, result_file) in [
('Summary from current test results: ', results_xml),
('Summary from baseline: ', CTS_BASELINE)
]:
print(title, result_file)
with open(result_file) as f:
for line in f:
if re_summary.search(line):
print(line)
break
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)
return EXIT_FAILURE if diff_tree_report(baseline_tree, result_tree) else 0
if __name__ == '__main__':
sys.exit(Main())