#!/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 a set of `test_result.xml` files in # third_party/android_cts_baseline/jack. The current test considered a success # if all tests pass that consistently pass in the baseline. 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 time import gradle import utils CTS_BASELINE_FILES_DIR = join(utils.REPO_ROOT, 'third_party/android_cts_baseline/jack') 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') 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 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 # Return list of fully qualified names of tests passing in # all the files. def consistently_passing_tests_from_test_results(filenames): tree = {} module = None testcase = None # Build a tree with leaves True|False|None for passing, failing and flaky # tests. for f in filenames: for x in utils.read_cts_test_result(f): if type(x) is utils.CtsModule: module = tree.setdefault(x.name, {}) elif type(x) is utils.CtsTestCase: testcase = module.setdefault(x.name, {}) else: outcome = testcase.setdefault(x.name, x.outcome) if outcome is not None and outcome != x.outcome: testcase[x.name] = None result = [] for module_name, module in tree.iteritems(): for test_case_name, test_case in module.iteritems(): result.extend(['{}/{}/{}'.format(module_name, test_case_name, test_name) for test_name, test in test_case.iteritems() if test]) return result 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) utils.makedirs_if_needed(OUT_IMG) utils.makedirs_if_needed(OUT_CTS) # remove dex files older than the current d8 tool 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 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') # 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 += D8LOGGER_JAR os.environ['D8LOGGER_OUTPUT'] = args.d8log else: alt_jar_option += COMPATDX_JAR gradle.RunGradle(['d8','d8logger', 'compatdx']) setup_and_clean(args.tool == 'd8', args.clean_dex) 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 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) # 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) 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() time.sleep(6) # aosp_helper waits to be killed in looping 'sleep 5' # 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(' 1: text = '{} tests that consistently pass in the baseline' \ ' are missing or failing in the current test:'.format(num_tests) else: text = '1 test that consistently passes in the baseline' \ ' is missing or failing in the current test:' print(text) for t in missing_or_failing_tests: print(t) r = EXIT_FAILURE else: r = 0 if args.save_result: copy2(results_xml, args.save_result) return r if __name__ == '__main__': sys.exit(Main())