• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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