OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import datetime | 5 import datetime |
| 6 import functools |
6 import logging | 7 import logging |
7 import os | 8 import os |
8 import shutil | 9 import shutil |
9 import tempfile | 10 import tempfile |
10 import threading | 11 import threading |
11 | 12 |
| 13 from devil import base_error |
12 from devil.android import device_blacklist | 14 from devil.android import device_blacklist |
13 from devil.android import device_errors | 15 from devil.android import device_errors |
14 from devil.android import device_list | 16 from devil.android import device_list |
15 from devil.android import device_utils | 17 from devil.android import device_utils |
16 from devil.android import logcat_monitor | 18 from devil.android import logcat_monitor |
17 from devil.utils import file_utils | 19 from devil.utils import file_utils |
18 from devil.utils import parallelizer | 20 from devil.utils import parallelizer |
19 from pylib import constants | 21 from pylib import constants |
20 from pylib.base import environment | 22 from pylib.base import environment |
21 | 23 |
22 | 24 |
23 def _DeviceCachePath(device): | 25 def _DeviceCachePath(device): |
24 file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() | 26 file_name = 'device_cache_%s.json' % device.adb.GetDeviceSerial() |
25 return os.path.join(constants.GetOutDirectory(), file_name) | 27 return os.path.join(constants.GetOutDirectory(), file_name) |
26 | 28 |
27 | 29 |
| 30 def handle_shard_failures(f): |
| 31 """A decorator that handles device failures for per-device functions. |
| 32 |
| 33 Args: |
| 34 f: the function being decorated. The function must take at least one |
| 35 argument, and that argument must be the device. |
| 36 """ |
| 37 return handle_shard_failures_with(None)(f) |
| 38 |
| 39 |
| 40 # TODO(jbudorick): Refactor this to work as a decorator or context manager. |
| 41 def handle_shard_failures_with(on_failure): |
| 42 """A decorator that handles device failures for per-device functions. |
| 43 |
| 44 This calls on_failure in the event of a failure. |
| 45 |
| 46 Args: |
| 47 f: the function being decorated. The function must take at least one |
| 48 argument, and that argument must be the device. |
| 49 on_failure: A binary function to call on failure. |
| 50 """ |
| 51 def decorator(f): |
| 52 @functools.wraps(f) |
| 53 def wrapper(dev, *args, **kwargs): |
| 54 try: |
| 55 return f(dev, *args, **kwargs) |
| 56 except device_errors.CommandTimeoutError: |
| 57 logging.exception('Shard timed out: %s(%s)', f.__name__, str(dev)) |
| 58 except device_errors.DeviceUnreachableError: |
| 59 logging.exception('Shard died: %s(%s)', f.__name__, str(dev)) |
| 60 except base_error.BaseError: |
| 61 logging.exception('Shard failed: %s(%s)', f.__name__, str(dev)) |
| 62 except SystemExit: |
| 63 logging.exception('Shard killed: %s(%s)', f.__name__, str(dev)) |
| 64 raise |
| 65 if on_failure: |
| 66 on_failure(dev, f.__name__) |
| 67 return None |
| 68 |
| 69 return wrapper |
| 70 |
| 71 return decorator |
| 72 |
| 73 |
28 class LocalDeviceEnvironment(environment.Environment): | 74 class LocalDeviceEnvironment(environment.Environment): |
29 | 75 |
30 def __init__(self, args, _error_func): | 76 def __init__(self, args, _error_func): |
31 super(LocalDeviceEnvironment, self).__init__() | 77 super(LocalDeviceEnvironment, self).__init__() |
32 self._blacklist = (device_blacklist.Blacklist(args.blacklist_file) | 78 self._blacklist = (device_blacklist.Blacklist(args.blacklist_file) |
33 if args.blacklist_file | 79 if args.blacklist_file |
34 else None) | 80 else None) |
35 self._device_serial = args.test_device | 81 self._device_serial = args.test_device |
36 self._devices_lock = threading.Lock() | 82 self._devices_lock = threading.Lock() |
37 self._devices = [] | 83 self._devices = [] |
(...skipping 22 matching lines...) Expand all Loading... |
60 'Read device list %s from target devices file.', str(device_arg)) | 106 'Read device list %s from target devices file.', str(device_arg)) |
61 elif self._device_serial: | 107 elif self._device_serial: |
62 device_arg = self._device_serial | 108 device_arg = self._device_serial |
63 | 109 |
64 self._devices = device_utils.DeviceUtils.HealthyDevices( | 110 self._devices = device_utils.DeviceUtils.HealthyDevices( |
65 self._blacklist, enable_device_files_cache=self._enable_device_cache, | 111 self._blacklist, enable_device_files_cache=self._enable_device_cache, |
66 default_retries=self._max_tries - 1, device_arg=device_arg) | 112 default_retries=self._max_tries - 1, device_arg=device_arg) |
67 if not self._devices: | 113 if not self._devices: |
68 raise device_errors.NoDevicesError | 114 raise device_errors.NoDevicesError |
69 | 115 |
70 if self._enable_device_cache: | 116 if self._logcat_output_file: |
71 for d in self._devices: | 117 self._logcat_output_dir = tempfile.mkdtemp() |
| 118 |
| 119 @handle_shard_failures_with(on_failure=self.BlacklistDevice) |
| 120 def prepare_device(d): |
| 121 if self._enable_device_cache: |
72 cache_path = _DeviceCachePath(d) | 122 cache_path = _DeviceCachePath(d) |
73 if os.path.exists(cache_path): | 123 if os.path.exists(cache_path): |
74 logging.info('Using device cache: %s', cache_path) | 124 logging.info('Using device cache: %s', cache_path) |
75 with open(cache_path) as f: | 125 with open(cache_path) as f: |
76 d.LoadCacheData(f.read()) | 126 d.LoadCacheData(f.read()) |
77 # Delete cached file so that any exceptions cause it to be cleared. | 127 # Delete cached file so that any exceptions cause it to be cleared. |
78 os.unlink(cache_path) | 128 os.unlink(cache_path) |
79 if self._logcat_output_file: | 129 |
80 self._logcat_output_dir = tempfile.mkdtemp() | 130 if self._logcat_output_dir: |
81 if self._logcat_output_dir: | |
82 for d in self._devices: | |
83 logcat_file = os.path.join( | 131 logcat_file = os.path.join( |
84 self._logcat_output_dir, | 132 self._logcat_output_dir, |
85 '%s_%s' % (d.adb.GetDeviceSerial(), | 133 '%s_%s' % (d.adb.GetDeviceSerial(), |
86 datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%S'))) | 134 datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%S'))) |
87 monitor = logcat_monitor.LogcatMonitor( | 135 monitor = logcat_monitor.LogcatMonitor( |
88 d.adb, clear=True, output_file=logcat_file) | 136 d.adb, clear=True, output_file=logcat_file) |
89 self._logcat_monitors.append(monitor) | 137 self._logcat_monitors.append(monitor) |
90 monitor.Start() | 138 monitor.Start() |
91 | 139 |
| 140 self.parallel_devices.pMap(prepare_device) |
| 141 |
92 @property | 142 @property |
93 def blacklist(self): | 143 def blacklist(self): |
94 return self._blacklist | 144 return self._blacklist |
95 | 145 |
96 @property | 146 @property |
97 def concurrent_adb(self): | 147 def concurrent_adb(self): |
98 return self._concurrent_adb | 148 return self._concurrent_adb |
99 | 149 |
100 @property | 150 @property |
101 def devices(self): | 151 def devices(self): |
(...skipping 12 matching lines...) Expand all Loading... |
114 @property | 164 @property |
115 def skip_clear_data(self): | 165 def skip_clear_data(self): |
116 return self._skip_clear_data | 166 return self._skip_clear_data |
117 | 167 |
118 @property | 168 @property |
119 def tool(self): | 169 def tool(self): |
120 return self._tool_name | 170 return self._tool_name |
121 | 171 |
122 #override | 172 #override |
123 def TearDown(self): | 173 def TearDown(self): |
124 # Write the cache even when not using it so that it will be ready the first | 174 @handle_shard_failures_with(on_failure=self.BlacklistDevice) |
125 # time that it is enabled. Writing it every time is also necessary so that | 175 def tear_down_device(d): |
126 # an invalid cache can be flushed just by disabling it for one run. | 176 # Write the cache even when not using it so that it will be ready the |
127 for d in self._devices: | 177 # first time that it is enabled. Writing it every time is also necessary |
| 178 # so that an invalid cache can be flushed just by disabling it for one |
| 179 # run. |
128 cache_path = _DeviceCachePath(d) | 180 cache_path = _DeviceCachePath(d) |
129 with open(cache_path, 'w') as f: | 181 with open(cache_path, 'w') as f: |
130 f.write(d.DumpCacheData()) | 182 f.write(d.DumpCacheData()) |
131 logging.info('Wrote device cache: %s', cache_path) | 183 logging.info('Wrote device cache: %s', cache_path) |
| 184 |
| 185 self.parallel_devices.pMap(tear_down_device) |
| 186 |
132 for m in self._logcat_monitors: | 187 for m in self._logcat_monitors: |
133 m.Stop() | 188 try: |
134 m.Close() | 189 m.Stop() |
| 190 m.Close() |
| 191 except base_error.BaseError: |
| 192 logging.exception('Failed to stop logcat monitor for %s', |
| 193 m.adb.GetDeviceSerial()) |
| 194 |
135 if self._logcat_output_file: | 195 if self._logcat_output_file: |
136 file_utils.MergeFiles( | 196 file_utils.MergeFiles( |
137 self._logcat_output_file, | 197 self._logcat_output_file, |
138 [m.output_file for m in self._logcat_monitors]) | 198 [m.output_file for m in self._logcat_monitors]) |
139 shutil.rmtree(self._logcat_output_dir) | 199 shutil.rmtree(self._logcat_output_dir) |
140 | 200 |
141 def BlacklistDevice(self, device, reason='local_device_failure'): | 201 def BlacklistDevice(self, device, reason='local_device_failure'): |
142 device_serial = device.adb.GetDeviceSerial() | 202 device_serial = device.adb.GetDeviceSerial() |
143 if self._blacklist: | 203 if self._blacklist: |
144 self._blacklist.Extend([device_serial], reason=reason) | 204 self._blacklist.Extend([device_serial], reason=reason) |
145 with self._devices_lock: | 205 with self._devices_lock: |
146 self._devices = [d for d in self._devices if str(d) != device_serial] | 206 self._devices = [d for d in self._devices if str(d) != device_serial] |
147 | 207 |
OLD | NEW |