1#!/usr/bin/env python3 2# 3# [VPYTHON:BEGIN] 4# python_version: "3.8" 5# [VPYTHON:END] 6# 7# Copyright 2017, The Android Open Source Project 8# 9# Licensed under the Apache License, Version 2.0 (the "License"); 10# you may not use this file except in compliance with the License. 11# You may obtain a copy of the License at 12# 13# http://www.apache.org/licenses/LICENSE-2.0 14# 15# Unless required by applicable law or agreed to in writing, software 16# distributed under the License is distributed on an "AS IS" BASIS, 17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18# See the License for the specific language governing permissions and 19# limitations under the License. 20 21"""ART Run-Test TestRunner 22 23The testrunner runs the ART run-tests by simply invoking the script. 24It fetches the list of eligible tests from art/test directory, and list of 25disabled tests from art/test/knownfailures.json. It runs the tests by 26invoking art/test/run-test script and checks the exit value to decide if the 27test passed or failed. 28 29Before invoking the script, first build all the tests dependencies. 30There are two major build targets for building target and host tests 31dependencies: 321) test-art-host-run-test 332) test-art-target-run-test 34 35There are various options to invoke the script which are: 36-t: Either the test name as in art/test or the test name including the variant 37 information. Eg, "-t 001-HelloWorld", 38 "-t test-art-host-run-test-debug-prebuild-optimizing-relocate-ntrace-cms-checkjni-picimage-ndebuggable-001-HelloWorld32" 39-j: Number of thread workers to be used. Eg - "-j64" 40--dry-run: Instead of running the test name, just print its name. 41--verbose 42-b / --build-dependencies: to build the dependencies before running the test 43 44To specify any specific variants for the test, use --<<variant-name>>. 45For eg, for compiler type as optimizing, use --optimizing. 46 47 48In the end, the script will print the failed and skipped tests if any. 49 50""" 51import argparse 52import collections 53 54# b/140161314 diagnostics. 55try: 56 import concurrent.futures 57except Exception: 58 import sys 59 sys.stdout.write("\n\n" + sys.executable + " " + sys.version + "\n\n") 60 sys.stdout.flush() 61 raise 62 63import contextlib 64import csv 65import datetime 66import fnmatch 67import itertools 68import json 69import multiprocessing 70import os 71import re 72import shlex 73import shutil 74import signal 75import subprocess 76import sys 77import tempfile 78import threading 79import time 80 81import env 82from target_config import target_config 83from device_config import device_config 84 85# TODO: make it adjustable per tests and for buildbots 86# 87# Note: this needs to be larger than run-test timeouts, as long as this script 88# does not push the value to run-test. run-test is somewhat complicated: 89# base: 25m (large for ASAN) 90# + timeout handling: 2m 91# + gcstress extra: 20m 92# ----------------------- 93# 47m 94timeout = 3600 # 60 minutes 95 96if env.ART_TEST_RUN_ON_ARM_FVP: 97 # Increase timeout to 600 minutes due to the emulation overhead on FVP. 98 timeout = 36000 99 100# DISABLED_TEST_CONTAINER holds information about the disabled tests. It is a map 101# that has key as the test name (like 001-HelloWorld), and value as set of 102# variants that the test is disabled for. 103DISABLED_TEST_CONTAINER = {} 104 105# The Dict contains the list of all possible variants for a given type. For example, 106# for key TARGET, the value would be target and host. The list is used to parse 107# the test name given as the argument to run. 108VARIANT_TYPE_DICT = {} 109 110# The set of all variant sets that are incompatible and will always be skipped. 111NONFUNCTIONAL_VARIANT_SETS = set() 112 113# The set contains all the variants of each time. 114TOTAL_VARIANTS_SET = set() 115 116# The colors are used in the output. When a test passes, COLOR_PASS is used, 117# and so on. 118COLOR_ERROR = '\033[91m' 119COLOR_PASS = '\033[92m' 120COLOR_SKIP = '\033[93m' 121COLOR_NORMAL = '\033[0m' 122 123# The set contains the list of all the possible run tests that are in art/test 124# directory. 125RUN_TEST_SET = set() 126 127failed_tests = [] 128skipped_tests = [] 129 130# Flags 131n_thread = 0 132total_test_count = 0 133verbose = False 134dry_run = False 135ignore_skips = False 136build = False 137dist = False 138gdb = False 139gdb_arg = '' 140csv_result = None 141csv_writer = None 142runtime_option = '' 143with_agent = [] 144zipapex_loc = None 145run_test_option = [] 146dex2oat_jobs = -1 # -1 corresponds to default threads for dex2oat 147run_all_configs = False 148 149# Dict containing extra arguments 150extra_arguments = { "host" : [], "target" : [] } 151 152# Dict to store user requested test variants. 153# key: variant_type. 154# value: set of variants user wants to run of type <key>. 155_user_input_variants = collections.defaultdict(set) 156 157 158class ChildProcessTracker(object): 159 """Keeps track of forked child processes to be able to kill them.""" 160 161 def __init__(self): 162 self.procs = {} # dict from pid to subprocess.Popen object 163 self.mutex = threading.Lock() 164 165 def wait(self, proc, timeout): 166 """Waits on the given subprocess and makes it available to kill_all meanwhile. 167 168 Args: 169 proc: The subprocess.Popen object to wait on. 170 timeout: Timeout passed on to proc.communicate. 171 172 Returns: A tuple of the process stdout output and its return value. 173 """ 174 with self.mutex: 175 if self.procs is not None: 176 self.procs[proc.pid] = proc 177 else: 178 os.killpg(proc.pid, signal.SIGKILL) # kill_all has already been called. 179 try: 180 output = proc.communicate(timeout=timeout)[0] 181 return_value = proc.wait() 182 return output, return_value 183 finally: 184 with self.mutex: 185 if self.procs is not None: 186 del self.procs[proc.pid] 187 188 def kill_all(self): 189 """Kills all currently running processes and any future ones.""" 190 with self.mutex: 191 for pid in self.procs: 192 os.killpg(pid, signal.SIGKILL) 193 self.procs = None # Make future wait() calls kill their processes immediately. 194 195child_process_tracker = ChildProcessTracker() 196 197 198def setup_csv_result(): 199 """Set up the CSV output if required.""" 200 global csv_writer 201 csv_writer = csv.writer(csv_result) 202 # Write the header. 203 csv_writer.writerow(['target', 'run', 'prebuild', 'compiler', 'relocate', 'trace', 'gc', 204 'jni', 'image', 'debuggable', 'jvmti', 'cdex_level', 'test', 'address_size', 'result']) 205 206 207def send_csv_result(test, result): 208 """ 209 Write a line into the CSV results file if one is available. 210 """ 211 if csv_writer is not None: 212 csv_writer.writerow(extract_test_name(test) + [result]) 213 214def close_csv_file(): 215 global csv_result 216 global csv_writer 217 if csv_result is not None: 218 csv_writer = None 219 csv_result.flush() 220 csv_result.close() 221 csv_result = None 222 223def gather_test_info(): 224 """The method gathers test information about the test to be run which includes 225 generating the list of total tests from the art/test directory and the list 226 of disabled test. It also maps various variants to types. 227 """ 228 global TOTAL_VARIANTS_SET 229 # TODO: Avoid duplication of the variant names in different lists. 230 VARIANT_TYPE_DICT['run'] = {'ndebug', 'debug'} 231 VARIANT_TYPE_DICT['target'] = {'target', 'host', 'jvm'} 232 VARIANT_TYPE_DICT['trace'] = {'trace', 'ntrace', 'stream'} 233 VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image'} 234 VARIANT_TYPE_DICT['debuggable'] = {'ndebuggable', 'debuggable'} 235 VARIANT_TYPE_DICT['gc'] = {'gcstress', 'gcverify', 'cms'} 236 VARIANT_TYPE_DICT['prebuild'] = {'no-prebuild', 'prebuild'} 237 VARIANT_TYPE_DICT['cdex_level'] = {'cdex-none', 'cdex-fast'} 238 VARIANT_TYPE_DICT['relocate'] = {'relocate', 'no-relocate'} 239 VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'} 240 VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} 241 VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress', 242 'field-stress', 'step-stress'} 243 VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'jit-on-first-use', 244 'optimizing', 'regalloc_gc', 245 'speed-profile', 'baseline'} 246 247 # Regalloc_GC cannot work with prebuild. 248 NONFUNCTIONAL_VARIANT_SETS.add(frozenset({'regalloc_gc', 'prebuild'})) 249 250 for v_type in VARIANT_TYPE_DICT: 251 TOTAL_VARIANTS_SET = TOTAL_VARIANTS_SET.union(VARIANT_TYPE_DICT.get(v_type)) 252 253 test_dir = env.ANDROID_BUILD_TOP + '/art/test' 254 for f in os.listdir(test_dir): 255 if fnmatch.fnmatch(f, '[0-9]*'): 256 RUN_TEST_SET.add(f) 257 258 259def setup_test_env(): 260 """The method sets default value for the various variants of the tests if they 261 are already not set. 262 """ 263 if env.ART_TEST_BISECTION: 264 env.ART_TEST_RUN_TEST_NO_PREBUILD = True 265 env.ART_TEST_RUN_TEST_PREBUILD = False 266 # Bisection search writes to standard output. 267 env.ART_TEST_QUIET = False 268 269 global _user_input_variants 270 global run_all_configs 271 # These are the default variant-options we will use if nothing in the group is specified. 272 default_variants = { 273 'target': {'host', 'target'}, 274 'prebuild': {'prebuild'}, 275 'cdex_level': {'cdex-fast'}, 276 'jvmti': { 'no-jvmti'}, 277 'compiler': {'optimizing', 278 'jit', 279 'interpreter', 280 'interp-ac', 281 'speed-profile'}, 282 'relocate': {'no-relocate'}, 283 'trace': {'ntrace'}, 284 'gc': {'cms'}, 285 'jni': {'checkjni'}, 286 'image': {'picimage'}, 287 'debuggable': {'ndebuggable'}, 288 'run': {'debug'}, 289 # address_sizes_target depends on the target so it is dealt with below. 290 } 291 # We want to pull these early since the full VARIANT_TYPE_DICT has a few additional ones we don't 292 # want to pick up if we pass --all. 293 default_variants_keys = default_variants.keys() 294 if run_all_configs: 295 default_variants = VARIANT_TYPE_DICT 296 297 for key in default_variants_keys: 298 if not _user_input_variants[key]: 299 _user_input_variants[key] = default_variants[key] 300 301 _user_input_variants['address_sizes_target'] = collections.defaultdict(set) 302 if not _user_input_variants['address_sizes']: 303 _user_input_variants['address_sizes_target']['target'].add( 304 env.ART_PHONY_TEST_TARGET_SUFFIX) 305 _user_input_variants['address_sizes_target']['host'].add( 306 env.ART_PHONY_TEST_HOST_SUFFIX) 307 if env.ART_TEST_RUN_TEST_2ND_ARCH: 308 _user_input_variants['address_sizes_target']['host'].add( 309 env.ART_2ND_PHONY_TEST_HOST_SUFFIX) 310 _user_input_variants['address_sizes_target']['target'].add( 311 env.ART_2ND_PHONY_TEST_TARGET_SUFFIX) 312 else: 313 _user_input_variants['address_sizes_target']['host'] = _user_input_variants['address_sizes'] 314 _user_input_variants['address_sizes_target']['target'] = _user_input_variants['address_sizes'] 315 316 global n_thread 317 if 'target' in _user_input_variants['target']: 318 device_name = get_device_name() 319 if n_thread == 0: 320 # Use only half of the cores since fully loading the device tends to lead to timeouts. 321 n_thread = get_target_cpu_count() // 2 322 if device_name == 'fugu': 323 n_thread = 1 324 else: 325 device_name = "host" 326 if n_thread == 0: 327 n_thread = get_host_cpu_count() 328 print_text("Concurrency: {} ({})\n".format(n_thread, device_name)) 329 330 global extra_arguments 331 for target in _user_input_variants['target']: 332 extra_arguments[target] = find_extra_device_arguments(target) 333 334 if not sys.stdout.isatty(): 335 global COLOR_ERROR 336 global COLOR_PASS 337 global COLOR_SKIP 338 global COLOR_NORMAL 339 COLOR_ERROR = '' 340 COLOR_PASS = '' 341 COLOR_SKIP = '' 342 COLOR_NORMAL = '' 343 344def find_extra_device_arguments(target): 345 """ 346 Gets any extra arguments from the device_config. 347 """ 348 device_name = target 349 if target == 'target': 350 device_name = get_device_name() 351 return device_config.get(device_name, { 'run-test-args' : [] })['run-test-args'] 352 353def get_device_name(): 354 """ 355 Gets the value of ro.product.name from remote device. 356 """ 357 proc = subprocess.Popen(['adb', 'shell', 'getprop', 'ro.product.name'], 358 stderr=subprocess.STDOUT, 359 stdout = subprocess.PIPE, 360 universal_newlines=True) 361 # only wait 2 seconds. 362 timeout_val = 2 363 364 if env.ART_TEST_RUN_ON_ARM_FVP: 365 # Increase timeout to 200 seconds due to the emulation overhead on FVP. 366 timeout_val = 200 367 368 output = proc.communicate(timeout = timeout_val)[0] 369 success = not proc.wait() 370 if success: 371 return output.strip() 372 else: 373 print_text("Unable to determine device type!\n") 374 print_text("Continuing anyway.\n") 375 return "UNKNOWN_TARGET" 376 377def run_tests(tests): 378 """This method generates variants of the tests to be run and executes them. 379 380 Args: 381 tests: The set of tests to be run. 382 """ 383 options_all = '' 384 385 # jvm does not run with all these combinations, 386 # or at least it doesn't make sense for most of them. 387 # TODO: support some jvm variants like jvmti ? 388 target_input_variants = _user_input_variants['target'] 389 uncombinated_target_input_variants = [] 390 if 'jvm' in target_input_variants: 391 _user_input_variants['target'].remove('jvm') 392 uncombinated_target_input_variants.append('jvm') 393 394 global total_test_count 395 total_test_count = len(tests) 396 if target_input_variants: 397 for variant_type in VARIANT_TYPE_DICT: 398 if not (variant_type == 'target' or 'address_sizes' in variant_type): 399 total_test_count *= len(_user_input_variants[variant_type]) 400 target_address_combinations = 0 401 for target in target_input_variants: 402 for address_size in _user_input_variants['address_sizes_target'][target]: 403 target_address_combinations += 1 404 target_address_combinations += len(uncombinated_target_input_variants) 405 total_test_count *= target_address_combinations 406 407 if env.ART_TEST_WITH_STRACE: 408 options_all += ' --strace' 409 410 if env.ART_TEST_RUN_TEST_ALWAYS_CLEAN: 411 options_all += ' --always-clean' 412 413 if env.ART_TEST_BISECTION: 414 options_all += ' --bisection-search' 415 416 if gdb: 417 options_all += ' --gdb' 418 if gdb_arg: 419 options_all += ' --gdb-arg ' + gdb_arg 420 421 options_all += ' ' + ' '.join(run_test_option) 422 423 if runtime_option: 424 for opt in runtime_option: 425 options_all += ' --runtime-option ' + opt 426 if with_agent: 427 for opt in with_agent: 428 options_all += ' --with-agent ' + opt 429 430 if dex2oat_jobs != -1: 431 options_all += ' --dex2oat-jobs ' + str(dex2oat_jobs) 432 433 def iter_config(tests, input_variants, user_input_variants): 434 config = itertools.product(tests, input_variants, user_input_variants['run'], 435 user_input_variants['prebuild'], user_input_variants['compiler'], 436 user_input_variants['relocate'], user_input_variants['trace'], 437 user_input_variants['gc'], user_input_variants['jni'], 438 user_input_variants['image'], 439 user_input_variants['debuggable'], user_input_variants['jvmti'], 440 user_input_variants['cdex_level']) 441 return config 442 443 # [--host, --target] combines with all the other user input variants. 444 config = iter_config(tests, target_input_variants, _user_input_variants) 445 # [--jvm] currently combines with nothing else. most of the extra flags we'd insert 446 # would be unrecognizable by the 'java' binary, so avoid inserting any extra flags for now. 447 uncombinated_config = iter_config(tests, uncombinated_target_input_variants, { 'run': [''], 448 'prebuild': [''], 'compiler': [''], 449 'relocate': [''], 'trace': [''], 450 'gc': [''], 'jni': [''], 451 'image': [''], 452 'debuggable': [''], 'jvmti': [''], 453 'cdex_level': ['']}) 454 455 def start_combination(executor, config_tuple, global_options, address_size): 456 test, target, run, prebuild, compiler, relocate, trace, gc, \ 457 jni, image, debuggable, jvmti, cdex_level = config_tuple 458 459 # NB The order of components here should match the order of 460 # components in the regex parser in parse_test_name. 461 test_name = 'test-art-' 462 test_name += target + '-run-test-' 463 test_name += run + '-' 464 test_name += prebuild + '-' 465 test_name += compiler + '-' 466 test_name += relocate + '-' 467 test_name += trace + '-' 468 test_name += gc + '-' 469 test_name += jni + '-' 470 test_name += image + '-' 471 test_name += debuggable + '-' 472 test_name += jvmti + '-' 473 test_name += cdex_level + '-' 474 test_name += test 475 test_name += address_size 476 477 variant_set = {target, run, prebuild, compiler, relocate, trace, gc, jni, 478 image, debuggable, jvmti, cdex_level, address_size} 479 480 options_test = global_options 481 482 if target == 'host': 483 options_test += ' --host' 484 elif target == 'jvm': 485 options_test += ' --jvm' 486 487 # Honor ART_TEST_CHROOT, ART_TEST_ANDROID_ROOT, ART_TEST_ANDROID_ART_ROOT, 488 # ART_TEST_ANDROID_I18N_ROOT, and ART_TEST_ANDROID_TZDATA_ROOT but only 489 # for target tests. 490 if target == 'target': 491 if env.ART_TEST_CHROOT: 492 options_test += ' --chroot ' + env.ART_TEST_CHROOT 493 if env.ART_TEST_ANDROID_ROOT: 494 options_test += ' --android-root ' + env.ART_TEST_ANDROID_ROOT 495 if env.ART_TEST_ANDROID_I18N_ROOT: 496 options_test += ' --android-i18n-root ' + env.ART_TEST_ANDROID_I18N_ROOT 497 if env.ART_TEST_ANDROID_ART_ROOT: 498 options_test += ' --android-art-root ' + env.ART_TEST_ANDROID_ART_ROOT 499 if env.ART_TEST_ANDROID_TZDATA_ROOT: 500 options_test += ' --android-tzdata-root ' + env.ART_TEST_ANDROID_TZDATA_ROOT 501 502 if run == 'ndebug': 503 options_test += ' -O' 504 505 if prebuild == 'prebuild': 506 options_test += ' --prebuild' 507 elif prebuild == 'no-prebuild': 508 options_test += ' --no-prebuild' 509 510 if cdex_level: 511 # Add option and remove the cdex- prefix. 512 options_test += ' --compact-dex-level ' + cdex_level.replace('cdex-','') 513 514 if compiler == 'optimizing': 515 options_test += ' --optimizing' 516 elif compiler == 'regalloc_gc': 517 options_test += ' --optimizing -Xcompiler-option --register-allocation-strategy=graph-color' 518 elif compiler == 'interpreter': 519 options_test += ' --interpreter' 520 elif compiler == 'interp-ac': 521 options_test += ' --interpreter --verify-soft-fail' 522 elif compiler == 'jit': 523 options_test += ' --jit' 524 elif compiler == 'jit-on-first-use': 525 options_test += ' --jit --runtime-option -Xjitthreshold:0' 526 elif compiler == 'speed-profile': 527 options_test += ' --random-profile' 528 elif compiler == 'baseline': 529 options_test += ' --baseline' 530 531 if relocate == 'relocate': 532 options_test += ' --relocate' 533 elif relocate == 'no-relocate': 534 options_test += ' --no-relocate' 535 536 if trace == 'trace': 537 options_test += ' --trace' 538 elif trace == 'stream': 539 options_test += ' --trace --stream' 540 541 if gc == 'gcverify': 542 options_test += ' --gcverify' 543 elif gc == 'gcstress': 544 options_test += ' --gcstress' 545 546 if jni == 'forcecopy': 547 options_test += ' --runtime-option -Xjniopts:forcecopy' 548 elif jni == 'checkjni': 549 options_test += ' --runtime-option -Xcheck:jni' 550 551 if image == 'no-image': 552 options_test += ' --no-image' 553 554 if debuggable == 'debuggable': 555 options_test += ' --debuggable --runtime-option -Xopaque-jni-ids:true' 556 557 if jvmti == 'jvmti-stress': 558 options_test += ' --jvmti-trace-stress --jvmti-redefine-stress --jvmti-field-stress' 559 elif jvmti == 'field-stress': 560 options_test += ' --jvmti-field-stress' 561 elif jvmti == 'trace-stress': 562 options_test += ' --jvmti-trace-stress' 563 elif jvmti == 'redefine-stress': 564 options_test += ' --jvmti-redefine-stress' 565 elif jvmti == 'step-stress': 566 options_test += ' --jvmti-step-stress' 567 568 if address_size == '64': 569 options_test += ' --64' 570 571 # TODO(http://36039166): This is a temporary solution to 572 # fix build breakages. 573 options_test = (' --output-path %s') % ( 574 tempfile.mkdtemp(dir=env.ART_HOST_TEST_DIR)) + options_test 575 576 run_test_sh = env.ANDROID_BUILD_TOP + '/art/test/run-test' 577 command = ' '.join((run_test_sh, options_test, ' '.join(extra_arguments[target]), test)) 578 return executor.submit(run_test, command, test, variant_set, test_name) 579 580 # Use a context-manager to handle cleaning up the extracted zipapex if needed. 581 with handle_zipapex(zipapex_loc) as zipapex_opt: 582 options_all += zipapex_opt 583 global n_thread 584 with concurrent.futures.ThreadPoolExecutor(max_workers=n_thread) as executor: 585 test_futures = [] 586 for config_tuple in config: 587 target = config_tuple[1] 588 for address_size in _user_input_variants['address_sizes_target'][target]: 589 test_futures.append(start_combination(executor, config_tuple, options_all, address_size)) 590 591 for config_tuple in uncombinated_config: 592 test_futures.append( 593 start_combination(executor, config_tuple, options_all, "")) # no address size 594 595 try: 596 tests_done = 0 597 for test_future in concurrent.futures.as_completed(test_futures): 598 (test, status, failure_info, test_time) = test_future.result() 599 tests_done += 1 600 print_test_info(tests_done, test, status, failure_info, test_time) 601 if failure_info and not env.ART_TEST_KEEP_GOING: 602 for f in test_futures: 603 f.cancel() 604 break 605 except KeyboardInterrupt: 606 for f in test_futures: 607 f.cancel() 608 child_process_tracker.kill_all() 609 executor.shutdown(True) 610 611@contextlib.contextmanager 612def handle_zipapex(ziploc): 613 """Extracts the zipapex (if present) and handles cleanup. 614 615 If we are running out of a zipapex we want to unzip it once and have all the tests use the same 616 extracted contents. This extracts the files and handles cleanup if needed. It returns the 617 required extra arguments to pass to the run-test. 618 """ 619 if ziploc is not None: 620 with tempfile.TemporaryDirectory() as tmpdir: 621 subprocess.check_call(["unzip", "-qq", ziploc, "apex_payload.zip", "-d", tmpdir]) 622 subprocess.check_call( 623 ["unzip", "-qq", os.path.join(tmpdir, "apex_payload.zip"), "-d", tmpdir]) 624 yield " --runtime-extracted-zipapex " + tmpdir 625 else: 626 yield "" 627 628def _popen(**kwargs): 629 if sys.version_info.major == 3 and sys.version_info.minor >= 6: 630 return subprocess.Popen(encoding=sys.stdout.encoding, **kwargs) 631 return subprocess.Popen(**kwargs) 632 633def run_test(command, test, test_variant, test_name): 634 """Runs the test. 635 636 It invokes art/test/run-test script to run the test. The output of the script 637 is checked, and if it ends with "Succeeded!", it assumes that the tests 638 passed, otherwise, put it in the list of failed test. Before actually running 639 the test, it also checks if the test is placed in the list of disabled tests, 640 and if yes, it skips running it, and adds the test in the list of skipped 641 tests. 642 643 Args: 644 command: The command to be used to invoke the script 645 test: The name of the test without the variant information. 646 test_variant: The set of variant for the test. 647 test_name: The name of the test along with the variants. 648 649 Returns: a tuple of testname, status, optional failure info, and test time. 650 """ 651 try: 652 if is_test_disabled(test, test_variant): 653 test_skipped = True 654 test_time = datetime.timedelta() 655 else: 656 test_skipped = False 657 test_start_time = time.monotonic() 658 if verbose: 659 print_text("Starting %s at %s\n" % (test_name, test_start_time)) 660 if gdb: 661 proc = _popen( 662 args=command.split(), 663 stderr=subprocess.STDOUT, 664 universal_newlines=True, 665 start_new_session=True 666 ) 667 else: 668 proc = _popen( 669 args=command.split(), 670 stderr=subprocess.STDOUT, 671 stdout = subprocess.PIPE, 672 universal_newlines=True, 673 start_new_session=True, 674 ) 675 script_output, return_value = child_process_tracker.wait(proc, timeout) 676 test_passed = not return_value 677 test_time_seconds = time.monotonic() - test_start_time 678 test_time = datetime.timedelta(seconds=test_time_seconds) 679 680 if not test_skipped: 681 if test_passed: 682 return (test_name, 'PASS', None, test_time) 683 else: 684 failed_tests.append((test_name, str(command) + "\n" + script_output)) 685 return (test_name, 'FAIL', ('%s\n%s') % (command, script_output), test_time) 686 elif not dry_run: 687 skipped_tests.append(test_name) 688 return (test_name, 'SKIP', None, test_time) 689 else: 690 return (test_name, 'PASS', None, test_time) 691 except subprocess.TimeoutExpired as e: 692 if verbose: 693 print_text("Timeout of %s at %s\n" % (test_name, time.monotonic())) 694 test_time_seconds = time.monotonic() - test_start_time 695 test_time = datetime.timedelta(seconds=test_time_seconds) 696 failed_tests.append((test_name, 'Timed out in %d seconds' % timeout)) 697 698 # HACK(b/142039427): Print extra backtraces on timeout. 699 if "-target-" in test_name: 700 for i in range(8): 701 proc_name = "dalvikvm" + test_name[-2:] 702 pidof = subprocess.run(["adb", "shell", "pidof", proc_name], stdout=subprocess.PIPE) 703 for pid in pidof.stdout.decode("ascii").split(): 704 if i >= 4: 705 print_text("Backtrace of %s at %s\n" % (pid, time.monotonic())) 706 subprocess.run(["adb", "shell", "debuggerd", pid]) 707 time.sleep(10) 708 task_dir = "/proc/%s/task" % pid 709 tids = subprocess.run(["adb", "shell", "ls", task_dir], stdout=subprocess.PIPE) 710 for tid in tids.stdout.decode("ascii").split(): 711 for status in ["stat", "status"]: 712 filename = "%s/%s/%s" % (task_dir, tid, status) 713 print_text("Content of %s\n" % (filename)) 714 subprocess.run(["adb", "shell", "cat", filename]) 715 time.sleep(60) 716 717 # The python documentation states that it is necessary to actually kill the process. 718 os.killpg(proc.pid, signal.SIGKILL) 719 script_output = proc.communicate() 720 721 return (test_name, 'TIMEOUT', 'Timed out in %d seconds\n%s' % (timeout, command), test_time) 722 except Exception as e: 723 failed_tests.append((test_name, str(e))) 724 return (test_name, 'FAIL', ('%s\n%s\n\n') % (command, str(e)), datetime.timedelta()) 725 726def print_test_info(test_count, test_name, result, failed_test_info="", 727 test_time=datetime.timedelta()): 728 """Print the continous test information 729 730 If verbose is set to True, it continuously prints test status information 731 on a new line. 732 If verbose is set to False, it keeps on erasing test 733 information by overriding it with the latest test information. Also, 734 in this case it stictly makes sure that the information length doesn't 735 exceed the console width. It does so by shortening the test_name. 736 737 When a test fails, it prints the output of the run-test script and 738 command used to invoke the script. It doesn't override the failing 739 test information in either of the cases. 740 """ 741 742 info = '' 743 if not verbose: 744 # Without --verbose, the testrunner erases passing test info. It 745 # does that by overriding the printed text with white spaces all across 746 # the console width. 747 console_width = int(os.popen('stty size', 'r').read().split()[1]) 748 info = '\r' + ' ' * console_width + '\r' 749 try: 750 percent = (test_count * 100) / total_test_count 751 progress_info = ('[ %d%% %d/%d ]') % ( 752 percent, 753 test_count, 754 total_test_count) 755 if test_time.total_seconds() != 0 and verbose: 756 info += '(%s)' % str(test_time) 757 758 759 if result == 'FAIL' or result == 'TIMEOUT': 760 if not verbose: 761 info += ('%s %s %s\n') % ( 762 progress_info, 763 test_name, 764 COLOR_ERROR + result + COLOR_NORMAL) 765 else: 766 info += ('%s %s %s\n%s\n') % ( 767 progress_info, 768 test_name, 769 COLOR_ERROR + result + COLOR_NORMAL, 770 failed_test_info) 771 else: 772 result_text = '' 773 if result == 'PASS': 774 result_text += COLOR_PASS + 'PASS' + COLOR_NORMAL 775 elif result == 'SKIP': 776 result_text += COLOR_SKIP + 'SKIP' + COLOR_NORMAL 777 778 if verbose: 779 info += ('%s %s %s\n') % ( 780 progress_info, 781 test_name, 782 result_text) 783 else: 784 total_output_length = 2 # Two spaces 785 total_output_length += len(progress_info) 786 total_output_length += len(result) 787 allowed_test_length = console_width - total_output_length 788 test_name_len = len(test_name) 789 if allowed_test_length < test_name_len: 790 test_name = ('...%s') % ( 791 test_name[-(allowed_test_length - 3):]) 792 info += ('%s %s %s') % ( 793 progress_info, 794 test_name, 795 result_text) 796 send_csv_result(test_name, result) 797 print_text(info) 798 except Exception as e: 799 print_text(('%s\n%s\n') % (test_name, str(e))) 800 failed_tests.append(test_name) 801 802def verify_knownfailure_entry(entry): 803 supported_field = { 804 'tests' : (list, str), 805 'test_patterns' : (list,), 806 'description' : (list, str), 807 'bug' : (str,), 808 'variant' : (str,), 809 'devices': (list, str), 810 'env_vars' : (dict,), 811 'zipapex' : (bool,), 812 } 813 for field in entry: 814 field_type = type(entry[field]) 815 if field_type not in supported_field[field]: 816 raise ValueError('%s is not supported type for %s\n%s' % ( 817 str(field_type), 818 field, 819 str(entry))) 820 821def get_disabled_test_info(device_name): 822 """Generate set of known failures. 823 824 It parses the art/test/knownfailures.json file to generate the list of 825 disabled tests. 826 827 Returns: 828 The method returns a dict of tests mapped to the variants list 829 for which the test should not be run. 830 """ 831 known_failures_file = env.ANDROID_BUILD_TOP + '/art/test/knownfailures.json' 832 with open(known_failures_file) as known_failures_json: 833 known_failures_info = json.loads(known_failures_json.read()) 834 835 disabled_test_info = {} 836 for failure in known_failures_info: 837 verify_knownfailure_entry(failure) 838 tests = failure.get('tests', []) 839 if isinstance(tests, str): 840 tests = [tests] 841 patterns = failure.get("test_patterns", []) 842 if (not isinstance(patterns, list)): 843 raise ValueError("test_patterns is not a list in %s" % failure) 844 845 tests += [f for f in RUN_TEST_SET if any(re.match(pat, f) is not None for pat in patterns)] 846 variants = parse_variants(failure.get('variant')) 847 848 # Treat a '"devices": "<foo>"' equivalent to 'target' variant if 849 # "foo" is present in "devices". 850 device_names = failure.get('devices', []) 851 if isinstance(device_names, str): 852 device_names = [device_names] 853 if len(device_names) != 0: 854 if device_name in device_names: 855 variants.add('target') 856 else: 857 # Skip adding test info as device_name is not present in "devices" entry. 858 continue 859 860 env_vars = failure.get('env_vars') 861 862 if check_env_vars(env_vars): 863 for test in tests: 864 if test not in RUN_TEST_SET: 865 raise ValueError('%s is not a valid run-test' % ( 866 test)) 867 if test in disabled_test_info: 868 disabled_test_info[test] = disabled_test_info[test].union(variants) 869 else: 870 disabled_test_info[test] = variants 871 872 zipapex_disable = failure.get("zipapex", False) 873 if zipapex_disable and zipapex_loc is not None: 874 for test in tests: 875 if test not in RUN_TEST_SET: 876 raise ValueError('%s is not a valid run-test' % (test)) 877 if test in disabled_test_info: 878 disabled_test_info[test] = disabled_test_info[test].union(variants) 879 else: 880 disabled_test_info[test] = variants 881 882 return disabled_test_info 883 884def gather_disabled_test_info(): 885 global DISABLED_TEST_CONTAINER 886 device_name = get_device_name() if 'target' in _user_input_variants['target'] else None 887 DISABLED_TEST_CONTAINER = get_disabled_test_info(device_name) 888 889def check_env_vars(env_vars): 890 """Checks if the env variables are set as required to run the test. 891 892 Returns: 893 True if all the env variables are set as required, otherwise False. 894 """ 895 896 if not env_vars: 897 return True 898 for key in env_vars: 899 if env.get_env(key) != env_vars.get(key): 900 return False 901 return True 902 903 904def is_test_disabled(test, variant_set): 905 """Checks if the test along with the variant_set is disabled. 906 907 Args: 908 test: The name of the test as in art/test directory. 909 variant_set: Variants to be used for the test. 910 Returns: 911 True, if the test is disabled. 912 """ 913 if dry_run: 914 return True 915 if test in env.EXTRA_DISABLED_TESTS: 916 return True 917 if ignore_skips: 918 return False 919 variants_list = DISABLED_TEST_CONTAINER.get(test, {}) 920 for variants in variants_list: 921 variants_present = True 922 for variant in variants: 923 if variant not in variant_set: 924 variants_present = False 925 break 926 if variants_present: 927 return True 928 for bad_combo in NONFUNCTIONAL_VARIANT_SETS: 929 if bad_combo.issubset(variant_set): 930 return True 931 return False 932 933 934def parse_variants(variants): 935 """Parse variants fetched from art/test/knownfailures.json. 936 """ 937 if not variants: 938 variants = '' 939 for variant in TOTAL_VARIANTS_SET: 940 variants += variant 941 variants += '|' 942 variants = variants[:-1] 943 variant_list = set() 944 or_variants = variants.split('|') 945 for or_variant in or_variants: 946 and_variants = or_variant.split('&') 947 variant = set() 948 for and_variant in and_variants: 949 and_variant = and_variant.strip() 950 if and_variant not in TOTAL_VARIANTS_SET: 951 raise ValueError('%s is not a valid variant' % ( 952 and_variant)) 953 variant.add(and_variant) 954 variant_list.add(frozenset(variant)) 955 return variant_list 956 957def print_text(output): 958 sys.stdout.write(output) 959 sys.stdout.flush() 960 961def print_analysis(): 962 if not verbose: 963 # Without --verbose, the testrunner erases passing test info. It 964 # does that by overriding the printed text with white spaces all across 965 # the console width. 966 console_width = int(os.popen('stty size', 'r').read().split()[1]) 967 eraser_text = '\r' + ' ' * console_width + '\r' 968 print_text(eraser_text) 969 970 # Prints information about the total tests run. 971 # E.g., "2/38 (5%) tests passed". 972 passed_test_count = total_test_count - len(skipped_tests) - len(failed_tests) 973 passed_test_information = ('%d/%d (%d%%) %s passed.\n') % ( 974 passed_test_count, 975 total_test_count, 976 (passed_test_count*100)/total_test_count, 977 'tests' if passed_test_count > 1 else 'test') 978 print_text(passed_test_information) 979 980 # Prints the list of skipped tests, if any. 981 if skipped_tests: 982 print_text(COLOR_SKIP + 'SKIPPED TESTS: ' + COLOR_NORMAL + '\n') 983 for test in skipped_tests: 984 print_text(test + '\n') 985 print_text('\n') 986 987 # Prints the list of failed tests, if any. 988 if failed_tests: 989 print_text(COLOR_ERROR + 'FAILED: ' + COLOR_NORMAL + '\n') 990 for test_info in failed_tests: 991 print_text(('%s\n%s\n' % (test_info[0], test_info[1]))) 992 print_text(COLOR_ERROR + '----------' + COLOR_NORMAL + '\n') 993 for failed_test in sorted([test_info[0] for test_info in failed_tests]): 994 print_text(('%s\n' % (failed_test))) 995 996test_name_matcher = None 997def extract_test_name(test_name): 998 """Parses the test name and returns all the parts""" 999 global test_name_matcher 1000 if test_name_matcher is None: 1001 regex = '^test-art-' 1002 regex += '(' + '|'.join(VARIANT_TYPE_DICT['target']) + ')-' 1003 regex += 'run-test-' 1004 regex += '(' + '|'.join(VARIANT_TYPE_DICT['run']) + ')-' 1005 regex += '(' + '|'.join(VARIANT_TYPE_DICT['prebuild']) + ')-' 1006 regex += '(' + '|'.join(VARIANT_TYPE_DICT['compiler']) + ')-' 1007 regex += '(' + '|'.join(VARIANT_TYPE_DICT['relocate']) + ')-' 1008 regex += '(' + '|'.join(VARIANT_TYPE_DICT['trace']) + ')-' 1009 regex += '(' + '|'.join(VARIANT_TYPE_DICT['gc']) + ')-' 1010 regex += '(' + '|'.join(VARIANT_TYPE_DICT['jni']) + ')-' 1011 regex += '(' + '|'.join(VARIANT_TYPE_DICT['image']) + ')-' 1012 regex += '(' + '|'.join(VARIANT_TYPE_DICT['debuggable']) + ')-' 1013 regex += '(' + '|'.join(VARIANT_TYPE_DICT['jvmti']) + ')-' 1014 regex += '(' + '|'.join(VARIANT_TYPE_DICT['cdex_level']) + ')-' 1015 regex += '(' + '|'.join(RUN_TEST_SET) + ')' 1016 regex += '(' + '|'.join(VARIANT_TYPE_DICT['address_sizes']) + ')$' 1017 test_name_matcher = re.compile(regex) 1018 match = test_name_matcher.match(test_name) 1019 if match: 1020 return list(match.group(i) for i in range(1,15)) 1021 raise ValueError(test_name + " is not a valid test") 1022 1023def parse_test_name(test_name): 1024 """Parses the testname provided by the user. 1025 It supports two types of test_name: 1026 1) Like 001-HelloWorld. In this case, it will just verify if the test actually 1027 exists and if it does, it returns the testname. 1028 2) Like test-art-host-run-test-debug-prebuild-interpreter-no-relocate-ntrace-cms-checkjni-pointer-ids-picimage-ndebuggable-001-HelloWorld32 1029 In this case, it will parse all the variants and check if they are placed 1030 correctly. If yes, it will set the various VARIANT_TYPES to use the 1031 variants required to run the test. Again, it returns the test_name 1032 without the variant information like 001-HelloWorld. 1033 """ 1034 test_set = set() 1035 for test in RUN_TEST_SET: 1036 if test.startswith(test_name): 1037 test_set.add(test) 1038 if test_set: 1039 return test_set 1040 1041 parsed = extract_test_name(test_name) 1042 _user_input_variants['target'].add(parsed[0]) 1043 _user_input_variants['run'].add(parsed[1]) 1044 _user_input_variants['prebuild'].add(parsed[2]) 1045 _user_input_variants['compiler'].add(parsed[3]) 1046 _user_input_variants['relocate'].add(parsed[4]) 1047 _user_input_variants['trace'].add(parsed[5]) 1048 _user_input_variants['gc'].add(parsed[6]) 1049 _user_input_variants['jni'].add(parsed[7]) 1050 _user_input_variants['image'].add(parsed[8]) 1051 _user_input_variants['debuggable'].add(parsed[9]) 1052 _user_input_variants['jvmti'].add(parsed[10]) 1053 _user_input_variants['cdex_level'].add(parsed[11]) 1054 _user_input_variants['address_sizes'].add(parsed[13]) 1055 return {parsed[12]} 1056 1057 1058def get_target_cpu_count(): 1059 adb_command = 'adb shell cat /sys/devices/system/cpu/present' 1060 cpu_info_proc = subprocess.Popen(adb_command.split(), stdout=subprocess.PIPE) 1061 cpu_info = cpu_info_proc.stdout.read() 1062 if type(cpu_info) is bytes: 1063 cpu_info = cpu_info.decode('utf-8') 1064 cpu_info_regex = r'\d*-(\d*)' 1065 match = re.match(cpu_info_regex, cpu_info) 1066 if match: 1067 return int(match.group(1)) + 1 # Add one to convert from "last-index" to "count" 1068 else: 1069 raise ValueError('Unable to predict the concurrency for the target. ' 1070 'Is device connected?') 1071 1072 1073def get_host_cpu_count(): 1074 return multiprocessing.cpu_count() 1075 1076 1077def parse_option(): 1078 global verbose 1079 global dry_run 1080 global ignore_skips 1081 global n_thread 1082 global build 1083 global dist 1084 global gdb 1085 global gdb_arg 1086 global runtime_option 1087 global run_test_option 1088 global timeout 1089 global dex2oat_jobs 1090 global run_all_configs 1091 global with_agent 1092 global zipapex_loc 1093 global csv_result 1094 1095 parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.") 1096 parser.add_argument('-t', '--test', action='append', dest='tests', help='name(s) of the test(s)') 1097 global_group = parser.add_argument_group('Global options', 1098 'Options that affect all tests being run') 1099 global_group.add_argument('-j', type=int, dest='n_thread', help="""Number of CPUs to use. 1100 Defaults to half of CPUs on target and all CPUs on host.""") 1101 global_group.add_argument('--timeout', default=timeout, type=int, dest='timeout') 1102 global_group.add_argument('--verbose', '-v', action='store_true', dest='verbose') 1103 global_group.add_argument('--dry-run', action='store_true', dest='dry_run') 1104 global_group.add_argument("--skip", action='append', dest="skips", default=[], 1105 help="Skip the given test in all circumstances.") 1106 global_group.add_argument("--no-skips", dest="ignore_skips", action='store_true', default=False, 1107 help="""Don't skip any run-test configurations listed in 1108 knownfailures.json.""") 1109 global_group.add_argument('--no-build-dependencies', 1110 action='store_false', dest='build', 1111 help="""Don't build dependencies under any circumstances. This is the 1112 behavior if ART_TEST_RUN_TEST_ALWAYS_BUILD is not set to 'true'.""") 1113 global_group.add_argument('-b', '--build-dependencies', 1114 action='store_true', dest='build', 1115 help="""Build dependencies under all circumstances. By default we will 1116 not build dependencies unless ART_TEST_RUN_TEST_BUILD=true.""") 1117 global_group.add_argument('--dist', 1118 action='store_true', dest='dist', 1119 help="""If dependencies are to be built, pass `dist` to the build 1120 command line. You may want to also set the DIST_DIR environment 1121 variable when using this flag.""") 1122 global_group.set_defaults(build = env.ART_TEST_RUN_TEST_BUILD) 1123 global_group.add_argument('--gdb', action='store_true', dest='gdb') 1124 global_group.add_argument('--gdb-arg', dest='gdb_arg') 1125 global_group.add_argument('--run-test-option', action='append', dest='run_test_option', 1126 default=[], 1127 help="""Pass an option, unaltered, to the run-test script. 1128 This should be enclosed in single-quotes to allow for spaces. The option 1129 will be split using shlex.split() prior to invoking run-test. 1130 Example \"--run-test-option='--with-agent libtifast.so=MethodExit'\"""") 1131 global_group.add_argument('--with-agent', action='append', dest='with_agent', 1132 help="""Pass an agent to be attached to the runtime""") 1133 global_group.add_argument('--runtime-option', action='append', dest='runtime_option', 1134 help="""Pass an option to the runtime. Runtime options 1135 starting with a '-' must be separated by a '=', for 1136 example '--runtime-option=-Xjitthreshold:0'.""") 1137 global_group.add_argument('--dex2oat-jobs', type=int, dest='dex2oat_jobs', 1138 help='Number of dex2oat jobs') 1139 global_group.add_argument('--runtime-zipapex', dest='runtime_zipapex', default=None, 1140 help='Location for runtime zipapex.') 1141 global_group.add_argument('-a', '--all', action='store_true', dest='run_all', 1142 help="Run all the possible configurations for the input test set") 1143 global_group.add_argument('--csv-results', action='store', dest='csv_result', default=None, 1144 type=argparse.FileType('w'), help='Store a CSV record of all results.') 1145 for variant_type, variant_set in VARIANT_TYPE_DICT.items(): 1146 var_group = parser.add_argument_group( 1147 '{}-type Options'.format(variant_type), 1148 "Options that control the '{}' variants.".format(variant_type)) 1149 var_group.add_argument('--all-' + variant_type, 1150 action='store_true', 1151 dest='all_' + variant_type, 1152 help='Enable all variants of ' + variant_type) 1153 for variant in variant_set: 1154 flag = '--' + variant 1155 var_group.add_argument(flag, action='store_true', dest=variant) 1156 1157 options = vars(parser.parse_args()) 1158 if options['csv_result'] is not None: 1159 csv_result = options['csv_result'] 1160 setup_csv_result() 1161 # Handle the --all-<type> meta-options 1162 for variant_type, variant_set in VARIANT_TYPE_DICT.items(): 1163 if options['all_' + variant_type]: 1164 for variant in variant_set: 1165 options[variant] = True 1166 1167 tests = None 1168 env.EXTRA_DISABLED_TESTS.update(set(options['skips'])) 1169 if options['tests']: 1170 tests = set() 1171 for test_name in options['tests']: 1172 tests |= parse_test_name(test_name) 1173 1174 for variant_type in VARIANT_TYPE_DICT: 1175 for variant in VARIANT_TYPE_DICT[variant_type]: 1176 if options.get(variant): 1177 _user_input_variants[variant_type].add(variant) 1178 1179 if options['verbose']: 1180 verbose = True 1181 if options['n_thread']: 1182 n_thread = max(1, options['n_thread']) 1183 ignore_skips = options['ignore_skips'] 1184 if options['dry_run']: 1185 dry_run = True 1186 verbose = True 1187 build = options['build'] 1188 dist = options['dist'] 1189 if options['gdb']: 1190 n_thread = 1 1191 gdb = True 1192 if options['gdb_arg']: 1193 gdb_arg = options['gdb_arg'] 1194 runtime_option = options['runtime_option']; 1195 with_agent = options['with_agent']; 1196 run_test_option = sum(map(shlex.split, options['run_test_option']), []) 1197 zipapex_loc = options['runtime_zipapex'] 1198 1199 timeout = options['timeout'] 1200 if options['dex2oat_jobs']: 1201 dex2oat_jobs = options['dex2oat_jobs'] 1202 if options['run_all']: 1203 run_all_configs = True 1204 1205 return tests 1206 1207def main(): 1208 gather_test_info() 1209 user_requested_tests = parse_option() 1210 setup_test_env() 1211 gather_disabled_test_info() 1212 if build: 1213 build_targets = '' 1214 if 'host' in _user_input_variants['target']: 1215 build_targets += 'test-art-host-run-test-dependencies ' 1216 if 'target' in _user_input_variants['target']: 1217 build_targets += 'test-art-target-run-test-dependencies ' 1218 if 'jvm' in _user_input_variants['target']: 1219 build_targets += 'test-art-host-run-test-dependencies ' 1220 build_command = env.ANDROID_BUILD_TOP + '/build/soong/soong_ui.bash --make-mode' 1221 build_command += ' DX=' 1222 if dist: 1223 build_command += ' dist' 1224 build_command += ' ' + build_targets 1225 print_text('Build command: %s\n' % build_command) 1226 if subprocess.call(build_command.split()): 1227 # Debugging for b/62653020 1228 if env.DIST_DIR: 1229 shutil.copyfile(env.SOONG_OUT_DIR + '/build.ninja', env.DIST_DIR + '/soong.ninja') 1230 sys.exit(1) 1231 1232 if user_requested_tests: 1233 run_tests(user_requested_tests) 1234 else: 1235 run_tests(RUN_TEST_SET) 1236 1237 print_analysis() 1238 close_csv_file() 1239 1240 exit_code = 0 if len(failed_tests) == 0 else 1 1241 sys.exit(exit_code) 1242 1243if __name__ == '__main__': 1244 main() 1245