OLD | NEW |
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2013 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 """Implements test sharding logic.""" | 5 """Implements test sharding logic.""" |
6 | 6 |
7 import logging | 7 import logging |
8 import threading | 8 import threading |
9 | 9 |
10 from pylib import android_commands | 10 from pylib import android_commands |
11 from pylib import forwarder | 11 from pylib import forwarder |
12 from pylib.utils import reraiser_thread | 12 from pylib.utils import reraiser_thread |
13 | 13 |
14 import test_result | 14 import test_result |
15 | 15 |
16 | 16 |
| 17 class _ThreadSafeCounter(object): |
| 18 """A threadsafe counter.""" |
| 19 def __init__(self): |
| 20 self._lock = threading.Lock() |
| 21 self._value = 0 |
| 22 |
| 23 def GetAndIncrement(self): |
| 24 """Get the current value and increment it atomically. |
| 25 |
| 26 Returns: |
| 27 The value before incrementing. |
| 28 """ |
| 29 with self._lock: |
| 30 pre_increment = self._value |
| 31 self._value += 1 |
| 32 return pre_increment |
| 33 |
| 34 |
17 class _Test(object): | 35 class _Test(object): |
18 """Holds a test with additional metadata.""" | 36 """Holds a test with additional metadata.""" |
19 def __init__(self, test, tries=0): | 37 def __init__(self, test, tries=0): |
20 """Initializes the _Test object. | 38 """Initializes the _Test object. |
21 | 39 |
22 Args: | 40 Args: |
23 test: the test. | 41 test: the test. |
24 tries: number of tries so far. | 42 tries: number of tries so far. |
25 """ | 43 """ |
26 self.test = test | 44 self.test = test |
(...skipping 24 matching lines...) Expand all Loading... |
51 A test or None if all tests have been handled. | 69 A test or None if all tests have been handled. |
52 """ | 70 """ |
53 while True: | 71 while True: |
54 # Wait for a test to be avaliable or all tests to have been handled. | 72 # Wait for a test to be avaliable or all tests to have been handled. |
55 self._item_avaliable_or_all_done.wait() | 73 self._item_avaliable_or_all_done.wait() |
56 with self._lock: | 74 with self._lock: |
57 # Check which of the two conditions triggered the signal. | 75 # Check which of the two conditions triggered the signal. |
58 if self._tests_in_progress == 0: | 76 if self._tests_in_progress == 0: |
59 return None | 77 return None |
60 try: | 78 try: |
61 return self._tests.pop() | 79 return self._tests.pop(0) |
62 except IndexError: | 80 except IndexError: |
63 # Another thread beat us to the avaliable test, wait again. | 81 # Another thread beat us to the avaliable test, wait again. |
64 self._item_avaliable_or_all_done.clear() | 82 self._item_avaliable_or_all_done.clear() |
65 | 83 |
66 def add(self, test): | 84 def add(self, test): |
67 """Add an test to the collection. | 85 """Add an test to the collection. |
68 | 86 |
69 Args: | 87 Args: |
70 item: A test to add. | 88 item: A test to add. |
71 """ | 89 """ |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 if not android_commands.IsDeviceAttached(runner.device): | 125 if not android_commands.IsDeviceAttached(runner.device): |
108 # Device is unresponsive, stop handling tests on this device. | 126 # Device is unresponsive, stop handling tests on this device. |
109 msg = 'Device %s is unresponsive.' % runner.device | 127 msg = 'Device %s is unresponsive.' % runner.device |
110 logging.warning(msg) | 128 logging.warning(msg) |
111 raise android_commands.errors.DeviceUnresponsiveError(msg) | 129 raise android_commands.errors.DeviceUnresponsiveError(msg) |
112 result, retry = runner.RunTest(test.test) | 130 result, retry = runner.RunTest(test.test) |
113 test.tries += 1 | 131 test.tries += 1 |
114 if retry and test.tries <= 3: | 132 if retry and test.tries <= 3: |
115 # Retry non-passing results, only record passing results. | 133 # Retry non-passing results, only record passing results. |
116 out_results.append(test_result.TestResults.FromRun(ok=result.ok)) | 134 out_results.append(test_result.TestResults.FromRun(ok=result.ok)) |
117 logging.warning('****Retrying test, try #%s.' % test.tries) | 135 logging.warning('****Will retry test, try #%s.' % test.tries) |
118 test_collection.add(_Test(test=retry, tries=test.tries)) | 136 test_collection.add(_Test(test=retry, tries=test.tries)) |
119 else: | 137 else: |
120 # All tests passed or retry limit reached. Either way, record results. | 138 # All tests passed or retry limit reached. Either way, record results. |
121 out_results.append(result) | 139 out_results.append(result) |
122 except android_commands.errors.DeviceUnresponsiveError: | 140 except android_commands.errors.DeviceUnresponsiveError: |
123 # Device is unresponsive, stop handling tests on this device and ensure | 141 # Device is unresponsive, stop handling tests on this device and ensure |
124 # current test gets runs by another device. Don't reraise this exception | 142 # current test gets runs by another device. Don't reraise this exception |
125 # on the main thread. | 143 # on the main thread. |
126 test_collection.add(test) | 144 test_collection.add(test) |
127 return | 145 return |
128 except: | 146 except: |
129 # An unhandleable exception, ensure tests get run by another device and | 147 # An unhandleable exception, ensure tests get run by another device and |
130 # reraise this exception on the main thread. | 148 # reraise this exception on the main thread. |
131 test_collection.add(test) | 149 test_collection.add(test) |
132 raise | 150 raise |
133 finally: | 151 finally: |
134 # Retries count as separate tasks so always mark the popped test as done. | 152 # Retries count as separate tasks so always mark the popped test as done. |
135 test_collection.test_completed() | 153 test_collection.test_completed() |
136 | 154 |
137 | 155 |
138 def _SetUp(runner_factory, device, out_runners): | 156 def _SetUp(runner_factory, device, out_runners, threadsafe_counter): |
139 """Creates a test runner for each device and calls SetUp() in parallel. | 157 """Creates a test runner for each device and calls SetUp() in parallel. |
140 | 158 |
141 Note: if a device is unresponsive the corresponding TestRunner will not be | 159 Note: if a device is unresponsive the corresponding TestRunner will not be |
142 added to out_runners. | 160 added to out_runners. |
143 | 161 |
144 Args: | 162 Args: |
145 runner_factory: callable that takes a device and returns a TestRunner. | 163 runner_factory: callable that takes a device and index and returns a |
| 164 TestRunner object. |
146 device: the device serial number to set up. | 165 device: the device serial number to set up. |
147 out_runners: list to add the successfully set up TestRunner object. | 166 out_runners: list to add the successfully set up TestRunner object. |
| 167 threadsafe_counter: a _ThreadSafeCounter object used to get shard indices. |
148 """ | 168 """ |
149 try: | 169 try: |
150 logging.warning('*****Creating shard for %s.', device) | 170 index = threadsafe_counter.GetAndIncrement() |
151 runner = runner_factory(device) | 171 logging.warning('*****Creating shard %s for device %s.', index, device) |
| 172 runner = runner_factory(device, index) |
152 runner.SetUp() | 173 runner.SetUp() |
153 out_runners.append(runner) | 174 out_runners.append(runner) |
154 except android_commands.errors.DeviceUnresponsiveError as e: | 175 except android_commands.errors.DeviceUnresponsiveError as e: |
155 logging.warning('****Failed to create shard for %s: [%s]', (device, e)) | 176 logging.warning('****Failed to create shard for %s: [%s]', device, e) |
156 | 177 |
157 | 178 |
158 def _RunAllTests(runners, tests): | 179 def _RunAllTests(runners, tests): |
159 """Run all tests using the given TestRunners. | 180 """Run all tests using the given TestRunners. |
160 | 181 |
161 Args: | 182 Args: |
162 runners: a list of TestRunner objects. | 183 runners: a list of TestRunner objects. |
163 tests: a list of Tests to run using the given TestRunners. | 184 tests: a list of Tests to run using the given TestRunners. |
164 | 185 |
165 Returns: | 186 Returns: |
(...skipping 10 matching lines...) Expand all Loading... |
176 return test_result.TestResults.FromTestResults(results) | 197 return test_result.TestResults.FromTestResults(results) |
177 | 198 |
178 | 199 |
179 def _CreateRunners(runner_factory, devices): | 200 def _CreateRunners(runner_factory, devices): |
180 """Creates a test runner for each device and calls SetUp() in parallel. | 201 """Creates a test runner for each device and calls SetUp() in parallel. |
181 | 202 |
182 Note: if a device is unresponsive the corresponding TestRunner will not be | 203 Note: if a device is unresponsive the corresponding TestRunner will not be |
183 included in the returned list. | 204 included in the returned list. |
184 | 205 |
185 Args: | 206 Args: |
186 runner_factory: callable that takes a device and returns a TestRunner. | 207 runner_factory: callable that takes a device and index and returns a |
| 208 TestRunner object. |
187 devices: list of device serial numbers as strings. | 209 devices: list of device serial numbers as strings. |
188 | 210 |
189 Returns: | 211 Returns: |
190 A list of TestRunner objects. | 212 A list of TestRunner objects. |
191 """ | 213 """ |
192 logging.warning('****Creating %s test runners.' % len(devices)) | 214 logging.warning('****Creating %s test runners.' % len(devices)) |
193 test_runners = [] | 215 runners = [] |
| 216 counter = _ThreadSafeCounter() |
194 threads = reraiser_thread.ReraiserThreadGroup( | 217 threads = reraiser_thread.ReraiserThreadGroup( |
195 [reraiser_thread.ReraiserThread(_SetUp, [runner_factory, d, test_runners]) | 218 [reraiser_thread.ReraiserThread(_SetUp, [runner_factory, d, runners, |
| 219 counter]) |
196 for d in devices]) | 220 for d in devices]) |
197 threads.StartAll() | 221 threads.StartAll() |
198 threads.JoinAll() | 222 threads.JoinAll() |
199 return test_runners | 223 return runners |
200 | 224 |
201 | 225 |
202 def _TearDownRunners(runners): | 226 def _TearDownRunners(runners): |
203 """Calls TearDown() for each test runner in parallel. | 227 """Calls TearDown() for each test runner in parallel. |
204 Args: | 228 Args: |
205 runners: a list of TestRunner objects. | 229 runners: a list of TestRunner objects. |
206 """ | 230 """ |
207 threads = reraiser_thread.ReraiserThreadGroup( | 231 threads = reraiser_thread.ReraiserThreadGroup( |
208 [reraiser_thread.ReraiserThread(runner.TearDown) | 232 [reraiser_thread.ReraiserThread(runner.TearDown) |
209 for runner in runners]) | 233 for runner in runners]) |
210 threads.StartAll() | 234 threads.StartAll() |
211 threads.JoinAll() | 235 threads.JoinAll() |
212 | 236 |
213 | 237 |
214 def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug'): | 238 def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug'): |
215 """Run all tests on attached devices, retrying tests that don't pass. | 239 """Run all tests on attached devices, retrying tests that don't pass. |
216 | 240 |
217 Args: | 241 Args: |
218 runner_factory: callable that takes a device and returns a TestRunner. | 242 runner_factory: callable that takes a device and index and returns a |
| 243 TestRunner object. |
219 devices: list of attached device serial numbers as strings. | 244 devices: list of attached device serial numbers as strings. |
220 tests: list of tests to run. | 245 tests: list of tests to run. |
221 build_type: either 'Debug' or 'Release'. | 246 build_type: either 'Debug' or 'Release'. |
222 | 247 |
223 Returns: | 248 Returns: |
224 A test_result.TestResults object. | 249 A test_result.TestResults object. |
225 """ | 250 """ |
226 forwarder.Forwarder.KillHost(build_type) | 251 forwarder.Forwarder.KillHost(build_type) |
227 runners = _CreateRunners(runner_factory, devices) | 252 runners = _CreateRunners(runner_factory, devices) |
228 try: | 253 try: |
229 return _RunAllTests(runners, tests) | 254 return _RunAllTests(runners, tests) |
230 finally: | 255 finally: |
231 try: | 256 try: |
232 _TearDownRunners(runners) | 257 _TearDownRunners(runners) |
233 except android_commands.errors.DeviceUnresponsiveError as e: | 258 except android_commands.errors.DeviceUnresponsiveError as e: |
234 logging.warning('****Device unresponsive during TearDown: [%s]', e) | 259 logging.warning('****Device unresponsive during TearDown: [%s]', e) |
235 finally: | 260 finally: |
236 forwarder.Forwarder.KillHost(build_type) | 261 forwarder.Forwarder.KillHost(build_type) |
OLD | NEW |