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

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: Created 7 years, 10 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 _TasksDoneException(Exception):
18 """Runs tests from the test_queue using the given runner in a separate thread. 18 pass
19 19
20 Places results in the out_results. 20
21 """ 21 class _Test(object):
22 def __init__(self, runner, test_queue, out_results, out_retry): 22 """Holds a test with additional metadata."""
23 """Initializes the worker. 23 def __init__(self, test, tries=0):
24 """Initializes the _Test object.
24 25
25 Args: 26 Args:
26 runner: A TestRunner object used to run the tests. 27 test: the test.
27 test_queue: A list from which to get tests to run. 28 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 """ 29 """
31 super(_Worker, self).__init__() 30 self.test = test
32 self.daemon = True 31 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 32
39 #override
40 def run(self):
41 """Run tests from the queue in a seperate thread until it is empty.
42 33
43 Adds TestResults objects to the out_results list and may add tests to the 34 class _TestQueue(object):
44 out_retry list. 35 """A queue that implements specific blocking semantics.
36
37 Args:
38 items: items to put in the queue.
39 """
40 def __init__(self, items=[]):
41 self._lock = threading.Lock()
42 self._items = list(items)
43 self._incomplete_count = len(self._items)
44 self._can_pop = threading.Event()
45 self._can_pop.set()
46
47 def pop(self):
48 """Pop an item from the queue.
49
50 Waits until an item is avaliable or all items have been handled.
51
52 Returns:
53 An item or None if all items have been handled.
45 """ 54 """
55 while True:
56 self._can_pop.wait()
57 with self._lock:
58 if self._incomplete_count == 0:
59 return None
60 try:
61 return self._items.pop()
62 except IndexError:
63 self._can_pop.clear()
64
65 def add(self, item):
66 """Add an item to the queue.
67
68 Args:
69 item: An item to add.
70 """
71 with self._lock:
72 self._items.append(item)
73 self._can_pop.set()
74 self._incomplete_count += 1
75
76 def task_done(self):
77 """Indicate that a queue item has been fully handled."""
78 with self._lock:
79 self._incomplete_count -= 1
80 if self._incomplete_count == 0:
81 self._can_pop.set()
82 assert self._incomplete_count >= 0
83
84 def __iter__(self):
85 """Iterate through items in the queue until all items have been handled."""
86 while True:
87 r = self.pop()
88 if r is None:
89 break
90 yield r
91
92
93 def _RunTestsFromQueue(runner, test_queue, out_results):
94 """Runs tests from the test_queue until empty using the given runner.
95
96 Adds TestResults objects to the out_results list and may add tests to the
97 out_retry list.
98
99 Args:
100 runner: A TestRunner object used to run the tests.
101 test_queue: A _TestQueue from which to get _Test objects to run.
102 out_results: A list to add TestResults to.
103 """
104 for test in test_queue:
105 if not android_commands.IsDeviceAttached(runner.device):
106 raise android_commands.errors.DeviceUnresponsiveError(
107 'Device %s is unresponsive.' % runner.device)
46 try: 108 try:
47 while True: 109 result, retry = runner.RunTest(test.test)
48 test = self._test_queue.pop() 110 # TODO(frankf): Don't break TestResults encapsulation.
49 result, retry = self._runner.Run(test) 111 out_results.append(test_result.TestResults.FromRun(ok=result.ok))
50 self._out_results.append(result) 112 if retry:
51 if retry: 113 if test.tries == 2:
52 self._out_retry.append(retry) 114 # Out of retries, store results.
53 except IndexError: 115 result.ok = []
54 pass 116 out_results.append(result)
117 else:
118 # Retry, don't store results.
119 test_queue.add(_Test(test=retry, tries=test.tries + 1))
120 except android_commands.errors.DeviceUnresponsiveError:
121 test_queue.add(test)
55 except: 122 except:
56 self._exc_info = sys.exc_info() 123 test_queue.add(test)
57 raise 124 raise
125 finally:
126 test_queue.task_done()
58 127
59 def ReraiseIfException(self): 128
60 """Reraise exception if an exception was raised in the thread.""" 129 def _SetUp(runner_factory, device, out_runners):
61 if self._exc_info: 130 """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] 131
132 Note: if a device is unresponsive the corresponding TestRunner will not be
133 added to out_runners.
134
135 Args:
136 runner_factory: callable that takes a device and returns a TestRunner.
137 device: the device serial number to set up.
138 out_runners: list to add the successfully set up TestRunner object.
139 """
140 try:
141 logging.warning('****Creating shard for %s', device)
142 runner = runner_factory(device)
143 runner.SetUp()
144 out_runners.append(runner)
145 except android_commands.errors.DeviceUnresponsiveError as e:
146 logging.warning('****Failed to create shard for %s: [%s]', (device, e))
63 147
64 148
65 def _RunAllTests(runners, tests): 149 def _RunAllTests(runners, tests):
66 """Run all tests using the given TestRunners. 150 """Run all tests using the given TestRunners.
67 151
68 Args: 152 Args:
69 runners: a list of TestRunner objects. 153 runners: a list of TestRunner objects.
70 tests: a list of Tests to run using the given TestRunners. 154 tests: a list of Tests to run using the given TestRunners.
71 155
72 Returns: 156 Returns:
73 Tuple: (list of TestResults, list of tests to retry) 157 A TestResults object.
74 """ 158 """
75 tests_queue = list(tests) 159 tests_queue = _TestQueue([_Test(t) for t in tests])
76 workers = []
77 results = [] 160 results = []
78 retry = [] 161 workers = reraiser_thread.ReraiserThreadGroup([reraiser_thread.ReraiserThread(
79 for r in runners: 162 _RunTestsFromQueue, [r, tests_queue, results]) for r in runners])
80 worker = _Worker(r, tests_queue, results, retry) 163 workers.StartAll()
81 worker.start() 164 workers.JoinAll()
82 workers.append(worker) 165 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 166
92 167
93 def _CreateRunners(runner_factory, devices): 168 def _CreateRunners(runner_factory, devices):
94 """Creates a test runner for each device. 169 """Creates a test runner for each device and calls SetUp() in parallel.
95 170
96 Note: if a device is unresponsive the corresponding TestRunner will not be 171 Note: if a device is unresponsive the corresponding TestRunner will not be
97 included in the returned list. 172 included in the returned list.
98 173
99 Args: 174 Args:
100 runner_factory: callable that takes a device and returns a TestRunner. 175 runner_factory: callable that takes a device and returns a TestRunner.
101 devices: list of device serial numbers as strings. 176 devices: list of device serial numbers as strings.
102 177
103 Returns: 178 Returns:
104 A list of TestRunner objects. 179 A list of TestRunner objects.
105 """ 180 """
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. 210 tries: number of tries before accepting failure.
128 211
129 Returns: 212 Returns:
130 A test_result.TestResults object. 213 A test_result.TestResults object.
131 """ 214 """
132 final_results = test_result.TestResults()
133 results = test_result.TestResults()
134 forwarder.Forwarder.KillHost(build_type) 215 forwarder.Forwarder.KillHost(build_type)
135 try_count = 0 216 runners = _CreateRunners(runner_factory, devices)
136 while tests: 217 try:
137 devices = set(devices).intersection(android_commands.GetAttachedDevices()) 218 return _RunAllTests(runners, tests)
138 if not devices: 219 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: 220 try:
151 results_list, tests = _RunAllTests(runners, tests) 221 _TearDownRunners(runners)
152 results = test_result.TestResults.FromTestResults(results_list)
153 final_results.ok += results.ok
154 except android_commands.errors.DeviceUnresponsiveError as e: 222 except android_commands.errors.DeviceUnresponsiveError as e:
155 logging.warning('****Failed to run test: [%s]', e) 223 logging.warning('****Device unresponsive during TearDown: [%s]', e)
156 forwarder.Forwarder.KillHost(build_type) 224 finally:
157 return final_results 225 forwarder.Forwarder.KillHost(build_type)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698