Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | # Clone and build AOSP, using D8 instead of JACK or DX, |
| 7 | # then run the Android-CTS on the emulator and compare results |
| 8 | # to a baseline. |
| 9 | # |
| 10 | # This script uses the repo manifest file 'third_party/aosp_manifest.xml' |
| 11 | # which is a snapshot of the aosp repo set. |
| 12 | # The manifest file can be updated with the following commands: |
| 13 | # |
| 14 | # cd build/aosp |
| 15 | # repo manifest -o ../../third_party/aosp_manifest.xml -r |
| 16 | # |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 17 | # The baseline is a set of `test_result.xml` files in |
| 18 | # third_party/android_cts_baseline/jack. The current test considered a success |
| 19 | # if all tests pass that consistently pass in the baseline. |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 20 | |
| 21 | from __future__ import print_function |
| 22 | from glob import glob |
| 23 | from itertools import chain |
| 24 | from os.path import join |
| 25 | from shutil import copy2 |
| 26 | from subprocess import check_call, Popen |
| 27 | import argparse |
| 28 | import os |
| 29 | import re |
| 30 | import sys |
Tamas Kenez | 82efeb5 | 2017-06-12 13:56:22 +0200 | [diff] [blame] | 31 | import time |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 32 | |
Søren Gjesse | 298759e | 2017-09-08 08:22:41 +0200 | [diff] [blame] | 33 | import checkout_aosp |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 34 | import gradle |
| 35 | import utils |
| 36 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 37 | CTS_BASELINE_FILES_DIR = join(utils.REPO_ROOT, |
| 38 | 'third_party/android_cts_baseline/jack') |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 39 | AOSP_MANIFEST_XML = join(utils.REPO_ROOT, 'third_party', |
| 40 | 'aosp_manifest.xml') |
| 41 | AOSP_HELPER_SH = join(utils.REPO_ROOT, 'scripts', 'aosp_helper.sh') |
| 42 | |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 43 | AOSP_ROOT = join(utils.REPO_ROOT, 'build/aosp') |
| 44 | |
| 45 | AOSP_MANIFEST_URL = 'https://android.googlesource.com/platform/manifest' |
| 46 | AOSP_PRESET = 'aosp_x86-eng' |
| 47 | |
| 48 | AOSP_OUT = join(AOSP_ROOT, 'out') |
| 49 | OUT_IMG = join(AOSP_ROOT, 'out_img') # output dir for android img build |
| 50 | OUT_CTS = join(AOSP_ROOT, 'out_cts') # output dir for CTS build |
| 51 | RESULTS_DIR_BASE = join(OUT_CTS, 'host/linux-x86/cts/android-cts/results') |
| 52 | CTS_TRADEFED = join(OUT_CTS, |
| 53 | 'host/linux-x86/cts/android-cts/tools/cts-tradefed') |
| 54 | |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 55 | J_DEFAULT = 8 |
| 56 | J_OPTION = '-j' + str(J_DEFAULT) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 57 | |
| 58 | EXIT_FAILURE = 1 |
| 59 | |
| 60 | def parse_arguments(): |
| 61 | parser = argparse.ArgumentParser( |
| 62 | description = 'Download the AOSP source tree, build an Android image' |
| 63 | ' and the CTS targets and run CTS with the emulator on the image.') |
| 64 | parser.add_argument('--tool', |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 65 | choices = ['dx', 'd8'], |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 66 | default = 'd8', |
| 67 | help='compiler tool to use') |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 68 | parser.add_argument('--save-result', |
| 69 | metavar = 'FILE', |
| 70 | help = 'Save final test_result.xml to the specified file.') |
| 71 | parser.add_argument('--no-baseline', |
| 72 | action = 'store_true', |
| 73 | help = "Don't compare results to baseline hence don't return failure if" |
| 74 | ' they differ.') |
| 75 | parser.add_argument('--clean-dex', |
| 76 | action = 'store_true', |
| 77 | help = 'Remove AOSP/dex files always, before the build. By default they' |
| 78 | " are removed only if '--tool=d8' and they're older then the D8 tool") |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 79 | return parser.parse_args() |
| 80 | |
| 81 | # return False on error |
| 82 | def remove_aosp_out(): |
| 83 | if os.path.exists(AOSP_OUT): |
| 84 | if os.path.islink(AOSP_OUT): |
| 85 | os.remove(AOSP_OUT) |
| 86 | else: |
| 87 | print("The AOSP out directory ('" + AOSP_OUT + "') is expected" |
| 88 | " to be a symlink", file = sys.stderr) |
| 89 | return False |
| 90 | return True |
| 91 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 92 | # Return list of fully qualified names of tests passing in |
| 93 | # all the files. |
| 94 | def consistently_passing_tests_from_test_results(filenames): |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 95 | tree = {} |
| 96 | module = None |
| 97 | testcase = None |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 98 | # Build a tree with leaves True|False|None for passing, failing and flaky |
| 99 | # tests. |
| 100 | for f in filenames: |
| 101 | for x in utils.read_cts_test_result(f): |
| 102 | if type(x) is utils.CtsModule: |
| 103 | module = tree.setdefault(x.name, {}) |
| 104 | elif type(x) is utils.CtsTestCase: |
| 105 | testcase = module.setdefault(x.name, {}) |
| 106 | else: |
| 107 | outcome = testcase.setdefault(x.name, x.outcome) |
| 108 | if outcome is not None and outcome != x.outcome: |
| 109 | testcase[x.name] = None |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 110 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 111 | result = [] |
| 112 | for module_name, module in tree.iteritems(): |
| 113 | for test_case_name, test_case in module.iteritems(): |
| 114 | result.extend(['{}/{}/{}'.format(module_name, test_case_name, test_name) |
| 115 | for test_name, test in test_case.iteritems() |
| 116 | if test]) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 117 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 118 | return result |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 119 | |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 120 | def setup_and_clean(tool_is_d8, clean_dex): |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 121 | # Two output dirs, one for the android image and one for cts tests. |
| 122 | # The output is compiled with d8 and jack, respectively. |
| 123 | utils.makedirs_if_needed(AOSP_ROOT) |
| 124 | utils.makedirs_if_needed(OUT_IMG) |
| 125 | utils.makedirs_if_needed(OUT_CTS) |
| 126 | |
| 127 | # remove dex files older than the current d8 tool |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 128 | counter = 0 |
| 129 | if tool_is_d8 or clean_dex: |
| 130 | if not clean_dex: |
Rico Wind | b4621c1 | 2017-08-28 12:48:53 +0200 | [diff] [blame] | 131 | d8jar_mtime = os.path.getmtime(utils.D8_JAR) |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 132 | dex_files = (chain.from_iterable(glob(join(x[0], '*.dex')) |
| 133 | for x in os.walk(OUT_IMG))) |
| 134 | for f in dex_files: |
| 135 | if clean_dex or os.path.getmtime(f) <= d8jar_mtime: |
| 136 | os.remove(f) |
| 137 | counter += 1 |
| 138 | if counter > 0: |
| 139 | print('Removed {} dex files.'.format(counter)) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 140 | |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 141 | def Main(): |
| 142 | args = parse_arguments() |
| 143 | |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 144 | assert args.tool in ['dx', 'd8'] |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 145 | |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 146 | use_d8 = 'USE_D8=false' |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 147 | if args.tool == 'd8': |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 148 | use_d8 = 'USE_D8=true' |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 149 | |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 150 | gradle.RunGradle(['d8', 'compatdx']) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 151 | |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 152 | setup_and_clean(args.tool == 'd8', args.clean_dex) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 153 | |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 154 | checkout_aosp.checkout_aosp("test", AOSP_MANIFEST_URL, None, |
| 155 | AOSP_MANIFEST_XML, str(J_DEFAULT), True) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 156 | |
| 157 | # activate OUT_CTS and build Android CTS |
| 158 | # AOSP has no clean way to set the output directory. |
| 159 | # In order to do incremental builds we apply the following symlink-based |
| 160 | # workaround. |
| 161 | # Note: this does not work on windows, but the AOSP |
| 162 | # doesn't build, either |
| 163 | |
| 164 | if not remove_aosp_out(): |
| 165 | return EXIT_FAILURE |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 166 | print("-- Building CTS with 'make {} cts'.".format(J_OPTION)) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 167 | os.symlink(OUT_CTS, AOSP_OUT) |
| 168 | check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, 'cts'], |
| 169 | cwd = AOSP_ROOT) |
| 170 | |
| 171 | # activate OUT_IMG and build the Android image |
| 172 | if not remove_aosp_out(): |
| 173 | return EXIT_FAILURE |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 174 | print("-- Building Android image with 'make {} {}'." \ |
| 175 | .format(J_OPTION, use_d8)) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 176 | os.symlink(OUT_IMG, AOSP_OUT) |
Søren Gjesse | adfde76 | 2017-10-13 09:51:38 +0200 | [diff] [blame] | 177 | check_call([AOSP_HELPER_SH, AOSP_PRESET, 'make', J_OPTION, |
| 178 | use_d8_option], cwd = AOSP_ROOT) |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 179 | |
| 180 | emulator_proc = Popen([AOSP_HELPER_SH, AOSP_PRESET, |
| 181 | 'emulator', '-partition-size', '4096', '-wipe-data'], cwd = AOSP_ROOT) |
| 182 | |
| 183 | if emulator_proc.poll() is not None: |
| 184 | print("Can't start Android Emulator.", file = sys.stderr) |
| 185 | |
| 186 | check_call([AOSP_HELPER_SH, AOSP_PRESET, 'run-cts', |
| 187 | CTS_TRADEFED, 'run', 'cts'], cwd = AOSP_ROOT) |
| 188 | |
| 189 | emulator_proc.terminate() |
Tamas Kenez | 0cd1e1e | 2017-06-06 14:50:52 +0200 | [diff] [blame] | 190 | time.sleep(6) # aosp_helper waits to be killed in looping 'sleep 5' |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 191 | |
| 192 | # find the newest test_result.xml |
| 193 | result_dirs = \ |
| 194 | [f for f in glob(join(RESULTS_DIR_BASE, '*')) if os.path.isdir(f)] |
| 195 | if len(result_dirs) == 0: |
| 196 | print("Can't find result directories in ", RESULTS_DIR_BASE) |
| 197 | return EXIT_FAILURE |
| 198 | result_dirs.sort(key = os.path.getmtime) |
| 199 | results_xml = join(result_dirs[-1], 'test_result.xml') |
| 200 | |
| 201 | # print summaries |
| 202 | re_summary = re.compile('<Summary ') |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 203 | |
| 204 | summaries = [('Summary from current test results: ', results_xml)] |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 205 | |
| 206 | for (title, result_file) in summaries: |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 207 | print(title, result_file) |
| 208 | with open(result_file) as f: |
| 209 | for line in f: |
| 210 | if re_summary.search(line): |
| 211 | print(line) |
| 212 | break |
| 213 | |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 214 | if args.no_baseline: |
| 215 | r = 0 |
| 216 | else: |
| 217 | print('Comparing test results to baseline:\n') |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 218 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 219 | passing_tests = consistently_passing_tests_from_test_results([results_xml]) |
Tamas Kenez | 02bff03 | 2017-07-18 12:13:58 +0200 | [diff] [blame] | 220 | baseline_results = glob(join(CTS_BASELINE_FILES_DIR, '*.xml')) |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 221 | assert len(baseline_results) != 0 |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 222 | |
Tamas Kenez | 8a4f077 | 2017-06-13 09:16:30 +0200 | [diff] [blame] | 223 | passing_tests_in_baseline = \ |
| 224 | consistently_passing_tests_from_test_results(baseline_results) |
| 225 | |
| 226 | missing_or_failing_tests = \ |
| 227 | set(passing_tests_in_baseline) - set(passing_tests) |
| 228 | |
| 229 | num_tests = len(missing_or_failing_tests) |
| 230 | if num_tests != 0: |
| 231 | if num_tests > 1: |
| 232 | text = '{} tests that consistently pass in the baseline' \ |
| 233 | ' are missing or failing in the current test:'.format(num_tests) |
| 234 | else: |
| 235 | text = '1 test that consistently passes in the baseline' \ |
| 236 | ' is missing or failing in the current test:' |
| 237 | print(text) |
| 238 | for t in missing_or_failing_tests: |
| 239 | print(t) |
| 240 | r = EXIT_FAILURE |
| 241 | else: |
| 242 | r = 0 |
Tamas Kenez | 507481b | 2017-06-06 15:01:33 +0200 | [diff] [blame] | 243 | |
| 244 | if args.save_result: |
| 245 | copy2(results_xml, args.save_result) |
| 246 | |
| 247 | return r |
Tamas Kenez | 971eec6 | 2017-05-24 11:08:40 +0200 | [diff] [blame] | 248 | |
| 249 | if __name__ == '__main__': |
| 250 | sys.exit(Main()) |