Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(415)

Side by Side Diff: build/android/pylib/base/test_dispatcher.py

Issue 723343002: Update from https://crrev.com/304121 (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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))
OLDNEW
« no previous file with comments | « build/android/pylib/base/test_collection.py ('k') | build/android/pylib/base/test_dispatcher_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698