| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 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 """Dispatches tests, either sharding or replicating them. | 5 """Dispatches tests, either sharding or replicating them. |
| 6 | 6 |
| 7 Performs the following steps: | 7 Performs the following steps: |
| 8 * Create a test collection factory, using the given tests | 8 * Create a test collection factory, using the given tests |
| 9 - If sharding: test collection factory returns the same shared test collection | 9 - If sharding: test collection factory returns the same shared test collection |
| 10 to all test runners | 10 to all test runners |
| 11 - If replciating: test collection factory returns a unique test collection to | 11 - If replciating: test collection factory returns a unique test collection to |
| 12 each test runner, with the same set of tests in each. | 12 each test runner, with the same set of tests in each. |
| 13 * Create a test runner for each device. | 13 * Create a test runner for each device. |
| 14 * Run each test runner in its own thread, grabbing tests from the test | 14 * Run each test runner in its own thread, grabbing tests from the test |
| 15 collection until there are no tests left. | 15 collection until there are no tests left. |
| 16 """ | 16 """ |
| 17 | 17 |
| 18 # TODO(jbudorick) Deprecate and remove this class after any relevant parts have | 18 # TODO(jbudorick) Deprecate and remove this class after any relevant parts have |
| 19 # been ported to the new environment / test instance model. | 19 # been ported to the new environment / test instance model. |
| 20 | 20 |
| 21 import logging | 21 import logging |
| 22 import threading | 22 import threading |
| 23 | 23 |
| 24 from pylib import android_commands | 24 from pylib import android_commands |
| 25 from pylib import constants | 25 from pylib import constants |
| 26 from pylib.base import base_test_result | 26 from pylib.base import base_test_result |
| 27 from pylib.base import test_collection |
| 27 from pylib.device import device_errors | 28 from pylib.device import device_errors |
| 28 from pylib.utils import reraiser_thread | 29 from pylib.utils import reraiser_thread |
| 29 from pylib.utils import watchdog_timer | 30 from pylib.utils import watchdog_timer |
| 30 | 31 |
| 31 | 32 |
| 32 DEFAULT_TIMEOUT = 7 * 60 # seven minutes | 33 DEFAULT_TIMEOUT = 7 * 60 # seven minutes |
| 33 | 34 |
| 34 | 35 |
| 35 class _ThreadSafeCounter(object): | 36 class _ThreadSafeCounter(object): |
| 36 """A threadsafe counter.""" | 37 """A threadsafe counter.""" |
| (...skipping 21 matching lines...) Expand all Loading... |
| 58 """Initializes the _Test object. | 59 """Initializes the _Test object. |
| 59 | 60 |
| 60 Args: | 61 Args: |
| 61 test: The test. | 62 test: The test. |
| 62 tries: Number of tries so far. | 63 tries: Number of tries so far. |
| 63 """ | 64 """ |
| 64 self.test = test | 65 self.test = test |
| 65 self.tries = tries | 66 self.tries = tries |
| 66 | 67 |
| 67 | 68 |
| 68 class _TestCollection(object): | 69 def _RunTestsFromQueue(runner, collection, out_results, watcher, |
| 69 """A threadsafe collection of tests. | |
| 70 | |
| 71 Args: | |
| 72 tests: List of tests to put in the collection. | |
| 73 """ | |
| 74 | |
| 75 def __init__(self, tests=None): | |
| 76 if not tests: | |
| 77 tests = [] | |
| 78 self._lock = threading.Lock() | |
| 79 self._tests = [] | |
| 80 self._tests_in_progress = 0 | |
| 81 # Used to signal that an item is available or all items have been handled. | |
| 82 self._item_available_or_all_done = threading.Event() | |
| 83 for t in tests: | |
| 84 self.add(t) | |
| 85 | |
| 86 def _pop(self): | |
| 87 """Pop a test from the collection. | |
| 88 | |
| 89 Waits until a test is available or all tests have been handled. | |
| 90 | |
| 91 Returns: | |
| 92 A test or None if all tests have been handled. | |
| 93 """ | |
| 94 while True: | |
| 95 # Wait for a test to be available or all tests to have been handled. | |
| 96 self._item_available_or_all_done.wait() | |
| 97 with self._lock: | |
| 98 # Check which of the two conditions triggered the signal. | |
| 99 if self._tests_in_progress == 0: | |
| 100 return None | |
| 101 try: | |
| 102 return self._tests.pop(0) | |
| 103 except IndexError: | |
| 104 # Another thread beat us to the available test, wait again. | |
| 105 self._item_available_or_all_done.clear() | |
| 106 | |
| 107 def add(self, test): | |
| 108 """Add an test to the collection. | |
| 109 | |
| 110 Args: | |
| 111 test: A test to add. | |
| 112 """ | |
| 113 with self._lock: | |
| 114 self._tests.append(test) | |
| 115 self._item_available_or_all_done.set() | |
| 116 self._tests_in_progress += 1 | |
| 117 | |
| 118 def test_completed(self): | |
| 119 """Indicate that a test has been fully handled.""" | |
| 120 with self._lock: | |
| 121 self._tests_in_progress -= 1 | |
| 122 if self._tests_in_progress == 0: | |
| 123 # All tests have been handled, signal all waiting threads. | |
| 124 self._item_available_or_all_done.set() | |
| 125 | |
| 126 def __iter__(self): | |
| 127 """Iterate through tests in the collection until all have been handled.""" | |
| 128 while True: | |
| 129 r = self._pop() | |
| 130 if r is None: | |
| 131 break | |
| 132 yield r | |
| 133 | |
| 134 def __len__(self): | |
| 135 """Return the number of tests currently in the collection.""" | |
| 136 return len(self._tests) | |
| 137 | |
| 138 def test_names(self): | |
| 139 """Return a list of the names of the tests currently in the collection.""" | |
| 140 with self._lock: | |
| 141 return list(t.test for t in self._tests) | |
| 142 | |
| 143 | |
| 144 def _RunTestsFromQueue(runner, test_collection, out_results, watcher, | |
| 145 num_retries, tag_results_with_device=False): | 70 num_retries, tag_results_with_device=False): |
| 146 """Runs tests from the test_collection until empty using the given runner. | 71 """Runs tests from the collection until empty using the given runner. |
| 147 | 72 |
| 148 Adds TestRunResults objects to the out_results list and may add tests to the | 73 Adds TestRunResults objects to the out_results list and may add tests to the |
| 149 out_retry list. | 74 out_retry list. |
| 150 | 75 |
| 151 Args: | 76 Args: |
| 152 runner: A TestRunner object used to run the tests. | 77 runner: A TestRunner object used to run the tests. |
| 153 test_collection: A _TestCollection from which to get _Test objects to run. | 78 collection: A TestCollection from which to get _Test objects to run. |
| 154 out_results: A list to add TestRunResults to. | 79 out_results: A list to add TestRunResults to. |
| 155 watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout. | 80 watcher: A watchdog_timer.WatchdogTimer object, used as a shared timeout. |
| 156 num_retries: Number of retries for a test. | 81 num_retries: Number of retries for a test. |
| 157 tag_results_with_device: If True, appends the name of the device on which | 82 tag_results_with_device: If True, appends the name of the device on which |
| 158 the test was run to the test name. Used when replicating to identify | 83 the test was run to the test name. Used when replicating to identify |
| 159 which device ran each copy of the test, and to ensure each copy of the | 84 which device ran each copy of the test, and to ensure each copy of the |
| 160 test is recorded separately. | 85 test is recorded separately. |
| 161 """ | 86 """ |
| 162 | 87 |
| 163 def TagTestRunResults(test_run_results): | 88 def TagTestRunResults(test_run_results): |
| 164 """Tags all results with the last 4 digits of the device id. | 89 """Tags all results with the last 4 digits of the device id. |
| 165 | 90 |
| 166 Used when replicating tests to distinguish the same tests run on different | 91 Used when replicating tests to distinguish the same tests run on different |
| 167 devices. We use a set to store test results, so the hash (generated from | 92 devices. We use a set to store test results, so the hash (generated from |
| 168 name and tag) must be unique to be considered different results. | 93 name and tag) must be unique to be considered different results. |
| 169 """ | 94 """ |
| 170 new_test_run_results = base_test_result.TestRunResults() | 95 new_test_run_results = base_test_result.TestRunResults() |
| 171 for test_result in test_run_results.GetAll(): | 96 for test_result in test_run_results.GetAll(): |
| 172 test_result.SetName('%s_%s' % (runner.device_serial[-4:], | 97 test_result.SetName('%s_%s' % (runner.device_serial[-4:], |
| 173 test_result.GetName())) | 98 test_result.GetName())) |
| 174 new_test_run_results.AddResult(test_result) | 99 new_test_run_results.AddResult(test_result) |
| 175 return new_test_run_results | 100 return new_test_run_results |
| 176 | 101 |
| 177 for test in test_collection: | 102 for test in collection: |
| 178 watcher.Reset() | 103 watcher.Reset() |
| 179 try: | 104 try: |
| 180 if runner.device_serial not in android_commands.GetAttachedDevices(): | 105 if runner.device_serial not in android_commands.GetAttachedDevices(): |
| 181 # Device is unresponsive, stop handling tests on this device. | 106 # Device is unresponsive, stop handling tests on this device. |
| 182 msg = 'Device %s is unresponsive.' % runner.device_serial | 107 msg = 'Device %s is unresponsive.' % runner.device_serial |
| 183 logging.warning(msg) | 108 logging.warning(msg) |
| 184 raise device_errors.DeviceUnreachableError(msg) | 109 raise device_errors.DeviceUnreachableError(msg) |
| 185 result, retry = runner.RunTest(test.test) | 110 result, retry = runner.RunTest(test.test) |
| 186 if tag_results_with_device: | 111 if tag_results_with_device: |
| 187 result = TagTestRunResults(result) | 112 result = TagTestRunResults(result) |
| 188 test.tries += 1 | 113 test.tries += 1 |
| 189 if retry and test.tries <= num_retries: | 114 if retry and test.tries <= num_retries: |
| 190 # Retry non-passing results, only record passing results. | 115 # Retry non-passing results, only record passing results. |
| 191 pass_results = base_test_result.TestRunResults() | 116 pass_results = base_test_result.TestRunResults() |
| 192 pass_results.AddResults(result.GetPass()) | 117 pass_results.AddResults(result.GetPass()) |
| 193 out_results.append(pass_results) | 118 out_results.append(pass_results) |
| 194 logging.warning('Will retry test, try #%s.' % test.tries) | 119 logging.warning('Will retry test, try #%s.' % test.tries) |
| 195 test_collection.add(_Test(test=retry, tries=test.tries)) | 120 collection.add(_Test(test=retry, tries=test.tries)) |
| 196 else: | 121 else: |
| 197 # All tests passed or retry limit reached. Either way, record results. | 122 # All tests passed or retry limit reached. Either way, record results. |
| 198 out_results.append(result) | 123 out_results.append(result) |
| 199 except: | 124 except: |
| 200 # An unhandleable exception, ensure tests get run by another device and | 125 # An unhandleable exception, ensure tests get run by another device and |
| 201 # reraise this exception on the main thread. | 126 # reraise this exception on the main thread. |
| 202 test_collection.add(test) | 127 collection.add(test) |
| 203 raise | 128 raise |
| 204 finally: | 129 finally: |
| 205 # Retries count as separate tasks so always mark the popped test as done. | 130 # Retries count as separate tasks so always mark the popped test as done. |
| 206 test_collection.test_completed() | 131 collection.test_completed() |
| 207 | 132 |
| 208 | 133 |
| 209 def _SetUp(runner_factory, device, out_runners, threadsafe_counter): | 134 def _SetUp(runner_factory, device, out_runners, threadsafe_counter): |
| 210 """Creates a test runner for each device and calls SetUp() in parallel. | 135 """Creates a test runner for each device and calls SetUp() in parallel. |
| 211 | 136 |
| 212 Note: if a device is unresponsive the corresponding TestRunner will not be | 137 Note: if a device is unresponsive the corresponding TestRunner will not be |
| 213 added to out_runners. | 138 added to out_runners. |
| 214 | 139 |
| 215 Args: | 140 Args: |
| 216 runner_factory: Callable that takes a device and index and returns a | 141 runner_factory: Callable that takes a device and index and returns a |
| (...skipping 14 matching lines...) Expand all Loading... |
| 231 android_commands.errors.DeviceUnresponsiveError) as e: | 156 android_commands.errors.DeviceUnresponsiveError) as e: |
| 232 logging.warning('Failed to create shard for %s: [%s]', device, e) | 157 logging.warning('Failed to create shard for %s: [%s]', device, e) |
| 233 | 158 |
| 234 | 159 |
| 235 def _RunAllTests(runners, test_collection_factory, num_retries, timeout=None, | 160 def _RunAllTests(runners, test_collection_factory, num_retries, timeout=None, |
| 236 tag_results_with_device=False): | 161 tag_results_with_device=False): |
| 237 """Run all tests using the given TestRunners. | 162 """Run all tests using the given TestRunners. |
| 238 | 163 |
| 239 Args: | 164 Args: |
| 240 runners: A list of TestRunner objects. | 165 runners: A list of TestRunner objects. |
| 241 test_collection_factory: A callable to generate a _TestCollection object for | 166 test_collection_factory: A callable to generate a TestCollection object for |
| 242 each test runner. | 167 each test runner. |
| 243 num_retries: Number of retries for a test. | 168 num_retries: Number of retries for a test. |
| 244 timeout: Watchdog timeout in seconds. | 169 timeout: Watchdog timeout in seconds. |
| 245 tag_results_with_device: If True, appends the name of the device on which | 170 tag_results_with_device: If True, appends the name of the device on which |
| 246 the test was run to the test name. Used when replicating to identify | 171 the test was run to the test name. Used when replicating to identify |
| 247 which device ran each copy of the test, and to ensure each copy of the | 172 which device ran each copy of the test, and to ensure each copy of the |
| 248 test is recorded separately. | 173 test is recorded separately. |
| 249 | 174 |
| 250 Returns: | 175 Returns: |
| 251 A tuple of (TestRunResults object, exit code) | 176 A tuple of (TestRunResults object, exit code) |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 377 | 302 |
| 378 Returns: | 303 Returns: |
| 379 A tuple of (base_test_result.TestRunResults object, exit code). | 304 A tuple of (base_test_result.TestRunResults object, exit code). |
| 380 """ | 305 """ |
| 381 if not tests: | 306 if not tests: |
| 382 logging.critical('No tests to run.') | 307 logging.critical('No tests to run.') |
| 383 return (base_test_result.TestRunResults(), constants.ERROR_EXIT_CODE) | 308 return (base_test_result.TestRunResults(), constants.ERROR_EXIT_CODE) |
| 384 | 309 |
| 385 tests_expanded = ApplyMaxPerRun(tests, max_per_run) | 310 tests_expanded = ApplyMaxPerRun(tests, max_per_run) |
| 386 if shard: | 311 if shard: |
| 387 # Generate a shared _TestCollection object for all test runners, so they | 312 # Generate a shared TestCollection object for all test runners, so they |
| 388 # draw from a common pool of tests. | 313 # draw from a common pool of tests. |
| 389 shared_test_collection = _TestCollection([_Test(t) for t in tests_expanded]) | 314 shared_test_collection = test_collection.TestCollection( |
| 315 [_Test(t) for t in tests_expanded]) |
| 390 test_collection_factory = lambda: shared_test_collection | 316 test_collection_factory = lambda: shared_test_collection |
| 391 tag_results_with_device = False | 317 tag_results_with_device = False |
| 392 log_string = 'sharded across devices' | 318 log_string = 'sharded across devices' |
| 393 else: | 319 else: |
| 394 # Generate a unique _TestCollection object for each test runner, but use | 320 # Generate a unique TestCollection object for each test runner, but use |
| 395 # the same set of tests. | 321 # the same set of tests. |
| 396 test_collection_factory = lambda: _TestCollection( | 322 test_collection_factory = lambda: test_collection.TestCollection( |
| 397 [_Test(t) for t in tests_expanded]) | 323 [_Test(t) for t in tests_expanded]) |
| 398 tag_results_with_device = True | 324 tag_results_with_device = True |
| 399 log_string = 'replicated on each device' | 325 log_string = 'replicated on each device' |
| 400 | 326 |
| 401 logging.info('Will run %d tests (%s): %s', | 327 logging.info('Will run %d tests (%s): %s', |
| 402 len(tests_expanded), log_string, str(tests_expanded)) | 328 len(tests_expanded), log_string, str(tests_expanded)) |
| 403 runners = _CreateRunners(runner_factory, devices, setup_timeout) | 329 runners = _CreateRunners(runner_factory, devices, setup_timeout) |
| 404 try: | 330 try: |
| 405 return _RunAllTests(runners, test_collection_factory, | 331 return _RunAllTests(runners, test_collection_factory, |
| 406 num_retries, test_timeout, tag_results_with_device) | 332 num_retries, test_timeout, tag_results_with_device) |
| 407 finally: | 333 finally: |
| 408 try: | 334 try: |
| 409 _TearDownRunners(runners, setup_timeout) | 335 _TearDownRunners(runners, setup_timeout) |
| 410 except (device_errors.DeviceUnreachableError, | 336 except (device_errors.DeviceUnreachableError, |
| 411 # TODO(jbudorick) Remove this once the underlying implementations | 337 # TODO(jbudorick) Remove this once the underlying implementations |
| 412 # for the above are switched or wrapped. | 338 # for the above are switched or wrapped. |
| 413 android_commands.errors.DeviceUnresponsiveError) as e: | 339 android_commands.errors.DeviceUnresponsiveError) as e: |
| 414 logging.warning('Device unresponsive during TearDown: [%s]', e) | 340 logging.warning('Device unresponsive during TearDown: [%s]', e) |
| 415 except Exception as e: | 341 except Exception as e: |
| 416 logging.error('Unexpected exception caught during TearDown: %s' % str(e)) | 342 logging.error('Unexpected exception caught during TearDown: %s' % str(e)) |
| OLD | NEW |