Commit 84c46fae authored by jbudorick's avatar jbudorick Committed by Commit bot

[Android] Blacklist devices on failures during environment set up + tear down.

Requires https://codereview.chromium.org/2144103002/

BUG=627939

Review-Url: https://codereview.chromium.org/2144823003
Cr-Commit-Position: refs/heads/master@{#407594}
parent 8a2b06d8
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
# found in the LICENSE file. # found in the LICENSE file.
import datetime import datetime
import functools
import logging import logging
import os import os
import shutil import shutil
import tempfile import tempfile
import threading import threading
from devil import base_error
from devil.android import device_blacklist from devil.android import device_blacklist
from devil.android import device_errors from devil.android import device_errors
from devil.android import device_list from devil.android import device_list
...@@ -25,6 +27,50 @@ def _DeviceCachePath(device): ...@@ -25,6 +27,50 @@ def _DeviceCachePath(device):
return os.path.join(constants.GetOutDirectory(), file_name) 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): class LocalDeviceEnvironment(environment.Environment):
def __init__(self, args, _error_func): def __init__(self, args, _error_func):
...@@ -67,8 +113,12 @@ class LocalDeviceEnvironment(environment.Environment): ...@@ -67,8 +113,12 @@ class LocalDeviceEnvironment(environment.Environment):
if not self._devices: if not self._devices:
raise device_errors.NoDevicesError raise device_errors.NoDevicesError
if self._enable_device_cache: if self._logcat_output_file:
for d in self._devices: 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) cache_path = _DeviceCachePath(d)
if os.path.exists(cache_path): if os.path.exists(cache_path):
logging.info('Using device cache: %s', cache_path) logging.info('Using device cache: %s', cache_path)
...@@ -76,10 +126,8 @@ class LocalDeviceEnvironment(environment.Environment): ...@@ -76,10 +126,8 @@ class LocalDeviceEnvironment(environment.Environment):
d.LoadCacheData(f.read()) d.LoadCacheData(f.read())
# Delete cached file so that any exceptions cause it to be cleared. # Delete cached file so that any exceptions cause it to be cleared.
os.unlink(cache_path) os.unlink(cache_path)
if self._logcat_output_file:
self._logcat_output_dir = tempfile.mkdtemp() if self._logcat_output_dir:
if self._logcat_output_dir:
for d in self._devices:
logcat_file = os.path.join( logcat_file = os.path.join(
self._logcat_output_dir, self._logcat_output_dir,
'%s_%s' % (d.adb.GetDeviceSerial(), '%s_%s' % (d.adb.GetDeviceSerial(),
...@@ -89,6 +137,8 @@ class LocalDeviceEnvironment(environment.Environment): ...@@ -89,6 +137,8 @@ class LocalDeviceEnvironment(environment.Environment):
self._logcat_monitors.append(monitor) self._logcat_monitors.append(monitor)
monitor.Start() monitor.Start()
self.parallel_devices.pMap(prepare_device)
@property @property
def blacklist(self): def blacklist(self):
return self._blacklist return self._blacklist
...@@ -121,17 +171,27 @@ class LocalDeviceEnvironment(environment.Environment): ...@@ -121,17 +171,27 @@ class LocalDeviceEnvironment(environment.Environment):
#override #override
def TearDown(self): def TearDown(self):
# Write the cache even when not using it so that it will be ready the first @handle_shard_failures_with(on_failure=self.BlacklistDevice)
# time that it is enabled. Writing it every time is also necessary so that def tear_down_device(d):
# an invalid cache can be flushed just by disabling it for one run. # Write the cache even when not using it so that it will be ready the
for d in self._devices: # 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) cache_path = _DeviceCachePath(d)
with open(cache_path, 'w') as f: with open(cache_path, 'w') as f:
f.write(d.DumpCacheData()) f.write(d.DumpCacheData())
logging.info('Wrote device cache: %s', cache_path) logging.info('Wrote device cache: %s', cache_path)
self.parallel_devices.pMap(tear_down_device)
for m in self._logcat_monitors: for m in self._logcat_monitors:
m.Stop() try:
m.Close() 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: if self._logcat_output_file:
file_utils.MergeFiles( file_utils.MergeFiles(
self._logcat_output_file, self._logcat_output_file,
......
...@@ -232,7 +232,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -232,7 +232,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def SetUp(self): def SetUp(self):
@local_device_test_run.handle_shard_failures_with( @local_device_environment.handle_shard_failures_with(
on_failure=self._env.BlacklistDevice) on_failure=self._env.BlacklistDevice)
def individual_device_set_up(dev): def individual_device_set_up(dev):
def install_apk(): def install_apk():
...@@ -313,7 +313,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -313,7 +313,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
# Even when there's only one device, it still makes sense to retrieve the # Even when there's only one device, it still makes sense to retrieve the
# test list so that tests can be split up and run in batches rather than all # test list so that tests can be split up and run in batches rather than all
# at once (since test output is not streamed). # at once (since test output is not streamed).
@local_device_test_run.handle_shard_failures_with( @local_device_environment.handle_shard_failures_with(
on_failure=self._env.BlacklistDevice) on_failure=self._env.BlacklistDevice)
def list_tests(dev): def list_tests(dev):
raw_test_list = self._delegate.Run( raw_test_list = self._delegate.Run(
...@@ -363,7 +363,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -363,7 +363,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
#override #override
def TearDown(self): def TearDown(self):
@local_device_test_run.handle_shard_failures @local_device_environment.handle_shard_failures
def individual_device_tear_down(dev): def individual_device_tear_down(dev):
for s in self._servers.get(str(dev), []): for s in self._servers.get(str(dev), []):
s.TearDown() s.TearDown()
......
...@@ -13,6 +13,7 @@ from devil.android import flag_changer ...@@ -13,6 +13,7 @@ from devil.android import flag_changer
from devil.utils import reraiser_thread from devil.utils import reraiser_thread
from pylib import valgrind_tools from pylib import valgrind_tools
from pylib.base import base_test_result from pylib.base import base_test_result
from pylib.local.device import local_device_environment
from pylib.local.device import local_device_test_run from pylib.local.device import local_device_test_run
...@@ -67,7 +68,7 @@ class LocalDeviceInstrumentationTestRun( ...@@ -67,7 +68,7 @@ class LocalDeviceInstrumentationTestRun(
else: else:
return d return d
@local_device_test_run.handle_shard_failures_with( @local_device_environment.handle_shard_failures_with(
self._env.BlacklistDevice) self._env.BlacklistDevice)
def individual_device_set_up(dev, host_device_tuples): def individual_device_set_up(dev, host_device_tuples):
def install_apk(): def install_apk():
...@@ -148,7 +149,7 @@ class LocalDeviceInstrumentationTestRun( ...@@ -148,7 +149,7 @@ class LocalDeviceInstrumentationTestRun(
self._test_instance.GetDataDependencies()) self._test_instance.GetDataDependencies())
def TearDown(self): def TearDown(self):
@local_device_test_run.handle_shard_failures_with( @local_device_environment.handle_shard_failures_with(
self._env.BlacklistDevice) self._env.BlacklistDevice)
def individual_device_tear_down(dev): def individual_device_tear_down(dev):
if str(dev) in self._flag_changers: if str(dev) in self._flag_changers:
......
...@@ -25,6 +25,7 @@ from devil.utils import parallelizer ...@@ -25,6 +25,7 @@ from devil.utils import parallelizer
from pylib import constants from pylib import constants
from pylib.base import base_test_result from pylib.base import base_test_result
from pylib.constants import host_paths from pylib.constants import host_paths
from pylib.local.device import local_device_environment
from pylib.local.device import local_device_test_run from pylib.local.device import local_device_test_run
...@@ -78,7 +79,7 @@ class TestShard(object): ...@@ -78,7 +79,7 @@ class TestShard(object):
self._timeout = timeout self._timeout = timeout
self._heart_beat = HeartBeat(self) self._heart_beat = HeartBeat(self)
@local_device_test_run.handle_shard_failures @local_device_environment.handle_shard_failures
def RunTestsOnShard(self): def RunTestsOnShard(self):
results = base_test_result.TestRunResults() results = base_test_result.TestRunResults()
for test in self._tests: for test in self._tests:
......
...@@ -3,20 +3,18 @@ ...@@ -3,20 +3,18 @@
# found in the LICENSE file. # found in the LICENSE file.
import fnmatch import fnmatch
import functools
import imp import imp
import logging import logging
import signal import signal
import thread import thread
import threading import threading
from devil import base_error
from devil.android import device_errors
from devil.utils import signal_handler from devil.utils import signal_handler
from pylib import valgrind_tools from pylib import valgrind_tools
from pylib.base import base_test_result from pylib.base import base_test_result
from pylib.base import test_run from pylib.base import test_run
from pylib.base import test_collection from pylib.base import test_collection
from pylib.local.device import local_device_environment
def IncrementalInstall(device, apk_helper, installer_script): def IncrementalInstall(device, apk_helper, installer_script):
...@@ -41,49 +39,6 @@ def IncrementalInstall(device, apk_helper, installer_script): ...@@ -41,49 +39,6 @@ def IncrementalInstall(device, apk_helper, installer_script):
permissions=None) # Auto-grant permissions from manifest. permissions=None) # Auto-grant permissions from manifest.
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)
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 LocalDeviceTestRun(test_run.TestRun): class LocalDeviceTestRun(test_run.TestRun):
def __init__(self, env, test_instance): def __init__(self, env, test_instance):
...@@ -96,7 +51,7 @@ class LocalDeviceTestRun(test_run.TestRun): ...@@ -96,7 +51,7 @@ class LocalDeviceTestRun(test_run.TestRun):
exit_now = threading.Event() exit_now = threading.Event()
@handle_shard_failures @local_device_environment.handle_shard_failures
def run_tests_on_device(dev, tests, results): def run_tests_on_device(dev, tests, results):
for test in tests: for test in tests:
if exit_now.isSet(): if exit_now.isSet():
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment