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 |