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 | |
142 | |
rnephew (Reviews Here)
2016/07/13 20:12:26
nit: one line
jbudorick
2016/07/13 20:43:09
Done.
| |
92 @property | 143 @property |
93 def blacklist(self): | 144 def blacklist(self): |
94 return self._blacklist | 145 return self._blacklist |
95 | 146 |
96 @property | 147 @property |
97 def concurrent_adb(self): | 148 def concurrent_adb(self): |
98 return self._concurrent_adb | 149 return self._concurrent_adb |
99 | 150 |
100 @property | 151 @property |
101 def devices(self): | 152 def devices(self): |
(...skipping 12 matching lines...) Expand all Loading... | |
114 @property | 165 @property |
115 def skip_clear_data(self): | 166 def skip_clear_data(self): |
116 return self._skip_clear_data | 167 return self._skip_clear_data |
117 | 168 |
118 @property | 169 @property |
119 def tool(self): | 170 def tool(self): |
120 return self._tool_name | 171 return self._tool_name |
121 | 172 |
122 #override | 173 #override |
123 def TearDown(self): | 174 def TearDown(self): |
124 # Write the cache even when not using it so that it will be ready the first | 175 @handle_shard_failures_with(on_failure=self.BlacklistDevice) |
125 # time that it is enabled. Writing it every time is also necessary so that | 176 def tear_down_device(d): |
126 # an invalid cache can be flushed just by disabling it for one run. | 177 # Write the cache even when not using it so that it will be ready the |
127 for d in self._devices: | 178 # first time that it is enabled. Writing it every time is also necessary |
179 # so that an invalid cache can be flushed just by disabling it for one | |
180 # run. | |
128 cache_path = _DeviceCachePath(d) | 181 cache_path = _DeviceCachePath(d) |
129 with open(cache_path, 'w') as f: | 182 with open(cache_path, 'w') as f: |
130 f.write(d.DumpCacheData()) | 183 f.write(d.DumpCacheData()) |
131 logging.info('Wrote device cache: %s', cache_path) | 184 logging.info('Wrote device cache: %s', cache_path) |
185 | |
186 self.parallel_devices.pMap(tear_down_device) | |
187 | |
132 for m in self._logcat_monitors: | 188 for m in self._logcat_monitors: |
133 m.Stop() | 189 try: |
134 m.Close() | 190 m.Stop() |
191 m.Close() | |
192 except base_error.BaseError: | |
193 logging.exception('Failed to stop logcat monitor for %s', | |
194 m.adb.GetDeviceSerial()) | |
195 | |
135 if self._logcat_output_file: | 196 if self._logcat_output_file: |
136 file_utils.MergeFiles( | 197 file_utils.MergeFiles( |
137 self._logcat_output_file, | 198 self._logcat_output_file, |
138 [m.output_file for m in self._logcat_monitors]) | 199 [m.output_file for m in self._logcat_monitors]) |
139 shutil.rmtree(self._logcat_output_dir) | 200 shutil.rmtree(self._logcat_output_dir) |
140 | 201 |
141 def BlacklistDevice(self, device, reason='local_device_failure'): | 202 def BlacklistDevice(self, device, reason='local_device_failure'): |
142 device_serial = device.adb.GetDeviceSerial() | 203 device_serial = device.adb.GetDeviceSerial() |
143 if self._blacklist: | 204 if self._blacklist: |
144 self._blacklist.Extend([device_serial], reason=reason) | 205 self._blacklist.Extend([device_serial], reason=reason) |
145 with self._devices_lock: | 206 with self._devices_lock: |
146 self._devices = [d for d in self._devices if str(d) != device_serial] | 207 self._devices = [d for d in self._devices if str(d) != device_serial] |
147 | 208 |
OLD | NEW |