• 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  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