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

Side by Side Diff: scripts/slave/unittests/expect_tests/pipeline.py

Issue 354913003: Add module discovery and autoloading to expect_tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Fix formatting + comment Created 6 years, 5 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 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 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 import Queue 5 import Queue
6 import atexit
6 import glob 7 import glob
7 import logging 8 import logging
8 import multiprocessing 9 import multiprocessing
9 import re 10 import re
10 import signal 11 import signal
11 import traceback 12 import traceback
12 13
13 from cStringIO import StringIO 14 from cStringIO import StringIO
14 15
15 from .type_definitions import ( 16 from .type_definitions import (
16 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, 17 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest,
17 Result, ResultStageAbort) 18 Result, ResultStageAbort, Cleanup)
18 19
19 from . import util 20 from . import util
20 21
21 22
22 class ResetableStringIO(object): 23 class ResetableStringIO(object):
23 def __init__(self): 24 def __init__(self):
24 self._stream = StringIO() 25 self._stream = StringIO()
25 26
26 def reset(self): 27 def reset(self):
27 self._stream = StringIO() 28 self._stream = StringIO()
28 29
29 def __getattr__(self, key): 30 def __getattr__(self, key):
30 return getattr(self._stream, key) 31 return getattr(self._stream, key)
31 32
32 33
33 def gen_loop_process(gen, test_queue, result_queue, opts, kill_switch, 34 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch,
34 cover_ctx): 35 cover_ctx):
35 """Generate `Test`'s from |gen|, and feed them into |test_queue|. 36 """Generate `Test`'s from |gens|, and feed them into |test_queue|.
36 37
37 Non-Test instances will be translated into `UnknownError` objects. 38 Non-Test instances will be translated into `UnknownError` objects.
38 39
39 On completion, feed |opts.jobs| None objects into |test_queue|. 40 On completion, feed |opts.jobs| None objects into |test_queue|.
40 41
41 @param gen: generator yielding Test() instances. 42 @param gens: list of generators yielding Test() instances.
42 @type test_queue: multiprocessing.Queue() 43 @type test_queue: multiprocessing.Queue()
43 @type result_queue: multiprocessing.Queue() 44 @type result_queue: multiprocessing.Queue()
44 @type opts: argparse.Namespace 45 @type opts: argparse.Namespace
45 @type kill_switch: multiprocessing.Event() 46 @type kill_switch: multiprocessing.Event()
46 @type cover_ctx: cover.CoverageContext().create_subprocess_context() 47 @type cover_ctx: cover.CoverageContext().create_subprocess_context()
47 """ 48 """
48 # Implicitly append '*'' to globs that don't specify it. 49 # Implicitly append '*'' to globs that don't specify it.
49 globs = ['%s%s' % (g, '*' if '*' not in g else '') for g in opts.test_glob] 50 globs = ['%s%s' % (g, '*' if '*' not in g else '') for g in opts.test_glob]
50 51
51 matcher = re.compile( 52 matcher = re.compile(
52 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g) 53 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g)
53 for g in globs if g[0] != '-')) 54 for g in globs if g[0] != '-'))
54 if matcher.pattern == '^$': 55 if matcher.pattern == '^$':
55 matcher = re.compile('^.*$') 56 matcher = re.compile('^.*$')
56 57
57 neg_matcher = re.compile( 58 neg_matcher = re.compile(
58 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g[1:]) 59 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g[1:])
59 for g in globs if g[0] == '-')) 60 for g in globs if g[0] == '-'))
60 61
62 SENTINEL = object()
63
61 def generate_tests(): 64 def generate_tests():
62 paths_seen = set() 65 paths_seen = set()
63 seen_tests = False 66 seen_tests = False
64 try: 67 try:
65 with cover_ctx: 68 for gen in gens:
66 gen_inst = gen() 69 gen_cover_ctx = cover_ctx(include=util.get_cover_list(gen))
67 70
68 while not kill_switch.is_set(): 71 with gen_cover_ctx:
69 with cover_ctx: 72 gen_inst = gen()
70 root_test = next(gen_inst)
71 73
72 if kill_switch.is_set(): 74 while not kill_switch.is_set():
73 break 75 with gen_cover_ctx:
76 root_test = next(gen_inst, SENTINEL)
74 77
75 ok_tests = [] 78 if root_test is SENTINEL:
79 break
76 80
77 if isinstance(root_test, MultiTest): 81 if kill_switch.is_set():
78 subtests = root_test.tests 82 break
79 else:
80 subtests = [root_test]
81 83
82 for subtest in subtests: 84 ok_tests = []
83 if not isinstance(subtest, Test): 85
84 result_queue.put_nowait( 86 if isinstance(root_test, Cleanup):
85 UnknownError('Got non-[Multi]Test isinstance from generator: %r' 87 result_queue.put_nowait(root_test)
86 % subtest))
87 continue 88 continue
88 89
89 test_path = subtest.expect_path() 90 if isinstance(root_test, MultiTest):
90 if test_path is not None and test_path in paths_seen: 91 subtests = root_test.tests
91 result_queue.put_nowait(
92 TestError(subtest, 'Duplicate expectation path!'))
93 else: 92 else:
94 if test_path is not None: 93 subtests = [root_test]
95 paths_seen.add(test_path)
96 name = subtest.name
97 if not neg_matcher.match(name) and matcher.match(name):
98 ok_tests.append(subtest)
99 94
100 if ok_tests: 95 for subtest in subtests:
101 seen_tests = True 96 if not isinstance(subtest, Test):
102 yield root_test.restrict(ok_tests) 97 result_queue.put_nowait(
98 UnknownError(
99 'Got non-[Multi]Test isinstance from generator: %r'
100 % subtest))
101 continue
102
103 test_path = subtest.expect_path()
104 if test_path is not None and test_path in paths_seen:
105 result_queue.put_nowait(
106 TestError(subtest, 'Duplicate expectation path!'))
107 else:
108 if test_path is not None:
109 paths_seen.add(test_path)
110 name = subtest.name
111 if not neg_matcher.match(name) and matcher.match(name):
112 ok_tests.append(subtest)
113
114 if ok_tests:
115 seen_tests = True
116 yield root_test.restrict(ok_tests)
103 117
104 if not seen_tests: 118 if not seen_tests:
105 result_queue.put_nowait(NoMatchingTestsError()) 119 result_queue.put_nowait(NoMatchingTestsError())
106 except StopIteration:
107 pass
108 except KeyboardInterrupt: 120 except KeyboardInterrupt:
109 pass 121 pass
110 finally: 122 finally:
111 for _ in xrange(opts.jobs): 123 for _ in xrange(opts.jobs):
112 test_queue.put_nowait(None) 124 test_queue.put_nowait(None)
113 125
114 126
115 next_stage = (result_queue if opts.handler.SKIP_RUNLOOP else test_queue) 127 next_stage = (result_queue if opts.handler.SKIP_RUNLOOP else test_queue)
116 opts.handler.gen_stage_loop(opts, generate_tests(), next_stage.put_nowait, 128 opts.handler.gen_stage_loop(opts, generate_tests(), next_stage.put_nowait,
117 result_queue.put_nowait) 129 result_queue.put_nowait)
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 result_queue.put_nowait( 183 result_queue.put_nowait(
172 TestError(test, traceback.format_exc(), 184 TestError(test, traceback.format_exc(),
173 logstream.getvalue().splitlines())) 185 logstream.getvalue().splitlines()))
174 except KeyboardInterrupt: 186 except KeyboardInterrupt:
175 pass 187 pass
176 188
177 opts.handler.run_stage_loop(opts, generate_tests_results(), 189 opts.handler.run_stage_loop(opts, generate_tests_results(),
178 result_queue.put_nowait) 190 result_queue.put_nowait)
179 191
180 192
181 def result_loop(test_gen, cover_ctx, opts): 193 def result_loop(test_gens, cover_ctx, opts):
182 kill_switch = multiprocessing.Event() 194 kill_switch = multiprocessing.Event()
183 def handle_killswitch(*_): 195 def handle_killswitch(*_):
184 kill_switch.set() 196 kill_switch.set()
185 # Reset the signal to DFL so that double ctrl-C kills us for sure. 197 # Reset the signal to DFL so that double ctrl-C kills us for sure.
186 signal.signal(signal.SIGINT, signal.SIG_DFL) 198 signal.signal(signal.SIGINT, signal.SIG_DFL)
187 signal.signal(signal.SIGTERM, signal.SIG_DFL) 199 signal.signal(signal.SIGTERM, signal.SIG_DFL)
188 signal.signal(signal.SIGINT, handle_killswitch) 200 signal.signal(signal.SIGINT, handle_killswitch)
189 signal.signal(signal.SIGTERM, handle_killswitch) 201 signal.signal(signal.SIGTERM, handle_killswitch)
190 202
191 test_queue = multiprocessing.Queue() 203 test_queue = multiprocessing.Queue()
192 result_queue = multiprocessing.Queue() 204 result_queue = multiprocessing.Queue()
193 205
194 gen_cover_ctx = cover_ctx
195 if cover_ctx.enabled:
196 gen_cover_ctx = cover_ctx(include=util.get_cover_list(test_gen))
197
198 test_gen_args = ( 206 test_gen_args = (
199 test_gen, test_queue, result_queue, opts, kill_switch, gen_cover_ctx) 207 test_gens, test_queue, result_queue, opts, kill_switch, cover_ctx)
200 208
201 procs = [] 209 procs = []
202 if opts.handler.SKIP_RUNLOOP: 210 if opts.handler.SKIP_RUNLOOP:
203 gen_loop_process(*test_gen_args) 211 gen_loop_process(*test_gen_args)
204 else: 212 else:
205 procs = [multiprocessing.Process( 213 procs = [multiprocessing.Process(
206 target=gen_loop_process, args=test_gen_args)] 214 target=gen_loop_process, args=test_gen_args)]
207 215
208 procs += [ 216 procs += [
209 multiprocessing.Process( 217 multiprocessing.Process(
210 target=run_loop_process, args=( 218 target=run_loop_process, args=(
211 test_queue, result_queue, opts, kill_switch, cover_ctx)) 219 test_queue, result_queue, opts, kill_switch, cover_ctx))
212 for _ in xrange(opts.jobs) 220 for _ in xrange(opts.jobs)
213 ] 221 ]
214 222
215 for p in procs: 223 for p in procs:
216 p.daemon = True 224 p.daemon = True
217 p.start() 225 p.start()
218 226
219 error = False 227 error = False
220 try: 228 try:
221 def generate_objects(): 229 def generate_objects():
222 while not kill_switch.is_set(): 230 while not kill_switch.is_set():
223 while not kill_switch.is_set(): 231 while not kill_switch.is_set():
224 try: 232 try:
225 yield result_queue.get(timeout=0.1) 233 obj = result_queue.get(timeout=0.1)
234 if isinstance(obj, Cleanup):
235 atexit.register(obj.func_call)
236 else:
237 yield obj
226 except Queue.Empty: 238 except Queue.Empty:
227 break 239 break
228 240
229 if not any(p.is_alive() for p in procs): 241 if not any(p.is_alive() for p in procs):
230 break 242 break
231 243
232 # Get everything still in the queue. Still need timeout, but since nothing 244 # Get everything still in the queue. Still need timeout, but since nothing
233 # is going to be adding stuff to the queue, use a very short timeout. 245 # is going to be adding stuff to the queue, use a very short timeout.
234 while not kill_switch.is_set(): 246 while not kill_switch.is_set():
235 try: 247 try:
236 yield result_queue.get(timeout=0.00001) 248 yield result_queue.get(timeout=0.00001)
237 except Queue.Empty: 249 except Queue.Empty:
238 break 250 break
239 251
240 if kill_switch.is_set(): 252 if kill_switch.is_set():
241 raise ResultStageAbort() 253 raise ResultStageAbort()
242 error = opts.handler.result_stage_loop(opts, generate_objects()) 254 error = opts.handler.result_stage_loop(opts, generate_objects())
243 except ResultStageAbort: 255 except ResultStageAbort:
244 pass 256 pass
245 257
246 for p in procs: 258 for p in procs:
247 p.join() 259 p.join()
248 260
249 if not kill_switch.is_set() and not result_queue.empty(): 261 if not kill_switch.is_set() and not result_queue.empty():
250 error = True 262 error = True
251 263
252 return error, kill_switch.is_set() 264 return error, kill_switch.is_set()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698