1# Copyright 2014 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5 6import contextlib 7import collections 8import fnmatch 9import itertools 10import logging 11import math 12import os 13import posixpath 14import subprocess 15import shutil 16import time 17 18from devil.android import crash_handler 19from devil.android import device_errors 20from devil.android import device_temp_file 21from devil.android import logcat_monitor 22from devil.android import ports 23from devil.android.sdk import version_codes 24from devil.utils import reraiser_thread 25from incremental_install import installer 26from pylib import constants 27from pylib.base import base_test_result 28from pylib.gtest import gtest_test_instance 29from pylib.local import local_test_server_spawner 30from pylib.local.device import local_device_environment 31from pylib.local.device import local_device_test_run 32from pylib.symbols import stack_symbolizer 33from pylib.utils import code_coverage_utils 34from pylib.utils import google_storage_helper 35from pylib.utils import logdog_helper 36from py_trace_event import trace_event 37from py_utils import contextlib_ext 38from py_utils import tempfile_ext 39import tombstones 40 41_MAX_INLINE_FLAGS_LENGTH = 50 # Arbitrarily chosen. 42_EXTRA_COMMAND_LINE_FILE = ( 43 'org.chromium.native_test.NativeTest.CommandLineFile') 44_EXTRA_COMMAND_LINE_FLAGS = ( 45 'org.chromium.native_test.NativeTest.CommandLineFlags') 46_EXTRA_COVERAGE_DEVICE_FILE = ( 47 'org.chromium.native_test.NativeTest.CoverageDeviceFile') 48_EXTRA_STDOUT_FILE = ( 49 'org.chromium.native_test.NativeTestInstrumentationTestRunner' 50 '.StdoutFile') 51_EXTRA_TEST = ( 52 'org.chromium.native_test.NativeTestInstrumentationTestRunner' 53 '.Test') 54_EXTRA_TEST_LIST = ( 55 'org.chromium.native_test.NativeTestInstrumentationTestRunner' 56 '.TestList') 57 58# Used to identify the prefix in gtests. 59_GTEST_PRETEST_PREFIX = 'PRE_' 60 61_SECONDS_TO_NANOS = int(1e9) 62 63# Tests that use SpawnedTestServer must run the LocalTestServerSpawner on the 64# host machine. 65# TODO(jbudorick): Move this up to the test instance if the net test server is 66# handled outside of the APK for the remote_device environment. 67_SUITE_REQUIRES_TEST_SERVER_SPAWNER = [ 68 'components_browsertests', 'content_unittests', 'content_browsertests', 69 'net_unittests', 'services_unittests', 'unit_tests' 70] 71 72# No-op context manager. If we used Python 3, we could change this to 73# contextlib.ExitStack() 74class _NullContextManager: 75 def __enter__(self): 76 pass 77 def __exit__(self, *args): 78 pass 79 80 81def _GenerateSequentialFileNames(filename): 82 """Infinite generator of names: 'name.ext', 'name_1.ext', 'name_2.ext', ...""" 83 yield filename 84 base, ext = os.path.splitext(filename) 85 for i in itertools.count(1): 86 yield '%s_%d%s' % (base, i, ext) 87 88 89def _ExtractTestsFromFilters(gtest_filters): 90 """Returns the list of tests specified by the given filters. 91 92 Returns: 93 None if the device should be queried for the test list instead. 94 """ 95 # - means exclude filter. 96 for gtest_filter in gtest_filters: 97 if '-' in gtest_filter: 98 return None 99 # Empty means all tests 100 if not any(gtest_filters): 101 return None 102 103 if len(gtest_filters) == 1: 104 patterns = gtest_filters[0].split(':') 105 # For a single pattern, allow it even if it has a wildcard so long as the 106 # wildcard comes at the end and there is at least one . to prove the scope 107 # is not too large. 108 # This heuristic is not necessarily faster, but normally is. 109 if len(patterns) == 1 and patterns[0].endswith('*'): 110 no_suffix = patterns[0].rstrip('*') 111 if '*' not in no_suffix and '.' in no_suffix: 112 return patterns 113 114 all_patterns = set(gtest_filters[0].split(':')) 115 for gtest_filter in gtest_filters: 116 patterns = gtest_filter.split(':') 117 for pattern in patterns: 118 if '*' in pattern: 119 return None 120 all_patterns = all_patterns.intersection(set(patterns)) 121 return list(all_patterns) 122 123 124def _GetDeviceTimeoutMultiplier(): 125 # Emulated devices typically run 20-150x slower than real-time. 126 # Give a way to control this through the DEVICE_TIMEOUT_MULTIPLIER 127 # environment variable. 128 multiplier = os.getenv("DEVICE_TIMEOUT_MULTIPLIER") 129 if multiplier: 130 return int(multiplier) 131 return 1 132 133 134def _GetLLVMProfilePath(device_coverage_dir, suite, coverage_index): 135 """Gets 'LLVM_PROFILE_FILE' environment variable path. 136 137 Dumping data to ONLY 1 file may cause warning and data overwrite in 138 browsertests, so that pattern "%2m" is used to expand to 2 raw profiles 139 at runtime. 140 141 Args: 142 device_coverage_dir: The directory to generate data on device. 143 suite: Test suite name. 144 coverage_index: The incremental index for this test suite. 145 146 Returns: 147 The path pattern for environment variable 'LLVM_PROFILE_FILE'. 148 """ 149 # "%2m" is used to expand to 2 raw profiles at runtime. 150 # "%c" enables continuous mode. See crbug.com/1468343, crbug.com/1518474 151 # For more details, refer to: 152 # https://clang.llvm.org/docs/SourceBasedCodeCoverage.html 153 return posixpath.join(device_coverage_dir, 154 '_'.join([suite, 155 str(coverage_index), '%2m%c.profraw'])) 156 157 158def _GroupPreTests(tests): 159 pre_tests = dict() 160 other_tests = [] 161 for test in tests: 162 test_name_start = max(test.find('.') + 1, 0) 163 test_name = test[test_name_start:] 164 if test_name_start > 0 and test_name.startswith(_GTEST_PRETEST_PREFIX): 165 test_suite = test[:test_name_start - 1] 166 trim_test = test 167 trim_tests = [test] 168 169 while test_name.startswith(_GTEST_PRETEST_PREFIX): 170 test_name = test_name[len(_GTEST_PRETEST_PREFIX):] 171 trim_test = '%s.%s' % (test_suite, test_name) 172 trim_tests.append(trim_test) 173 174 # The trim test should exist at first place. For example, if a test has 175 # been disabled, there is no need to run PRE_ test with this test. 176 if trim_test in tests and (not trim_test in pre_tests or len( 177 pre_tests[trim_test]) < len(trim_tests)): 178 pre_tests[trim_test] = trim_tests 179 else: 180 other_tests.append(test) 181 return pre_tests, other_tests 182 183 184class _ApkDelegate: 185 def __init__(self, test_instance, env): 186 self._activity = test_instance.activity 187 self._additional_apks = test_instance.additional_apks 188 self._apk_helper = test_instance.apk_helper 189 self._test_apk_incremental_install_json = ( 190 test_instance.test_apk_incremental_install_json) 191 self._package = test_instance.package 192 self._runner = test_instance.runner 193 self._permissions = test_instance.permissions 194 self._suite = test_instance.suite 195 self._component = '%s/%s' % (self._package, self._runner) 196 self._extras = test_instance.extras 197 self._wait_for_java_debugger = test_instance.wait_for_java_debugger 198 self._env = env 199 self._coverage_dir = test_instance.coverage_dir 200 self._coverage_index = 0 201 self._use_existing_test_data = test_instance.use_existing_test_data 202 203 def GetTestDataRoot(self, device): 204 # pylint: disable=no-self-use 205 return posixpath.join(device.GetExternalStoragePath(), 206 'chromium_tests_root') 207 208 def Install(self, device): 209 if self._use_existing_test_data: 210 return 211 212 for additional_apk in self._additional_apks: 213 device.Install(additional_apk, allow_downgrade=True, reinstall=True) 214 215 if self._test_apk_incremental_install_json: 216 installer.Install(device, self._test_apk_incremental_install_json, 217 apk=self._apk_helper, permissions=self._permissions) 218 else: 219 device.Install( 220 self._apk_helper, 221 allow_downgrade=True, 222 reinstall=True, 223 permissions=self._permissions) 224 225 def ResultsDirectory(self, device): # pylint: disable=no-self-use 226 return device.GetExternalStoragePath() 227 228 def Run(self, test, device, flags=None, **kwargs): 229 extras = dict(self._extras) 230 device_api = device.build_version_sdk 231 232 if self._coverage_dir and device_api >= version_codes.LOLLIPOP: 233 # TODO(b/293175593): Use device.ResolveSpecialPath for multi-user 234 device_coverage_dir = ( 235 code_coverage_utils.GetDeviceClangCoverageDir(device)) 236 extras[_EXTRA_COVERAGE_DEVICE_FILE] = _GetLLVMProfilePath( 237 device_coverage_dir, self._suite, self._coverage_index) 238 self._coverage_index += 1 239 240 if ('timeout' in kwargs 241 and gtest_test_instance.EXTRA_SHARD_NANO_TIMEOUT not in extras): 242 # Make sure the instrumentation doesn't kill the test before the 243 # scripts do. The provided timeout value is in seconds, but the 244 # instrumentation deals with nanoseconds because that's how Android 245 # handles time. 246 extras[gtest_test_instance.EXTRA_SHARD_NANO_TIMEOUT] = int( 247 kwargs['timeout'] * _SECONDS_TO_NANOS) 248 249 command_line_file = _NullContextManager() 250 if flags: 251 if len(flags) > _MAX_INLINE_FLAGS_LENGTH: 252 command_line_file = device_temp_file.DeviceTempFile(device.adb) 253 device.WriteFile(command_line_file.name, '_ %s' % flags) 254 extras[_EXTRA_COMMAND_LINE_FILE] = command_line_file.name 255 else: 256 extras[_EXTRA_COMMAND_LINE_FLAGS] = flags 257 258 test_list_file = _NullContextManager() 259 if test: 260 if len(test) > 1: 261 test_list_file = device_temp_file.DeviceTempFile(device.adb) 262 device.WriteFile(test_list_file.name, '\n'.join(test)) 263 extras[_EXTRA_TEST_LIST] = test_list_file.name 264 else: 265 extras[_EXTRA_TEST] = test[0] 266 267 # We need to use GetAppWritablePath here instead of GetExternalStoragePath 268 # since we will not have yet applied legacy storage permission workarounds 269 # on R+. 270 stdout_file = device_temp_file.DeviceTempFile( 271 device.adb, 272 dir=device.GetAppWritablePath(), 273 suffix='.gtest_out', 274 device_utils=device) 275 extras[_EXTRA_STDOUT_FILE] = stdout_file.name 276 277 if self._wait_for_java_debugger: 278 cmd = ['am', 'set-debug-app', '-w', self._package] 279 device.RunShellCommand(cmd, check_return=True) 280 logging.warning('*' * 80) 281 logging.warning('Waiting for debugger to attach to process: %s', 282 self._package) 283 logging.warning('*' * 80) 284 285 with command_line_file, test_list_file, stdout_file: 286 try: 287 device.StartInstrumentation( 288 self._component, extras=extras, raw=False, **kwargs) 289 except device_errors.CommandFailedError: 290 logging.exception('gtest shard failed.') 291 except device_errors.CommandTimeoutError: 292 logging.exception('gtest shard timed out.') 293 except device_errors.DeviceUnreachableError: 294 logging.exception('gtest shard device unreachable.') 295 except Exception: 296 device.ForceStop(self._package) 297 raise 298 finally: 299 if self._coverage_dir and device_api >= version_codes.LOLLIPOP: 300 if not os.path.isdir(self._coverage_dir): 301 os.makedirs(self._coverage_dir) 302 code_coverage_utils.PullAndMaybeMergeClangCoverageFiles( 303 device, device_coverage_dir, self._coverage_dir, 304 str(self._coverage_index)) 305 306 stdout_file_path = stdout_file.name 307 if self._env.force_main_user: 308 stdout_file_path = device.ResolveSpecialPath(stdout_file_path) 309 stdout_file_content = device.ReadFile(stdout_file_path, 310 as_root=self._env.force_main_user) 311 return stdout_file_content.splitlines() 312 313 def PullAppFiles(self, device, files, directory): 314 device_dir = device.GetApplicationDataDirectory(self._package) 315 host_dir = os.path.join(directory, str(device)) 316 for f in files: 317 device_file = posixpath.join(device_dir, f) 318 host_file = os.path.join(host_dir, *f.split(posixpath.sep)) 319 for host_file in _GenerateSequentialFileNames(host_file): 320 if not os.path.exists(host_file): 321 break 322 device.PullFile(device_file, host_file) 323 324 def Clear(self, device): 325 device.ClearApplicationState(self._package, permissions=self._permissions) 326 327 328class _ExeDelegate: 329 330 def __init__(self, tr, test_instance, env): 331 self._host_dist_dir = test_instance.exe_dist_dir 332 self._exe_file_name = os.path.basename( 333 test_instance.exe_dist_dir)[:-len('__dist')] 334 self._device_dist_dir = posixpath.join( 335 constants.TEST_EXECUTABLE_DIR, 336 os.path.basename(test_instance.exe_dist_dir)) 337 self._test_run = tr 338 self._env = env 339 self._suite = test_instance.suite 340 self._coverage_dir = test_instance.coverage_dir 341 self._coverage_index = 0 342 343 def GetTestDataRoot(self, device): 344 # pylint: disable=no-self-use 345 # pylint: disable=unused-argument 346 return posixpath.join(constants.TEST_EXECUTABLE_DIR, 'chromium_tests_root') 347 348 def Install(self, device): 349 # TODO(jbudorick): Look into merging this with normal data deps pushing if 350 # executables become supported on nonlocal environments. 351 device.PushChangedFiles([(self._host_dist_dir, self._device_dist_dir)], 352 delete_device_stale=True, 353 as_root=self._env.force_main_user) 354 355 def ResultsDirectory(self, device): 356 # pylint: disable=no-self-use 357 # pylint: disable=unused-argument 358 return constants.TEST_EXECUTABLE_DIR 359 360 def Run(self, test, device, flags=None, **kwargs): 361 cmd = [posixpath.join(self._device_dist_dir, self._exe_file_name)] 362 363 if test: 364 cmd.append('--gtest_filter=%s' % ':'.join(test)) 365 if flags: 366 # TODO(agrieve): This won't work if multiple flags are passed. 367 cmd.append(flags) 368 cwd = constants.TEST_EXECUTABLE_DIR 369 370 env = { 371 'LD_LIBRARY_PATH': self._device_dist_dir, 372 'UBSAN_OPTIONS': constants.UBSAN_OPTIONS, 373 } 374 375 if self._coverage_dir: 376 device_coverage_dir = ( 377 code_coverage_utils.GetDeviceClangCoverageDir(device)) 378 env['LLVM_PROFILE_FILE'] = _GetLLVMProfilePath( 379 device_coverage_dir, self._suite, self._coverage_index) 380 self._coverage_index += 1 381 382 383 try: 384 gcov_strip_depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP'] 385 external = device.GetExternalStoragePath() 386 env['GCOV_PREFIX'] = '%s/gcov' % external 387 env['GCOV_PREFIX_STRIP'] = gcov_strip_depth 388 except (device_errors.CommandFailedError, KeyError): 389 pass 390 391 # Executable tests return a nonzero exit code on test failure, which is 392 # fine from the test runner's perspective; thus check_return=False. 393 output = device.RunShellCommand( 394 cmd, cwd=cwd, env=env, check_return=False, large_output=True, **kwargs) 395 396 if self._coverage_dir: 397 # TODO(b/293175593): Use device.ResolveSpecialPath for multi-user 398 code_coverage_utils.PullAndMaybeMergeClangCoverageFiles( 399 device, device_coverage_dir, self._coverage_dir, 400 str(self._coverage_index)) 401 402 return output 403 404 def PullAppFiles(self, device, files, directory): 405 pass 406 407 def Clear(self, device): 408 device.KillAll(self._exe_file_name, 409 blocking=True, 410 timeout=30 * _GetDeviceTimeoutMultiplier(), 411 quiet=True) 412 413 414class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): 415 416 def __init__(self, env, test_instance): 417 assert isinstance(env, local_device_environment.LocalDeviceEnvironment) 418 assert isinstance(test_instance, gtest_test_instance.GtestTestInstance) 419 super().__init__(env, test_instance) 420 421 if self._test_instance.apk_helper: 422 self._installed_packages = [ 423 self._test_instance.apk_helper.GetPackageName() 424 ] 425 426 if self._test_instance.apk: 427 self._delegate = _ApkDelegate(self._test_instance, self._env) 428 elif self._test_instance.exe_dist_dir: 429 self._delegate = _ExeDelegate(self, self._test_instance, self._env) 430 if self._test_instance.isolated_script_test_perf_output: 431 self._test_perf_output_filenames = _GenerateSequentialFileNames( 432 self._test_instance.isolated_script_test_perf_output) 433 else: 434 self._test_perf_output_filenames = itertools.repeat(None) 435 self._crashes = set() 436 self._servers = collections.defaultdict(list) 437 438 #override 439 def TestPackage(self): 440 return self._test_instance.suite 441 442 #override 443 def SetUp(self): 444 @local_device_environment.handle_shard_failures_with( 445 on_failure=self._env.DenylistDevice) 446 @trace_event.traced 447 def individual_device_set_up(device, host_device_tuples): 448 def install_apk(dev): 449 # Install test APK. 450 self._delegate.Install(dev) 451 452 def push_test_data(dev): 453 if self._test_instance.use_existing_test_data: 454 return 455 # Push data dependencies. 456 device_root = self._delegate.GetTestDataRoot(dev) 457 if self._env.force_main_user: 458 device_root = dev.ResolveSpecialPath(device_root) 459 host_device_tuples_substituted = [ 460 (h, local_device_test_run.SubstituteDeviceRoot(d, device_root)) 461 for h, d in host_device_tuples] 462 dev.PlaceNomediaFile(device_root) 463 dev.PushChangedFiles( 464 host_device_tuples_substituted, 465 delete_device_stale=True, 466 as_root=self._env.force_main_user, 467 # Some gtest suites, e.g. unit_tests, have data dependencies that 468 # can take longer than the default timeout to push. See 469 # crbug.com/791632 for context. 470 timeout=600 * math.ceil(_GetDeviceTimeoutMultiplier() / 10)) 471 if not host_device_tuples: 472 dev.RemovePath(device_root, 473 force=True, 474 recursive=True, 475 rename=True, 476 as_root=self._env.force_main_user) 477 dev.RunShellCommand(['mkdir', '-p', device_root], 478 check_return=True, 479 as_root=self._env.force_main_user) 480 481 def start_servers(dev): 482 if self._env.disable_test_server: 483 logging.warning('Not starting test server. Some tests may fail.') 484 return 485 486 try: 487 # See https://crbug.com/1030827. 488 # This is a hack that may break in the future. We're relying on the 489 # fact that adb doesn't use ipv6 for it's server, and so doesn't 490 # listen on ipv6, but ssh remote forwarding does. 5037 is the port 491 # number adb uses for its server. 492 if b"[::1]:5037" in subprocess.check_output( 493 "ss -o state listening 'sport = 5037'", shell=True): 494 logging.error( 495 'Test Server cannot be started with a remote-forwarded adb ' 496 'server. Continuing anyways, but some tests may fail.') 497 return 498 except subprocess.CalledProcessError: 499 pass 500 501 self._servers[str(dev)] = [] 502 if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER: 503 self._servers[str(dev)].append( 504 local_test_server_spawner.LocalTestServerSpawner( 505 ports.AllocateTestServerPort(), dev)) 506 507 for s in self._servers[str(dev)]: 508 s.SetUp() 509 510 def bind_crash_handler(step, dev): 511 return lambda: crash_handler.RetryOnSystemCrash(step, dev) 512 513 steps = [ 514 bind_crash_handler(s, device) 515 for s in (install_apk, push_test_data, start_servers) 516 ] 517 if self._env.concurrent_adb: 518 reraiser_thread.RunAsync(steps) 519 else: 520 for step in steps: 521 step() 522 523 self._env.parallel_devices.pMap( 524 individual_device_set_up, 525 self._test_instance.GetDataDependencies()) 526 527 #override 528 def _ShouldShardTestsForDevices(self): 529 """Shard tests across several devices. 530 531 Returns: 532 True if tests should be sharded across several devices, 533 False otherwise. 534 """ 535 return True 536 537 #override 538 def _CreateShardsForDevices(self, tests): 539 """Create shards of tests to run on devices. 540 541 Args: 542 tests: List containing tests or test batches. 543 544 Returns: 545 List of test batches. 546 """ 547 # _crashes are tests that might crash and make the tests in the same shard 548 # following the crashed testcase not run. 549 # Thus we need to create separate shards for each crashed testcase, 550 # so that other tests can be run. 551 device_count = len(self._env.devices) 552 shards = [] 553 554 # Add shards with only one suspect testcase. 555 shards += [[crash] for crash in self._crashes if crash in tests] 556 557 # Delete suspect testcase from tests. 558 tests = [test for test in tests if not test in self._crashes] 559 560 max_shard_size = self._test_instance.test_launcher_batch_limit 561 562 shards.extend(self._PartitionTests(tests, device_count, max_shard_size)) 563 return shards 564 565 #override 566 def _GetTests(self): 567 if self._test_instance.extract_test_list_from_filter: 568 # When the exact list of tests to run is given via command-line (e.g. when 569 # locally iterating on a specific test), skip querying the device (which 570 # takes ~3 seconds). 571 tests = _ExtractTestsFromFilters(self._test_instance.gtest_filters) 572 if tests: 573 return tests 574 575 # Even when there's only one device, it still makes sense to retrieve the 576 # test list so that tests can be split up and run in batches rather than all 577 # at once (since test output is not streamed). 578 @local_device_environment.handle_shard_failures_with( 579 on_failure=self._env.DenylistDevice) 580 def list_tests(dev): 581 timeout = 30 * _GetDeviceTimeoutMultiplier() 582 retries = 1 583 if self._test_instance.wait_for_java_debugger: 584 timeout = None 585 586 flags = [ 587 f for f in self._test_instance.flags if f not in [ 588 '--wait-for-debugger', '--wait-for-java-debugger', 589 '--gtest_also_run_disabled_tests' 590 ] 591 ] 592 flags.append('--gtest_list_tests') 593 594 # TODO(crbug.com/40522854): Remove retries when no longer necessary. 595 for i in range(0, retries + 1): 596 logging.info('flags:') 597 for f in flags: 598 logging.info(' %s', f) 599 600 with self._ArchiveLogcat(dev, 'list_tests'): 601 raw_test_list = crash_handler.RetryOnSystemCrash( 602 lambda d: self._delegate.Run( 603 None, d, flags=' '.join(flags), timeout=timeout), 604 device=dev) 605 606 tests = gtest_test_instance.ParseGTestListTests(raw_test_list) 607 if not tests: 608 logging.info('No tests found. Output:') 609 for l in raw_test_list: 610 logging.info(' %s', l) 611 if i < retries: 612 logging.info('Retrying...') 613 else: 614 break 615 return tests 616 617 # Query all devices in case one fails. 618 test_lists = self._env.parallel_devices.pMap(list_tests).pGet(None) 619 620 # If all devices failed to list tests, raise an exception. 621 # Check that tl is not None and is not empty. 622 if all(not tl for tl in test_lists): 623 raise device_errors.CommandFailedError( 624 'Failed to list tests on any device') 625 tests = list(sorted(set().union(*[set(tl) for tl in test_lists if tl]))) 626 tests = self._test_instance.FilterTests(tests) 627 tests = self._ApplyExternalSharding( 628 tests, self._test_instance.external_shard_index, 629 self._test_instance.total_external_shards) 630 return tests 631 632 #override 633 def _AppendPreTestsForRetry(self, failed_tests, tests): 634 if not self._test_instance.run_pre_tests: 635 return failed_tests 636 637 pre_tests, _ = _GroupPreTests(tests) 638 trim_failed_tests = set() 639 for failed_test in failed_tests: 640 failed_test_name_start = max(failed_test.find('.') + 1, 0) 641 failed_test_name = failed_test[failed_test_name_start:] 642 643 if failed_test_name_start > 0 and failed_test_name.startswith( 644 _GTEST_PRETEST_PREFIX): 645 failed_test_suite = failed_test[:failed_test_name_start - 1] 646 while failed_test_name.startswith(_GTEST_PRETEST_PREFIX): 647 failed_test_name = failed_test_name[len(_GTEST_PRETEST_PREFIX):] 648 failed_test = '%s.%s' % (failed_test_suite, failed_test_name) 649 trim_failed_tests.add(failed_test) 650 651 all_tests = [] 652 for trim_failed_test in trim_failed_tests: 653 if trim_failed_test in tests: 654 if trim_failed_test in pre_tests: 655 all_tests.extend(pre_tests[trim_failed_test]) 656 else: 657 all_tests.append(trim_failed_test) 658 return all_tests 659 660 #override 661 def _GroupTests(self, tests): 662 pre_tests, other_tests = _GroupPreTests(tests) 663 664 all_tests = [] 665 for other_test in other_tests: 666 if not other_test in pre_tests: 667 all_tests.append(other_test) 668 669 # TODO(crbug.com/40200835): Add logic to support grouping tests. 670 # Once grouping logic is added, switch to 'append' from 'extend'. 671 for _, test_list in pre_tests.items(): 672 all_tests.extend(test_list) 673 674 return all_tests 675 676 #override 677 def _GroupTestsAfterSharding(self, tests): 678 return self._GroupTests(tests) 679 680 def _UploadTestArtifacts(self, device, test_artifacts_device_dir): 681 # TODO(jbudorick): Reconcile this with the output manager once 682 # https://codereview.chromium.org/2933993002/ lands. 683 if self._env.force_main_user: 684 test_artifacts_device_dir = device.ResolveSpecialPath( 685 test_artifacts_device_dir) 686 687 with tempfile_ext.NamedTemporaryDirectory() as test_artifacts_host_dir: 688 device.PullFile(test_artifacts_device_dir, 689 test_artifacts_host_dir, 690 as_root=self._env.force_main_user) 691 with tempfile_ext.NamedTemporaryDirectory() as temp_zip_dir: 692 zip_base_name = os.path.join(temp_zip_dir, 'test_artifacts') 693 test_artifacts_zip = shutil.make_archive(zip_base_name, 'zip', 694 test_artifacts_host_dir) 695 link = google_storage_helper.upload( 696 google_storage_helper.unique_name('test_artifacts', device=device), 697 test_artifacts_zip, 698 bucket='%s/test_artifacts' % 699 (self._test_instance.gs_test_artifacts_bucket)) 700 logging.info('Uploading test artifacts to %s.', link) 701 return link 702 703 def _PullRenderTestOutput(self, device, render_test_output_device_dir): 704 # We pull the render tests into a temp directory then copy them over 705 # individually. Otherwise we end up with a temporary directory name 706 # in the host output directory. 707 if self._env.force_main_user: 708 render_test_output_device_dir = device.ResolveSpecialPath( 709 render_test_output_device_dir) 710 711 with tempfile_ext.NamedTemporaryDirectory() as tmp_host_dir: 712 try: 713 device.PullFile(render_test_output_device_dir, 714 tmp_host_dir, 715 as_root=self._env.force_main_user) 716 except device_errors.CommandFailedError: 717 logging.exception('Failed to pull render test output dir %s', 718 render_test_output_device_dir) 719 temp_host_dir = os.path.join( 720 tmp_host_dir, os.path.basename(render_test_output_device_dir)) 721 for output_file in os.listdir(temp_host_dir): 722 src_path = os.path.join(temp_host_dir, output_file) 723 dst_path = os.path.join(self._test_instance.render_test_output_dir, 724 output_file) 725 shutil.move(src_path, dst_path) 726 727 @contextlib.contextmanager 728 def _ArchiveLogcat(self, device, test): 729 if isinstance(test, str): 730 desc = test 731 else: 732 desc = hash(tuple(test)) 733 734 stream_name = 'logcat_%s_shard%s_%s_%s' % ( 735 desc, self._test_instance.external_shard_index, 736 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), device.serial) 737 738 logcat_file = None 739 logmon = None 740 try: 741 with self._env.output_manager.ArchivedTempfile(stream_name, 742 'logcat') as logcat_file: 743 symbolizer = stack_symbolizer.PassThroughSymbolizerPool( 744 device.product_cpu_abi) 745 with symbolizer: 746 with logcat_monitor.LogcatMonitor( 747 device.adb, 748 filter_specs=local_device_environment.LOGCAT_FILTERS, 749 output_file=logcat_file.name, 750 transform_func=symbolizer.TransformLines, 751 check_error=False) as logmon: 752 with contextlib_ext.Optional(trace_event.trace(str(test)), 753 self._env.trace_output): 754 yield logcat_file 755 finally: 756 if logmon: 757 logmon.Close() 758 if logcat_file and logcat_file.Link(): 759 logging.critical('Logcat saved to %s', logcat_file.Link()) 760 761 #override 762 def _GetUniqueTestName(self, test): 763 return gtest_test_instance.TestNameWithoutDisabledPrefix(test) 764 765 #override 766 def _RunTest(self, device, test): 767 # Run the test. 768 timeout = self._test_instance.shard_timeout * _GetDeviceTimeoutMultiplier() 769 if self._test_instance.wait_for_java_debugger: 770 timeout = None 771 if self._test_instance.store_tombstones: 772 tombstones.ClearAllTombstones(device) 773 test_perf_output_filename = next(self._test_perf_output_filenames) 774 775 if self._test_instance.isolated_script_test_output: 776 suffix = '.json' 777 else: 778 suffix = '.xml' 779 780 with device_temp_file.DeviceTempFile( 781 adb=device.adb, 782 dir=self._delegate.ResultsDirectory(device), 783 suffix=suffix, 784 device_utils=device) as device_tmp_results_file: 785 with contextlib_ext.Optional( 786 device_temp_file.NamedDeviceTemporaryDirectory( 787 adb=device.adb, 788 dir=device.GetExternalStoragePath(), 789 device_utils=device), 790 self._test_instance.gs_test_artifacts_bucket) as test_artifacts_dir: 791 with contextlib_ext.Optional( 792 device_temp_file.DeviceTempFile( 793 adb=device.adb, 794 dir=self._delegate.ResultsDirectory(device), 795 device_utils=device), 796 test_perf_output_filename) as isolated_script_test_perf_output: 797 with contextlib_ext.Optional( 798 device_temp_file.NamedDeviceTemporaryDirectory( 799 adb=device.adb, 800 dir=device.GetExternalStoragePath(), 801 device_utils=device), 802 self._test_instance.render_test_output_dir 803 ) as render_test_output_dir: 804 805 flags = list(self._test_instance.flags) 806 if self._test_instance.enable_xml_result_parsing: 807 flags.append('--gtest_output=xml:%s' % 808 device_tmp_results_file.name) 809 810 if self._test_instance.gs_test_artifacts_bucket: 811 flags.append('--test_artifacts_dir=%s' % test_artifacts_dir.name) 812 813 if self._test_instance.isolated_script_test_output: 814 flags.append('--isolated-script-test-output=%s' % 815 device_tmp_results_file.name) 816 817 if test_perf_output_filename: 818 flags.append('--isolated_script_test_perf_output=%s' % 819 isolated_script_test_perf_output.name) 820 821 if self._test_instance.render_test_output_dir: 822 flags.append('--render-test-output-dir=%s' % 823 render_test_output_dir.name) 824 825 logging.info('flags:') 826 for f in flags: 827 logging.info(' %s', f) 828 829 with self._ArchiveLogcat(device, test) as logcat_file: 830 output = self._delegate.Run(test, 831 device, 832 flags=' '.join(flags), 833 timeout=timeout, 834 retries=0) 835 836 if self._test_instance.enable_xml_result_parsing: 837 file_path = device_tmp_results_file.name 838 if self._env.force_main_user: 839 file_path = device.ResolveSpecialPath(file_path) 840 try: 841 gtest_xml = device.ReadFile(file_path, 842 as_root=self._env.force_main_user) 843 except device_errors.CommandFailedError: 844 logging.exception('Failed to pull gtest results XML file %s', 845 file_path) 846 gtest_xml = None 847 848 if self._test_instance.isolated_script_test_output: 849 file_path = device_tmp_results_file.name 850 if self._env.force_main_user: 851 file_path = device.ResolveSpecialPath(file_path) 852 try: 853 gtest_json = device.ReadFile(file_path, 854 as_root=self._env.force_main_user) 855 except device_errors.CommandFailedError: 856 logging.exception('Failed to pull gtest results JSON file %s', 857 file_path) 858 gtest_json = None 859 860 if test_perf_output_filename: 861 file_path = isolated_script_test_perf_output.name 862 if self._env.force_main_user: 863 file_path = device.ResolveSpecialPath(file_path) 864 try: 865 device.PullFile(file_path, 866 test_perf_output_filename, 867 as_root=self._env.force_main_user) 868 except device_errors.CommandFailedError: 869 logging.exception('Failed to pull chartjson results %s', 870 file_path) 871 872 test_artifacts_url = None 873 if test_artifacts_dir: 874 test_artifacts_url = self._UploadTestArtifacts( 875 device, test_artifacts_dir.name) 876 877 if render_test_output_dir: 878 self._PullRenderTestOutput(device, render_test_output_dir.name) 879 880 for s in self._servers[str(device)]: 881 s.Reset() 882 if self._test_instance.app_files: 883 self._delegate.PullAppFiles(device, self._test_instance.app_files, 884 self._test_instance.app_file_dir) 885 if not self._env.skip_clear_data: 886 self._delegate.Clear(device) 887 888 for l in output: 889 logging.info(l) 890 891 # Parse the output. 892 # TODO(jbudorick): Transition test scripts away from parsing stdout. 893 if self._test_instance.enable_xml_result_parsing: 894 results = gtest_test_instance.ParseGTestXML(gtest_xml) 895 elif self._test_instance.isolated_script_test_output: 896 results = gtest_test_instance.ParseGTestJSON(gtest_json) 897 else: 898 results = gtest_test_instance.ParseGTestOutput( 899 output, self._test_instance.symbolizer, device.product_cpu_abi) 900 901 tombstones_url = None 902 for r in results: 903 if logcat_file: 904 r.SetLink('logcat', logcat_file.Link()) 905 906 if self._test_instance.gs_test_artifacts_bucket: 907 r.SetLink('test_artifacts', test_artifacts_url) 908 909 if r.GetType() == base_test_result.ResultType.CRASH: 910 self._crashes.add(r.GetName()) 911 if self._test_instance.store_tombstones: 912 if not tombstones_url: 913 resolved_tombstones = tombstones.ResolveTombstones( 914 device, 915 resolve_all_tombstones=True, 916 include_stack_symbols=False, 917 wipe_tombstones=True) 918 stream_name = 'tombstones_%s_%s' % ( 919 time.strftime('%Y%m%dT%H%M%S', time.localtime()), 920 device.serial) 921 tombstones_url = logdog_helper.text( 922 stream_name, '\n'.join(resolved_tombstones)) 923 r.SetLink('tombstones', tombstones_url) 924 925 tests_stripped_disabled_prefix = set() 926 for t in test: 927 tests_stripped_disabled_prefix.add( 928 gtest_test_instance.TestNameWithoutDisabledPrefix(t)) 929 not_run_tests = tests_stripped_disabled_prefix.difference( 930 set(r.GetName() for r in results)) 931 932 if self._test_instance.extract_test_list_from_filter: 933 # A test string might end with a * in this mode, and so may not match any 934 # r.GetName() for the set difference. It's possible a filter like foo.* 935 # can match two tests, ie foo.baz and foo.foo. 936 # When running it's possible Foo.baz is ran, foo.foo is not, but the test 937 # list foo.* will not be reran as at least one result matched it. 938 not_run_tests = { 939 t 940 for t in not_run_tests 941 if not any(fnmatch.fnmatch(r.GetName(), t) for r in results) 942 } 943 944 return results, list(not_run_tests) if results else None 945 946 #override 947 def TearDown(self): 948 # By default, teardown will invoke ADB. When receiving SIGTERM due to a 949 # timeout, there's a high probability that ADB is non-responsive. In these 950 # cases, sending an ADB command will potentially take a long time to time 951 # out. Before this happens, the process will be hard-killed for not 952 # responding to SIGTERM fast enough. 953 if self._received_sigterm: 954 return 955 956 @local_device_environment.handle_shard_failures 957 @trace_event.traced 958 def individual_device_tear_down(dev): 959 for s in self._servers.get(str(dev), []): 960 s.TearDown() 961 962 self._env.parallel_devices.pMap(individual_device_tear_down) 963