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

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

Issue 12317059: [Andoid] Threaded TestRunner creation and SetUp and TearDown calls. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: nit Created 7 years, 9 months 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 | Annotate | Revision Log
OLDNEW
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 sys
9 import threading 8 import threading
10 9
11 from pylib import android_commands 10 from pylib import android_commands
12 from pylib import forwarder 11 from pylib import forwarder
12 from pylib.utils import reraiser_thread
13 13
14 import test_result 14 import test_result
15 15
16 16
17 class _Worker(threading.Thread): 17 class _Test(object):
18 """Runs tests from the test_queue using the given runner in a separate thread. 18 """Holds a test with additional metadata."""
19 19 def __init__(self, test, tries=0):
20 Places results in the out_results. 20 """Initializes the _Test object.
21 """
22 def __init__(self, runner, test_queue, out_results, out_retry):
23 """Initializes the worker.
24 21
25 Args: 22 Args:
26 runner: A TestRunner object used to run the tests. 23 test: the test.
27 test_queue: A list from which to get tests to run. 24 tries: number of tries so far.
28 out_results: A list to add TestResults to.
29 out_retry: A list to add tests to retry.
30 """ 25 """
31 super(_Worker, self).__init__() 26 self.test = test
32 self.daemon = True 27 self.tries = tries
33 self._exc_info = None
34 self._runner = runner
35 self._test_queue = test_queue
36 self._out_results = out_results
37 self._out_retry = out_retry
38 28
39 #override
40 def run(self):
41 """Run tests from the queue in a seperate thread until it is empty.
42 29
43 Adds TestResults objects to the out_results list and may add tests to the 30 class _TestQueue(object):
44 out_retry list. 31 """A queue that implements specific blocking semantics.
32
33 Args:
34 items: items to put in the queue.
35 """
36 def __init__(self, items=[]):
37 self._lock = threading.Lock()
38 self._items = list(items)
39 self._incomplete_count = len(self._items)
frankf 2013/02/26 00:57:07 incomplete_count -> tests_in_progress
craigdh 2013/02/26 01:23:16 Done.
40 self._can_pop = threading.Event()
frankf 2013/02/26 00:57:07 This event is signalling two things 1) when an ite
craigdh 2013/02/26 01:23:16 Improved naming and added comments.
41 self._can_pop.set()
frankf 2013/02/26 00:57:07 what if items is empty?
craigdh 2013/02/26 01:23:16 It got cleared if someone went to pop and found th
42
43 def pop(self):
44 """Pop an item from the queue.
45
46 Waits until an item is avaliable or all items have been handled.
47
48 Returns:
49 An item or None if all items have been handled.
45 """ 50 """
51 while True:
52 self._can_pop.wait()
53 with self._lock:
54 if self._incomplete_count == 0:
55 return None
56 try:
57 return self._items.pop()
58 except IndexError:
59 self._can_pop.clear()
60
61 def add(self, item):
62 """Add an item to the queue.
63
64 Args:
65 item: An item to add.
66 """
67 with self._lock:
68 self._items.append(item)
69 self._can_pop.set()
70 self._incomplete_count += 1
71
72 def task_done(self):
73 """Indicate that a queue item has been fully handled."""
74 with self._lock:
75 self._incomplete_count -= 1
76 if self._incomplete_count == 0:
77 self._can_pop.set()
78 assert self._incomplete_count >= 0
79
80 def __iter__(self):
81 """Iterate through items in the queue until all items have been handled."""
82 while True:
83 r = self.pop()
84 if r is None:
85 break
86 yield r
87
88
89 def _RunTestsFromQueue(runner, test_queue, out_results):
90 """Runs tests from the test_queue until empty using the given runner.
91
92 Adds TestResults objects to the out_results list and may add tests to the
93 out_retry list.
94
95 Args:
96 runner: A TestRunner object used to run the tests.
97 test_queue: A _TestQueue from which to get _Test objects to run.
98 out_results: A list to add TestResults to.
99 """
100 for test in test_queue:
101 if not android_commands.IsDeviceAttached(runner.device):
102 raise android_commands.errors.DeviceUnresponsiveError(
103 'Device %s is unresponsive.' % runner.device)
46 try: 104 try:
47 while True: 105 result, retry = runner.RunTest(test.test)
48 test = self._test_queue.pop() 106 # TODO(frankf): Don't break TestResults encapsulation.
49 result, retry = self._runner.Run(test) 107 out_results.append(test_result.TestResults.FromRun(ok=result.ok))
50 self._out_results.append(result) 108 if retry:
51 if retry: 109 if test.tries == 2:
52 self._out_retry.append(retry) 110 # Out of retries, store results.
53 except IndexError: 111 result.ok = []
54 pass 112 out_results.append(result)
113 else:
114 # Retry, don't store results.
115 logging.warning('****Retrying test, retry #%s.' % (test.tries + 1))
116 test_queue.add(_Test(test=retry, tries=test.tries + 1))
117 except android_commands.errors.DeviceUnresponsiveError:
118 test_queue.add(test)
55 except: 119 except:
56 self._exc_info = sys.exc_info() 120 test_queue.add(test)
57 raise 121 raise
122 finally:
123 test_queue.task_done()
58 124
59 def ReraiseIfException(self): 125
60 """Reraise exception if an exception was raised in the thread.""" 126 def _SetUp(runner_factory, device, out_runners):
61 if self._exc_info: 127 """Creates a test runner for each device and calls SetUp() in parallel.
62 raise self._exc_info[0], self._exc_info[1], self._exc_info[2] 128
129 Note: if a device is unresponsive the corresponding TestRunner will not be
130 added to out_runners.
131
132 Args:
133 runner_factory: callable that takes a device and returns a TestRunner.
134 device: the device serial number to set up.
135 out_runners: list to add the successfully set up TestRunner object.
136 """
137 try:
138 logging.warning('*****Creating shard for %s.', device)
139 runner = runner_factory(device)
140 runner.SetUp()
141 out_runners.append(runner)
142 except android_commands.errors.DeviceUnresponsiveError as e:
143 logging.warning('****Failed to create shard for %s: [%s]', (device, e))
63 144
64 145
65 def _RunAllTests(runners, tests): 146 def _RunAllTests(runners, tests):
66 """Run all tests using the given TestRunners. 147 """Run all tests using the given TestRunners.
67 148
68 Args: 149 Args:
69 runners: a list of TestRunner objects. 150 runners: a list of TestRunner objects.
70 tests: a list of Tests to run using the given TestRunners. 151 tests: a list of Tests to run using the given TestRunners.
71 152
72 Returns: 153 Returns:
73 Tuple: (list of TestResults, list of tests to retry) 154 A TestResults object.
74 """ 155 """
75 tests_queue = list(tests) 156 logging.warning('****Running %s tests with %s test runners.' %
76 workers = [] 157 (len(tests), len(runners)))
158 tests_queue = _TestQueue([_Test(t) for t in tests])
77 results = [] 159 results = []
78 retry = [] 160 workers = reraiser_thread.ReraiserThreadGroup([reraiser_thread.ReraiserThread(
79 for r in runners: 161 _RunTestsFromQueue, [r, tests_queue, results]) for r in runners])
80 worker = _Worker(r, tests_queue, results, retry) 162 workers.StartAll()
81 worker.start() 163 workers.JoinAll()
82 workers.append(worker) 164 return test_result.TestResults.FromTestResults(results)
83 while workers:
84 for w in workers[:]:
85 # Allow the main thread to periodically check for keyboard interrupts.
86 w.join(0.1)
87 if not w.isAlive():
88 w.ReraiseIfException()
89 workers.remove(w)
90 return (results, retry)
91 165
92 166
93 def _CreateRunners(runner_factory, devices): 167 def _CreateRunners(runner_factory, devices):
94 """Creates a test runner for each device. 168 """Creates a test runner for each device and calls SetUp() in parallel.
95 169
96 Note: if a device is unresponsive the corresponding TestRunner will not be 170 Note: if a device is unresponsive the corresponding TestRunner will not be
97 included in the returned list. 171 included in the returned list.
98 172
99 Args: 173 Args:
100 runner_factory: callable that takes a device and returns a TestRunner. 174 runner_factory: callable that takes a device and returns a TestRunner.
101 devices: list of device serial numbers as strings. 175 devices: list of device serial numbers as strings.
102 176
103 Returns: 177 Returns:
104 A list of TestRunner objects. 178 A list of TestRunner objects.
105 """ 179 """
180 logging.warning('****Creating %s test runners.' % len(devices))
106 test_runners = [] 181 test_runners = []
107 for index, device in enumerate(devices): 182 threads = reraiser_thread.ReraiserThreadGroup(
108 logging.warning('*' * 80) 183 [reraiser_thread.ReraiserThread(_SetUp, [runner_factory, d, test_runners])
109 logging.warning('Creating shard %d for %s', index, device) 184 for d in devices])
110 logging.warning('*' * 80) 185 threads.StartAll()
111 try: 186 threads.JoinAll()
112 test_runners.append(runner_factory(device))
113 except android_commands.errors.DeviceUnresponsiveError as e:
114 logging.warning('****Failed to create a shard: [%s]', e)
115 return test_runners 187 return test_runners
116 188
117 189
118 def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug', 190 def _TearDownRunners(runners):
119 tries=3): 191 """Calls TearDown() for each test runner in parallel.
192 Args:
193 runners: a list of TestRunner objects.
194 """
195 threads = reraiser_thread.ReraiserThreadGroup(
196 [reraiser_thread.ReraiserThread(runner.TearDown)
197 for runner in runners])
198 threads.StartAll()
199 threads.JoinAll()
200
201
202 def ShardAndRunTests(runner_factory, devices, tests, build_type='Debug'):
120 """Run all tests on attached devices, retrying tests that don't pass. 203 """Run all tests on attached devices, retrying tests that don't pass.
121 204
122 Args: 205 Args:
123 runner_factory: callable that takes a device and returns a TestRunner. 206 runner_factory: callable that takes a device and returns a TestRunner.
124 devices: list of attached device serial numbers as strings. 207 devices: list of attached device serial numbers as strings.
125 tests: list of tests to run. 208 tests: list of tests to run.
126 build_type: either 'Debug' or 'Release'. 209 build_type: either 'Debug' or 'Release'.
127 tries: number of tries before accepting failure.
128 210
129 Returns: 211 Returns:
130 A test_result.TestResults object. 212 A test_result.TestResults object.
131 """ 213 """
132 final_results = test_result.TestResults()
133 results = test_result.TestResults()
134 forwarder.Forwarder.KillHost(build_type) 214 forwarder.Forwarder.KillHost(build_type)
135 try_count = 0 215 runners = _CreateRunners(runner_factory, devices)
136 while tests: 216 try:
137 devices = set(devices).intersection(android_commands.GetAttachedDevices()) 217 return _RunAllTests(runners, tests)
138 if not devices: 218 finally:
139 # There are no visible devices attached, this is unrecoverable.
140 msg = 'No devices attached and visible to run tests!'
141 logging.critical(msg)
142 raise Exception(msg)
143 if try_count >= tries:
144 # We've retried too many times, return the TestResults up to this point.
145 results.ok = final_results.ok
146 final_results = results
147 break
148 try_count += 1
149 runners = _CreateRunners(runner_factory, devices)
150 try: 219 try:
151 results_list, tests = _RunAllTests(runners, tests) 220 _TearDownRunners(runners)
152 results = test_result.TestResults.FromTestResults(results_list)
153 final_results.ok += results.ok
154 except android_commands.errors.DeviceUnresponsiveError as e: 221 except android_commands.errors.DeviceUnresponsiveError as e:
155 logging.warning('****Failed to run test: [%s]', e) 222 logging.warning('****Device unresponsive during TearDown: [%s]', e)
156 forwarder.Forwarder.KillHost(build_type) 223 finally:
157 return final_results 224 forwarder.Forwarder.KillHost(build_type)
OLDNEW
« no previous file with comments | « build/android/pylib/base/new_base_test_runner.py ('k') | build/android/pylib/base/shard_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698