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