| #!/usr/bin/env python3 | 
 | # 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']) | 
 |  | 
 |   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()) |