Chromium Code Reviews| 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 constants | |
| 11 from pylib import forwarder | 12 from pylib import forwarder |
| 12 from pylib.utils import reraiser_thread | 13 from pylib.utils import reraiser_thread |
| 13 from pylib.utils import watchdog_timer | 14 from pylib.utils import watchdog_timer |
| 14 | 15 |
| 15 import base_test_result | 16 import base_test_result |
| 16 | 17 |
| 17 | 18 |
| 18 DEFAULT_TIMEOUT = 7 * 60 # seven minutes | 19 DEFAULT_TIMEOUT = 7 * 60 # seven minutes |
| 19 | 20 |
| 20 | 21 |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 85 try: | 86 try: |
| 86 return self._tests.pop(0) | 87 return self._tests.pop(0) |
| 87 except IndexError: | 88 except IndexError: |
| 88 # Another thread beat us to the avaliable test, wait again. | 89 # Another thread beat us to the avaliable test, wait again. |
| 89 self._item_avaliable_or_all_done.clear() | 90 self._item_avaliable_or_all_done.clear() |
| 90 | 91 |
| 91 def add(self, test): | 92 def add(self, test): |
| 92 """Add an test to the collection. | 93 """Add an test to the collection. |
| 93 | 94 |
| 94 Args: | 95 Args: |
| 95 item: A test to add. | 96 test: A test to add. |
| 96 """ | 97 """ |
| 97 with self._lock: | 98 with self._lock: |
| 98 self._tests.append(test) | 99 self._tests.append(test) |
| 99 self._item_avaliable_or_all_done.set() | 100 self._item_avaliable_or_all_done.set() |
| 100 self._tests_in_progress += 1 | 101 self._tests_in_progress += 1 |
| 101 | 102 |
| 102 def test_completed(self): | 103 def test_completed(self): |
| 103 """Indicate that a test has been fully handled.""" | 104 """Indicate that a test has been fully handled.""" |
| 104 with self._lock: | 105 with self._lock: |
| 105 self._tests_in_progress -= 1 | 106 self._tests_in_progress -= 1 |
| 106 if self._tests_in_progress == 0: | 107 if self._tests_in_progress == 0: |
| 107 # All tests have been handled, signal all waiting threads. | 108 # All tests have been handled, signal all waiting threads. |
| 108 self._item_avaliable_or_all_done.set() | 109 self._item_avaliable_or_all_done.set() |
| 109 | 110 |
| 110 def __iter__(self): | 111 def __iter__(self): |
| 111 """Iterate through tests in the collection until all have been handled.""" | 112 """Iterate through tests in the collection until all have been handled.""" |
| 112 while True: | 113 while True: |
| 113 r = self._pop() | 114 r = self._pop() |
| 114 if r is None: | 115 if r is None: |
| 115 break | 116 break |
| 116 yield r | 117 yield r |
| 117 | 118 |
| 118 | 119 |
| 119 def _RunTestsFromQueue(runner, test_collection, out_results, watcher, | 120 def _RunTestsFromQueue(runner, test_collection, out_results, watcher, |
| 120 num_retries): | 121 num_retries): |
| 121 """Runs tests from the test_collection until empty using the given runner. | 122 """Runs tests from the test_collection until empty using the given runner. |
| 122 | 123 |
| 123 Adds TestRunResults objects to the out_results list and may add tests to the | 124 Adds TestRunResults objects to the out_results list and may add tests to the |
| 124 out_retry list. | 125 out_retry list. |
| 125 | 126 |
| 126 Args: | 127 Args: |
| 127 runner: A TestRunner object used to run the tests. | 128 runner: A TestRunner object used to run the tests. |
| 128 test_collection: A _TestCollection from which to get _Test objects to run. | 129 test_collection: A _TestCollection from which to get _Test objects to run. |
| 129 out_results: A list to add TestRunResults to. | 130 out_results: A list to add TestRunResults to. |
| 130 watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout. | 131 watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout. |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 143 if retry and test.tries <= num_retries: | 144 if retry and test.tries <= num_retries: |
| 144 # Retry non-passing results, only record passing results. | 145 # Retry non-passing results, only record passing results. |
| 145 pass_results = base_test_result.TestRunResults() | 146 pass_results = base_test_result.TestRunResults() |
| 146 pass_results.AddResults(result.GetPass()) | 147 pass_results.AddResults(result.GetPass()) |
| 147 out_results.append(pass_results) | 148 out_results.append(pass_results) |
| 148 logging.warning('Will retry test, try #%s.' % test.tries) | 149 logging.warning('Will retry test, try #%s.' % test.tries) |
| 149 test_collection.add(_Test(test=retry, tries=test.tries)) | 150 test_collection.add(_Test(test=retry, tries=test.tries)) |
| 150 else: | 151 else: |
| 151 # All tests passed or retry limit reached. Either way, record results. | 152 # All tests passed or retry limit reached. Either way, record results. |
| 152 out_results.append(result) | 153 out_results.append(result) |
| 153 except android_commands.errors.DeviceUnresponsiveError: | |
| 154 # Device is unresponsive, stop handling tests on this device and ensure | |
| 155 # current test gets runs by another device. Don't reraise this exception | |
| 156 # on the main thread. | |
| 157 test_collection.add(test) | |
|
frankf
2013/07/03 21:26:37
This decreases robustness. Currently, if we catch
gkanwar
2013/07/03 23:15:18
I'm not sure I fully understand -- is this decreas
frankf
2013/07/04 00:46:19
Ah, it makes sense.
On 2013/07/03 23:15:18, gkanw
| |
| 158 return | |
| 159 except: | 154 except: |
| 160 # An unhandleable exception, ensure tests get run by another device and | 155 # An unhandleable exception, ensure tests get run by another device and |
| 161 # reraise this exception on the main thread. | 156 # reraise this exception on the main thread. |
| 162 test_collection.add(test) | 157 test_collection.add(test) |
| 163 raise | 158 raise |
| 164 finally: | 159 finally: |
| 165 # Retries count as separate tasks so always mark the popped test as done. | 160 # Retries count as separate tasks so always mark the popped test as done. |
| 166 test_collection.test_completed() | 161 test_collection.test_completed() |
| 167 | 162 |
| 168 | 163 |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 205 (len(tests), len(runners))) | 200 (len(tests), len(runners))) |
| 206 tests_collection = _TestCollection([_Test(t) for t in tests]) | 201 tests_collection = _TestCollection([_Test(t) for t in tests]) |
| 207 results = [] | 202 results = [] |
| 208 watcher = watchdog_timer.WatchdogTimer(timeout) | 203 watcher = watchdog_timer.WatchdogTimer(timeout) |
| 209 workers = reraiser_thread.ReraiserThreadGroup( | 204 workers = reraiser_thread.ReraiserThreadGroup( |
| 210 [reraiser_thread.ReraiserThread( | 205 [reraiser_thread.ReraiserThread( |
| 211 _RunTestsFromQueue, | 206 _RunTestsFromQueue, |
| 212 [r, tests_collection, results, watcher, num_retries], | 207 [r, tests_collection, results, watcher, num_retries], |
| 213 name=r.device[-4:]) | 208 name=r.device[-4:]) |
| 214 for r in runners]) | 209 for r in runners]) |
| 210 run_results = base_test_result.TestRunResults() | |
| 215 workers.StartAll() | 211 workers.StartAll() |
| 216 workers.JoinAll(watcher) | 212 |
| 217 run_results = base_test_result.TestRunResults() | 213 # Catch DeviceUnresponsiveErrors and set a warning exit code |
| 214 try: | |
| 215 workers.JoinAll(watcher) | |
| 216 except android_commands.errors.DeviceUnresponsiveError as e: | |
| 217 run_results.exit_code = constants.WARNING_EXIT_CODE | |
|
frankf
2013/07/03 21:26:37
Perhaps a singleton object that flags this excepti
gkanwar
2013/07/03 23:15:18
That's a good point. Updated to use a singleton in
| |
| 218 | |
| 218 for r in results: | 219 for r in results: |
| 219 run_results.AddTestRunResults(r) | 220 run_results.AddTestRunResults(r) |
| 220 return run_results | 221 return run_results |
| 221 | 222 |
| 222 | 223 |
| 223 def _CreateRunners(runner_factory, devices, timeout=None): | 224 def _CreateRunners(runner_factory, devices, timeout=None): |
| 224 """Creates a test runner for each device and calls SetUp() in parallel. | 225 """Creates a test runner for each device and calls SetUp() in parallel. |
| 225 | 226 |
| 226 Note: if a device is unresponsive the corresponding TestRunner will not be | 227 Note: if a device is unresponsive the corresponding TestRunner will not be |
| 227 included in the returned list. | 228 included in the returned list. |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 243 [runner_factory, d, runners, counter], | 244 [runner_factory, d, runners, counter], |
| 244 name=d[-4:]) | 245 name=d[-4:]) |
| 245 for d in devices]) | 246 for d in devices]) |
| 246 threads.StartAll() | 247 threads.StartAll() |
| 247 threads.JoinAll(watchdog_timer.WatchdogTimer(timeout)) | 248 threads.JoinAll(watchdog_timer.WatchdogTimer(timeout)) |
| 248 return runners | 249 return runners |
| 249 | 250 |
| 250 | 251 |
| 251 def _TearDownRunners(runners, timeout=None): | 252 def _TearDownRunners(runners, timeout=None): |
| 252 """Calls TearDown() for each test runner in parallel. | 253 """Calls TearDown() for each test runner in parallel. |
| 254 | |
| 253 Args: | 255 Args: |
| 254 runners: a list of TestRunner objects. | 256 runners: a list of TestRunner objects. |
| 255 timeout: watchdog timeout in seconds, defaults to the default timeout. | 257 timeout: watchdog timeout in seconds, defaults to the default timeout. |
| 256 """ | 258 """ |
| 257 threads = reraiser_thread.ReraiserThreadGroup( | 259 threads = reraiser_thread.ReraiserThreadGroup( |
| 258 [reraiser_thread.ReraiserThread(r.TearDown, name=r.device[-4:]) | 260 [reraiser_thread.ReraiserThread(r.TearDown, name=r.device[-4:]) |
| 259 for r in runners]) | 261 for r in runners]) |
| 260 threads.StartAll() | 262 threads.StartAll() |
| 261 threads.JoinAll(watchdog_timer.WatchdogTimer(timeout)) | 263 threads.JoinAll(watchdog_timer.WatchdogTimer(timeout)) |
| 262 | 264 |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 291 runners = _CreateRunners(runner_factory, devices, setup_timeout) | 293 runners = _CreateRunners(runner_factory, devices, setup_timeout) |
| 292 try: | 294 try: |
| 293 return _RunAllTests(runners, tests, num_retries, test_timeout) | 295 return _RunAllTests(runners, tests, num_retries, test_timeout) |
| 294 finally: | 296 finally: |
| 295 try: | 297 try: |
| 296 _TearDownRunners(runners, setup_timeout) | 298 _TearDownRunners(runners, setup_timeout) |
| 297 except android_commands.errors.DeviceUnresponsiveError as e: | 299 except android_commands.errors.DeviceUnresponsiveError as e: |
| 298 logging.warning('Device unresponsive during TearDown: [%s]', e) | 300 logging.warning('Device unresponsive during TearDown: [%s]', e) |
| 299 finally: | 301 finally: |
| 300 forwarder.Forwarder.KillHost(build_type) | 302 forwarder.Forwarder.KillHost(build_type) |
| OLD | NEW |