tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

local_device_environment.py (11974B)


      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 
      6 import datetime
      7 import functools
      8 import logging
      9 import os
     10 import shutil
     11 import tempfile
     12 import threading
     13 
     14 import devil_chromium
     15 from devil import base_error
     16 from devil.android import device_denylist
     17 from devil.android import device_errors
     18 from devil.android import device_utils
     19 from devil.android import logcat_monitor
     20 from devil.android.sdk import adb_wrapper
     21 from devil.utils import file_utils
     22 from devil.utils import parallelizer
     23 from pylib import constants
     24 from pylib.constants import host_paths
     25 from pylib.base import environment
     26 from pylib.utils import instrumentation_tracing
     27 from py_trace_event import trace_event
     28 
     29 
     30 LOGCAT_FILTERS = [
     31  'chromium:v',
     32  'cr_*:v',
     33  'DEBUG:I',
     34  'StrictMode:D',
     35 ]
     36 
     37 SYSTEM_USER_ID = 0
     38 
     39 
     40 def _DeviceCachePath(device):
     41  file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial()
     42  return os.path.join(constants.GetOutDirectory(), file_name)
     43 
     44 
     45 def handle_shard_failures(f):
     46  """A decorator that handles device failures for per-device functions.
     47 
     48  Args:
     49    f: the function being decorated. The function must take at least one
     50      argument, and that argument must be the device.
     51  """
     52  return handle_shard_failures_with(None)(f)
     53 
     54 
     55 # TODO(jbudorick): Refactor this to work as a decorator or context manager.
     56 def handle_shard_failures_with(on_failure):
     57  """A decorator that handles device failures for per-device functions.
     58 
     59  This calls on_failure in the event of a failure.
     60 
     61  Args:
     62    f: the function being decorated. The function must take at least one
     63      argument, and that argument must be the device.
     64    on_failure: A binary function to call on failure.
     65  """
     66  def decorator(f):
     67    @functools.wraps(f)
     68    def wrapper(dev, *args, **kwargs):
     69      try:
     70        return f(dev, *args, **kwargs)
     71      except device_errors.CommandTimeoutError:
     72        logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev))
     73      except device_errors.DeviceUnreachableError:
     74        logging.exception('Shard died: %s(%s)', f.__name__, str(dev))
     75      except base_error.BaseError:
     76        logging.exception('Shard failed: %s(%s)', f.__name__, str(dev))
     77      except SystemExit:
     78        logging.exception('Shard killed: %s(%s)', f.__name__, str(dev))
     79        raise
     80      if on_failure:
     81        on_failure(dev, f.__name__)
     82      return None
     83 
     84    return wrapper
     85 
     86  return decorator
     87 
     88 
     89 # TODO(crbug.com/40799394): After Telemetry is supported by python3 we can
     90 # re-add super without arguments in this script.
     91 # pylint: disable=super-with-arguments
     92 class LocalDeviceEnvironment(environment.Environment):
     93 
     94  def __init__(self, args, output_manager, _error_func):
     95    super(LocalDeviceEnvironment, self).__init__(output_manager)
     96    self._current_try = 0
     97    self._denylist = (device_denylist.Denylist(args.denylist_file)
     98                      if args.denylist_file else None)
     99    self._device_serials = args.test_devices
    100    self._devices_lock = threading.Lock()
    101    self._devices = None
    102    self._concurrent_adb = args.enable_concurrent_adb
    103    self._enable_device_cache = args.enable_device_cache
    104    self._logcat_monitors = []
    105    self._logcat_output_dir = args.logcat_output_dir
    106    self._logcat_output_file = args.logcat_output_file
    107    self._max_tries = 1 + args.num_retries
    108    self._preferred_abis = None
    109    self._recover_devices = args.recover_devices
    110    self._skip_clear_data = args.skip_clear_data
    111    self._trace_output = None
    112    # Must check if arg exist because this class is used by
    113    # //third_party/catapult's browser_options.py
    114    if hasattr(args, 'trace_output'):
    115      self._trace_output = args.trace_output
    116    self._trace_all = None
    117    if hasattr(args, 'trace_all'):
    118      self._trace_all = args.trace_all
    119    self._force_main_user = False
    120    if hasattr(args, 'force_main_user'):
    121      self._force_main_user = args.force_main_user
    122    self._use_persistent_shell = args.use_persistent_shell
    123    self._disable_test_server = args.disable_test_server
    124 
    125    use_local_devil_tools = False
    126    if hasattr(args, 'use_local_devil_tools'):
    127      use_local_devil_tools = args.use_local_devil_tools
    128 
    129    devil_chromium.Initialize(output_directory=constants.GetOutDirectory(),
    130                              adb_path=args.adb_path,
    131                              use_local_devil_tools=use_local_devil_tools)
    132 
    133    # Some things such as Forwarder require ADB to be in the environment path,
    134    # while others like Devil's bundletool.py require Java on the path.
    135    adb_dir = os.path.dirname(adb_wrapper.AdbWrapper.GetAdbPath())
    136    if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
    137      os.environ['PATH'] = os.pathsep.join(
    138          [adb_dir, host_paths.JAVA_PATH, os.environ['PATH']])
    139 
    140  #override
    141  def SetUp(self):
    142    if self.trace_output and self._trace_all:
    143      to_include = [r"pylib\..*", r"devil\..*", "__main__"]
    144      to_exclude = ["logging"]
    145      instrumentation_tracing.start_instrumenting(self.trace_output, to_include,
    146                                                  to_exclude)
    147    elif self.trace_output:
    148      self.EnableTracing()
    149 
    150  # Must be called before accessing |devices|.
    151  def SetPreferredAbis(self, abis):
    152    assert self._devices is None
    153    self._preferred_abis = abis
    154 
    155  def _InitDevices(self):
    156    device_arg = []
    157    if self._device_serials:
    158      device_arg = self._device_serials
    159 
    160    self._devices = device_utils.DeviceUtils.HealthyDevices(
    161        self._denylist,
    162        retries=5,
    163        enable_usb_resets=True,
    164        enable_device_files_cache=self._enable_device_cache,
    165        default_retries=self._max_tries - 1,
    166        device_arg=device_arg,
    167        abis=self._preferred_abis,
    168        persistent_shell=self._use_persistent_shell)
    169 
    170    if self._logcat_output_file:
    171      self._logcat_output_dir = tempfile.mkdtemp()
    172 
    173    @handle_shard_failures_with(on_failure=self.DenylistDevice)
    174    def prepare_device(d):
    175      d.WaitUntilFullyBooted()
    176 
    177      if self._force_main_user:
    178        # Ensure the current user is the main user (the first real human user).
    179        main_user = d.GetMainUser()
    180        if d.GetCurrentUser() != main_user:
    181          logging.info('Switching to the main user with id %s', main_user)
    182          d.SwitchUser(main_user)
    183        d.target_user = main_user
    184      elif d.GetCurrentUser() != SYSTEM_USER_ID:
    185        # TODO(b/293175593): Remove this after "force_main_user" works fine.
    186        # Use system user to run tasks to avoid "/sdcard "accessing issue
    187        # due to multiple-users. For details, see
    188        # https://source.android.com/docs/devices/admin/multi-user-testing
    189        logging.info('Switching to user with id %s', SYSTEM_USER_ID)
    190        d.SwitchUser(SYSTEM_USER_ID)
    191 
    192      if self._enable_device_cache:
    193        cache_path = _DeviceCachePath(d)
    194        if os.path.exists(cache_path):
    195          logging.info('Using device cache: %s', cache_path)
    196          with open(cache_path) as f:
    197            d.LoadCacheData(f.read())
    198          # Delete cached file so that any exceptions cause it to be cleared.
    199          os.unlink(cache_path)
    200 
    201      if self._logcat_output_dir:
    202        logcat_file = os.path.join(
    203            self._logcat_output_dir,
    204            '%s_%s' % (d.adb.GetDeviceSerial(),
    205                       datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%S')))
    206        monitor = logcat_monitor.LogcatMonitor(d.adb,
    207                                               clear=True,
    208                                               output_file=logcat_file,
    209                                               check_error=False)
    210        self._logcat_monitors.append(monitor)
    211        monitor.Start()
    212 
    213    self.parallel_devices.pMap(prepare_device)
    214 
    215  @property
    216  def current_try(self):
    217    return self._current_try
    218 
    219  def IncrementCurrentTry(self):
    220    self._current_try += 1
    221 
    222  def ResetCurrentTry(self):
    223    self._current_try = 0
    224 
    225  @property
    226  def denylist(self):
    227    return self._denylist
    228 
    229  @property
    230  def concurrent_adb(self):
    231    return self._concurrent_adb
    232 
    233  @property
    234  def devices(self):
    235    # Initialize lazily so that host-only tests do not fail when no devices are
    236    # attached.
    237    if self._devices is None:
    238      self._InitDevices()
    239    return self._devices
    240 
    241  @property
    242  def max_tries(self):
    243    return self._max_tries
    244 
    245  @property
    246  def parallel_devices(self):
    247    return parallelizer.SyncParallelizer(self.devices)
    248 
    249  @property
    250  def recover_devices(self):
    251    return self._recover_devices
    252 
    253  @property
    254  def skip_clear_data(self):
    255    return self._skip_clear_data
    256 
    257  @property
    258  def trace_output(self):
    259    return self._trace_output
    260 
    261  @property
    262  def disable_test_server(self):
    263    return self._disable_test_server
    264 
    265  @property
    266  def force_main_user(self):
    267    return self._force_main_user
    268 
    269  #override
    270  def TearDown(self):
    271    if self.trace_output and self._trace_all:
    272      instrumentation_tracing.stop_instrumenting()
    273    elif self.trace_output:
    274      self.DisableTracing()
    275 
    276    # By default, teardown will invoke ADB. When receiving SIGTERM due to a
    277    # timeout, there's a high probability that ADB is non-responsive. In these
    278    # cases, sending an ADB command will potentially take a long time to time
    279    # out. Before this happens, the process will be hard-killed for not
    280    # responding to SIGTERM fast enough.
    281    if self._received_sigterm:
    282      return
    283 
    284    if not self._devices:
    285      return
    286 
    287    @handle_shard_failures_with(on_failure=self.DenylistDevice)
    288    def tear_down_device(d):
    289      # Write the cache even when not using it so that it will be ready the
    290      # first time that it is enabled. Writing it every time is also necessary
    291      # so that an invalid cache can be flushed just by disabling it for one
    292      # run.
    293      cache_path = _DeviceCachePath(d)
    294      if os.path.exists(os.path.dirname(cache_path)):
    295        with open(cache_path, 'w') as f:
    296          f.write(d.DumpCacheData())
    297          logging.info('Wrote device cache: %s', cache_path)
    298      else:
    299        logging.warning(
    300            'Unable to write device cache as %s directory does not exist',
    301            os.path.dirname(cache_path))
    302 
    303    self.parallel_devices.pMap(tear_down_device)
    304 
    305    for m in self._logcat_monitors:
    306      try:
    307        m.Stop()
    308        m.Close()
    309        _, temp_path = tempfile.mkstemp()
    310        with open(m.output_file, 'r') as infile:
    311          with open(temp_path, 'w') as outfile:
    312            for line in infile:
    313              outfile.write('Device(%s) %s' % (m.adb.GetDeviceSerial(), line))
    314        shutil.move(temp_path, m.output_file)
    315      except base_error.BaseError:
    316        logging.exception('Failed to stop logcat monitor for %s',
    317                          m.adb.GetDeviceSerial())
    318      except IOError:
    319        logging.exception('Failed to locate logcat for device %s',
    320                          m.adb.GetDeviceSerial())
    321 
    322    if self._logcat_output_file:
    323      file_utils.MergeFiles(
    324          self._logcat_output_file,
    325          [m.output_file for m in self._logcat_monitors
    326           if os.path.exists(m.output_file)])
    327      shutil.rmtree(self._logcat_output_dir)
    328 
    329  def DenylistDevice(self, device, reason='local_device_failure'):
    330    device_serial = device.adb.GetDeviceSerial()
    331    if self._denylist:
    332      self._denylist.Extend([device_serial], reason=reason)
    333    with self._devices_lock:
    334      self._devices = [d for d in self._devices if str(d) != device_serial]
    335    logging.error('Device %s denylisted: %s', device_serial, reason)
    336    if not self._devices:
    337      raise device_errors.NoDevicesError(
    338          'All devices were denylisted due to errors')
    339 
    340  @staticmethod
    341  def DisableTracing():
    342    if not trace_event.trace_is_enabled():
    343      logging.warning('Tracing is not running.')
    344    else:
    345      trace_event.trace_disable()
    346 
    347  def EnableTracing(self):
    348    if trace_event.trace_is_enabled():
    349      logging.warning('Tracing is already running.')
    350    else:
    351      trace_event.trace_enable(self._trace_output)