| 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 |