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