Index: build/android/pylib/local/device/local_device_environment.py |
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py |
index f8cd53048f097c66f444d10b995e153b5c1ddd66..431614377efa68bd4cffecc6158f13d0d6673965 100644 |
--- a/build/android/pylib/local/device/local_device_environment.py |
+++ b/build/android/pylib/local/device/local_device_environment.py |
@@ -3,12 +3,14 @@ |
# found in the LICENSE file. |
import datetime |
+import functools |
import logging |
import os |
import shutil |
import tempfile |
import threading |
+from devil import base_error |
from devil.android import device_blacklist |
from devil.android import device_errors |
from devil.android import device_list |
@@ -25,6 +27,50 @@ def _DeviceCachePath(device): |
return os.path.join(constants.GetOutDirectory(), file_name) |
+def handle_shard_failures(f): |
+ """A decorator that handles device failures for per-device functions. |
+ |
+ Args: |
+ f: the function being decorated. The function must take at least one |
+ argument, and that argument must be the device. |
+ """ |
+ return handle_shard_failures_with(None)(f) |
+ |
+ |
+# TODO(jbudorick): Refactor this to work as a decorator or context manager. |
+def handle_shard_failures_with(on_failure): |
+ """A decorator that handles device failures for per-device functions. |
+ |
+ This calls on_failure in the event of a failure. |
+ |
+ Args: |
+ f: the function being decorated. The function must take at least one |
+ argument, and that argument must be the device. |
+ on_failure: A binary function to call on failure. |
+ """ |
+ def decorator(f): |
+ @functools.wraps(f) |
+ def wrapper(dev, *args, **kwargs): |
+ try: |
+ return f(dev, *args, **kwargs) |
+ except device_errors.CommandTimeoutError: |
+ logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev)) |
+ except device_errors.DeviceUnreachableError: |
+ logging.exception('Shard died: %s(%s)', f.__name__, str(dev)) |
+ except base_error.BaseError: |
+ logging.exception('Shard failed: %s(%s)', f.__name__, str(dev)) |
+ except SystemExit: |
+ logging.exception('Shard killed: %s(%s)', f.__name__, str(dev)) |
+ raise |
+ if on_failure: |
+ on_failure(dev, f.__name__) |
+ return None |
+ |
+ return wrapper |
+ |
+ return decorator |
+ |
+ |
class LocalDeviceEnvironment(environment.Environment): |
def __init__(self, args, _error_func): |
@@ -67,8 +113,12 @@ class LocalDeviceEnvironment(environment.Environment): |
if not self._devices: |
raise device_errors.NoDevicesError |
- if self._enable_device_cache: |
- for d in self._devices: |
+ if self._logcat_output_file: |
+ self._logcat_output_dir = tempfile.mkdtemp() |
+ |
+ @handle_shard_failures_with(on_failure=self.BlacklistDevice) |
+ def prepare_device(d): |
+ if self._enable_device_cache: |
cache_path = _DeviceCachePath(d) |
if os.path.exists(cache_path): |
logging.info('Using device cache: %s', cache_path) |
@@ -76,10 +126,8 @@ class LocalDeviceEnvironment(environment.Environment): |
d.LoadCacheData(f.read()) |
# Delete cached file so that any exceptions cause it to be cleared. |
os.unlink(cache_path) |
- if self._logcat_output_file: |
- self._logcat_output_dir = tempfile.mkdtemp() |
- if self._logcat_output_dir: |
- for d in self._devices: |
+ |
+ if self._logcat_output_dir: |
logcat_file = os.path.join( |
self._logcat_output_dir, |
'%s_%s' % (d.adb.GetDeviceSerial(), |
@@ -89,6 +137,8 @@ class LocalDeviceEnvironment(environment.Environment): |
self._logcat_monitors.append(monitor) |
monitor.Start() |
+ self.parallel_devices.pMap(prepare_device) |
+ |
@property |
def blacklist(self): |
return self._blacklist |
@@ -121,17 +171,27 @@ class LocalDeviceEnvironment(environment.Environment): |
#override |
def TearDown(self): |
- # Write the cache even when not using it so that it will be ready the first |
- # time that it is enabled. Writing it every time is also necessary so that |
- # an invalid cache can be flushed just by disabling it for one run. |
- for d in self._devices: |
+ @handle_shard_failures_with(on_failure=self.BlacklistDevice) |
+ def tear_down_device(d): |
+ # Write the cache even when not using it so that it will be ready the |
+ # first time that it is enabled. Writing it every time is also necessary |
+ # so that an invalid cache can be flushed just by disabling it for one |
+ # run. |
cache_path = _DeviceCachePath(d) |
with open(cache_path, 'w') as f: |
f.write(d.DumpCacheData()) |
logging.info('Wrote device cache: %s', cache_path) |
+ |
+ self.parallel_devices.pMap(tear_down_device) |
+ |
for m in self._logcat_monitors: |
- m.Stop() |
- m.Close() |
+ try: |
+ m.Stop() |
+ m.Close() |
+ except base_error.BaseError: |
+ logging.exception('Failed to stop logcat monitor for %s', |
+ m.adb.GetDeviceSerial()) |
+ |
if self._logcat_output_file: |
file_utils.MergeFiles( |
self._logcat_output_file, |