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