|  | #!/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 checkout_aosp | 
|  | 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') | 
|  |  | 
|  | 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_DEFAULT = 8 | 
|  | J_OPTION = '-j' + str(J_DEFAULT) | 
|  |  | 
|  | 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 = ['dx', 'd8'], | 
|  | default = 'd8', | 
|  | help='compiler tool to use') | 
|  | 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(utils.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 Main(): | 
|  | args = parse_arguments() | 
|  |  | 
|  | assert args.tool in ['dx', 'd8'] | 
|  |  | 
|  | use_d8 = 'USE_D8=false' | 
|  | if args.tool == 'd8': | 
|  | use_d8 = 'USE_D8=true' | 
|  |  | 
|  | gradle.RunGradle(['d8', 'compatdx']) | 
|  |  | 
|  | setup_and_clean(args.tool == 'd8', args.clean_dex) | 
|  |  | 
|  | checkout_aosp.checkout_aosp("test", AOSP_MANIFEST_URL, None, | 
|  | AOSP_MANIFEST_XML, str(J_DEFAULT), True) | 
|  |  | 
|  | # 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, use_d8)) | 
|  | os.symlink(OUT_IMG, AOSP_OUT) | 
|  | check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, | 
|  | use_d8_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('<Summary ') | 
|  |  | 
|  | summaries = [('Summary from current test results: ', results_xml)] | 
|  |  | 
|  | for (title, result_file) in summaries: | 
|  | print(title, result_file) | 
|  | with open(result_file) as f: | 
|  | for line in f: | 
|  | if re_summary.search(line): | 
|  | print(line) | 
|  | break | 
|  |  | 
|  | if args.no_baseline: | 
|  | r = 0 | 
|  | else: | 
|  | print('Comparing test results to baseline:\n') | 
|  |  | 
|  | passing_tests = consistently_passing_tests_from_test_results([results_xml]) | 
|  | baseline_results = glob(join(CTS_BASELINE_FILES_DIR, '*.xml')) | 
|  | assert len(baseline_results) != 0 | 
|  |  | 
|  | passing_tests_in_baseline = \ | 
|  | consistently_passing_tests_from_test_results(baseline_results) | 
|  |  | 
|  | missing_or_failing_tests = \ | 
|  | set(passing_tests_in_baseline) - set(passing_tests) | 
|  |  | 
|  | num_tests = len(missing_or_failing_tests) | 
|  | if num_tests != 0: | 
|  | if num_tests > 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()) |