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

Side by Side Diff: expect_tests/pipeline.py

Issue 709853003: New expect_tests UI (Closed) Base URL: https://chromium.googlesource.com/infra/testing/expect_tests@shebang
Patch Set: Fixed nits 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
« no previous file with comments | « expect_tests/main.py ('k') | expect_tests/test/data/.expect_tests.cfg » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 contextlib 5 import contextlib
6 import ConfigParser
7 import glob
8 import imp 6 import imp
9 import inspect 7 import inspect
10 import logging 8 import logging
11 import multiprocessing 9 import multiprocessing
12 import os 10 import os
13 import Queue 11 import Queue
14 import re
15 import signal 12 import signal
16 import sys 13 import sys
17 import tempfile 14 import tempfile
18 import traceback 15 import traceback
19 16
20 from cStringIO import StringIO 17 from cStringIO import StringIO
21 18
22 from expect_tests.tempdir import TempDir 19 from expect_tests.tempdir import TempDir
23 from expect_tests.type_definitions import ( 20 from expect_tests.type_definitions import (
24 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, 21 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest,
25 Result, ResultStageAbort) 22 Result, ResultStageAbort)
26 23
27 from expect_tests.unittest_helper import _is_unittest, UnittestTestCase 24 from expect_tests.unittest_helper import _is_unittest, UnittestTestCase
28 from expect_tests import util 25 from expect_tests import util
29 26 from expect_tests import listing
30
31 CONFIG_FILE_NAME = '.expect_tests.cfg'
32 27
33 28
34 @contextlib.contextmanager 29 @contextlib.contextmanager
35 def use_chdir(path): 30 def use_chdir(path):
36 orig_cwd = os.getcwd() 31 orig_cwd = os.getcwd()
37 try: 32 try:
38 os.chdir(path) 33 os.chdir(path)
39 yield 34 yield
40 finally: 35 finally:
41 os.chdir(orig_cwd) 36 os.chdir(orig_cwd)
(...skipping 21 matching lines...) Expand all
63 if path is None: 58 if path is None:
64 _, package_path, _ = imp.find_module(package_name) 59 _, package_path, _ = imp.find_module(package_name)
65 else: 60 else:
66 package_path = os.path.join(path, package_name) 61 package_path = os.path.join(path, package_name)
67 if not os.path.exists(package_path): 62 if not os.path.exists(package_path):
68 raise ValueError('File not found: %s' % package_path) 63 raise ValueError('File not found: %s' % package_path)
69 ispkg = os.path.isfile(os.path.join(package_path, '__init__.py')) 64 ispkg = os.path.isfile(os.path.join(package_path, '__init__.py'))
70 return os.path.normpath(package_path) if ispkg else None 65 return os.path.normpath(package_path) if ispkg else None
71 66
72 67
73 def get_config(path):
74 """Get configuration values
75
76 Reads the config file in provided path, and returns content.
77 See Python ConfigParser for general formatting syntax.
78
79 Example:
80 [expect_tests]
81 skip=directory1
82 directory2
83 directory3
84
85 Args:
86 path (str): path to a directory.
87
88 Returns:
89 black_list (set): blacklisted subdirectories.
90 """
91 black_list = set()
92
93 config_file_name = os.path.join(path, CONFIG_FILE_NAME)
94 parser = ConfigParser.ConfigParser()
95 parser.read([config_file_name])
96
97 if not parser.has_section('expect_tests'):
98 return black_list
99
100 if parser.has_option('expect_tests', 'skip'):
101 black_list.update(parser.get('expect_tests', 'skip').splitlines())
102 return black_list
103
104
105 def is_test_file(filename): 68 def is_test_file(filename):
106 """Returns True if filename is supposed to contain tests. 69 """Returns True if filename is supposed to contain tests.
107 70
108 Args: 71 Args:
109 filename (str): path to a python file. 72 filename (str): path to a python file.
110 73
111 Returns: 74 Returns:
112 is_test (boolean): True if filename points to a test file. 75 is_test (boolean): True if filename points to a test file.
113 """ 76 """
114 return filename.endswith('_test.py') 77 return filename.endswith('_test.py')
115 78
116 79
117 def walk_package(package_name, path): 80 def walk_package(package_name, path, subpath=None):
118 """Return all test files inside a single package. 81 """Return all test files inside a single package.
119 82
120 In all cases, this function returns the full package name of files ending 83 In all cases, this function returns the full package name of files ending
121 in '_test.py' found either under the package called <package_name>. 84 in '_test.py' found either under the package called <package_name>.
122 Example: 'test_package.foo_test' for 'test_package/foo_test.py'. 85 Example: 'test_package.foo_test' for 'test_package/foo_test.py'.
123 86
124 Provided that <path> is in sys.path, calling __import__ with one of the 87 Provided that <path> is in sys.path, calling __import__ with one of the
125 strings returned by this function works. 88 strings returned by this function works.
126 89
127 If a config file is present somewhere in the search hierarchy, 90 If a config file is present somewhere in the search hierarchy,
128 it is interpreted as a list of subdirectories to ignore. This is the way to 91 it is interpreted as a list of subdirectories to ignore. This is the way to
129 make this function ignore some subpackages. 92 make this function ignore some subpackages.
130 93
131 This function has several behaviors depending on the arguments: 94 This function has several behaviors depending on the arguments:
132 - if path is None: search for package_name under any path in sys.path. 95 - if path is None: search for package_name under any path in sys.path.
133 - if path is not None: search for a package called package_name directly 96 - if path is not None: search for a package called package_name directly
134 under <path> (sys.path is not used). 97 under <path> (sys.path is not used).
135 98
136 It is not necessary to change sys.path for the present function to work (it 99 It is not necessary to change sys.path for the present function to work (it
137 does not actually import anything). 100 does not actually import anything).
138 101
139 Args: 102 Args:
140 package_name (str): name of the package, as expected by import. 103 package_name (str): name of the package, as expected by import.
141 path (str): path containing the above module (optional) 104 path (str): path containing the above module (optional)
105 subpath (str, optional): path inside the package, pointing to a subpackage.
106 This is used to restrict the listing to part of the package.
142 107
143 Returns: 108 Returns:
144 test_modules (list of str): name of modules containing tests. Each element is 109 test_modules (list of str): name of modules containing tests. Each element is
145 a period-separated string ending with '_test', 110 a period-separated string ending with '_test',
146 e.g. shiny_package.subpackage.feature_test 111 e.g. shiny_package.subpackage.feature_test
147 112
148 Example: 113 Example:
149 modules = walk_package('shiny_package', 'some/directory') 114 modules = walk_package('shiny_package', 'some/directory')
150 sys.path.insert(0, 'some/directory') 115 sys.path.insert(0, 'some/directory')
151 __import__(modules[0]) 116 __import__(modules[0])
152 117
153 the first line assumes that the directory 'some/directory' is in the 118 the first line assumes that the directory 'some/directory' is in the
154 current working directory. 119 current working directory.
155 """ 120 """
156 assert package_name, 'walk_package needs a package_name.' 121 assert package_name, 'walk_package needs a package_name.'
157 122
158 test_modules = [] 123 test_modules = []
159 package_path = get_package_path(package_name, path) 124 package_path = get_package_path(package_name, path)
160 assert package_path, 'no package found.' 125 assert package_path, 'no package found.'
161 126
162 base_path = os.path.split(package_path.rstrip(os.path.sep))[0] 127 base_path = os.path.split(package_path.rstrip(os.path.sep))[0]
163 assert package_path.startswith(base_path) 128 assert package_path.startswith(base_path)
164 129
165 explored = set() 130 explored = set()
166 131
167 for dirpath, dirnames, filenames in os.walk(package_path, followlinks=True): 132 if subpath:
133 start_path = os.path.join(package_path, subpath)
134 if not os.path.exists(start_path):
135 raise ValueError('Provided subpath does not exist: %s' % start_path)
136 else:
137 start_path = package_path
138
139 for dirpath, dirnames, filenames in os.walk(start_path, followlinks=True):
168 # Keep only submodules not blacklisted, break symlink cycles 140 # Keep only submodules not blacklisted, break symlink cycles
169 blacklist = get_config(dirpath) 141 blacklist = listing.get_config(dirpath)
170 dirnames[:] = [d for d in dirnames 142 dirnames[:] = [d for d in dirnames
171 if d not in blacklist and 143 if d not in blacklist and
172 os.path.isfile(os.path.join(dirpath, d, '__init__.py')) and 144 os.path.isfile(os.path.join(dirpath, d, '__init__.py')) and
173 os.path.realpath(os.path.join(dirpath, d)) not in explored] 145 os.path.realpath(os.path.join(dirpath, d)) not in explored]
174 realpaths = [os.path.realpath(os.path.join(dirpath, d)) for d in dirnames] 146 realpaths = [os.path.realpath(os.path.join(dirpath, d)) for d in dirnames]
175 explored.update(realpaths) 147 explored.update(realpaths)
176 148
177 assert dirpath.startswith(package_path) 149 assert dirpath.startswith(start_path)
178 base_module_name = os.path.relpath(dirpath, base_path).split(os.path.sep) 150 base_module_name = os.path.relpath(dirpath, base_path).split(os.path.sep)
179 test_modules.extend(['.'.join(base_module_name 151 test_modules.extend(['.'.join(base_module_name
180 + [inspect.getmodulename(filename)]) 152 + [inspect.getmodulename(filename)])
181 for filename in filenames 153 for filename in filenames
182 if is_test_file(filename)]) 154 if is_test_file(filename)])
183 155
184 return test_modules 156 return test_modules
185 157
186 158
187 def load_module(modname): 159 def load_module(modname):
188 """Import and return the specified module. 160 """Import and return the specified module.
189 161
190 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module 162 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module
191 because those just don't work. The tradeoff is that we have to walk down the 163 because those just don't work. The tradeoff is that we have to walk down the
192 package hierarchy to find the leaf module (since __import__ returns the 164 package hierarchy to find the leaf module (since __import__ returns the
193 topmost parent package), but at least this behaves deterministically. 165 topmost parent package), but at least this behaves deterministically.
194 """ 166 """
195 mod = __import__(modname) 167 mod = __import__(modname)
196 168
197 for part in modname.split('.')[1:]: 169 for part in modname.split('.')[1:]:
198 mod = getattr(mod, part) 170 mod = getattr(mod, part)
199 return mod 171 return mod
200 172
201 173
202 def get_test_gens_directory(path, cwd): 174 def get_test_gens_package(testing_context, subpath=None):
203 """Given a path, return list of MultiTest or Test instances. 175 """Given a testing context, return list of generators of *Test instances.
204 176
205 See UnittestTestCase for possible return values. 177 See UnittestTestCase for possible return values.
206 178
207 This function loads modules, thus no two conflicting packages (like appengine)
208 can be loaded at the same time: use separate processes for that.
209 """
210 assert isinstance(path, basestring), 'path must be a string'
211 assert os.path.isdir(path), 'path is not a directory: %s' % path
212 sys.path.insert(0, os.path.abspath(path))
213
214 test_gens = []
215 black_list = get_config(path)
216
217 for filename in filter(lambda x: x not in black_list, os.listdir(path)):
218 abs_filename = os.path.join(path, filename)
219 if (os.path.isdir(abs_filename)
220 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))):
221 test_gens += get_test_gens_package(abs_filename, cwd,
222 update_syspath=False)
223 return test_gens
224
225
226 def get_test_gens_package(package, cwd, update_syspath=True):
227 """Given a path, return list of MultiTest or Test instances.
228
229 See UnittestTestCase for possible return values.
230
231 This function loads modules, thus no two conflicting packages (like appengine) 179 This function loads modules, thus no two conflicting packages (like appengine)
232 should be loaded at the same time: use separate processes for that. 180 should be loaded at the same time: use separate processes for that.
233 181
234 Args: 182 Args:
235 package (str): path to a Python package. 183 testing_context (PackageTestingContext): what to test.
236 update_syspath (boolean): if True, the parent directory of 'package' is 184 subpath (str): relative path in the tested package to restrict the search
237 prepended to sys.path. 185 to. Relative to
186 os.path.join(testing_context.cwd, testing_context.package_name)
187
188 Returns:
189 gens_list (list of generator of tests): tests are instances of Test
190 or MultiTest.
238 """ 191 """
239 assert isinstance(package, basestring), "package name should be a string." 192 test_gens = []
240 assert os.path.isfile(os.path.join(package, '__init__.py')), \
241 "'package' is not pointing to a package. It must be a " + \
242 "path to a directory containing a __init__.py file."
243 193
244 test_gens = [] 194 # TODO(pgervais) add filtering on test names (use testing_context.filters)
245 path = os.path.abspath(os.path.dirname(package)) 195 for modname in walk_package(testing_context.package_name,
246 if update_syspath: 196 testing_context.cwd, subpath=subpath):
247 sys.path.insert(0, path) 197 with use_chdir(testing_context.cwd):
248
249 package_name = os.path.split(package.rstrip(os.path.sep))[-1]
250
251 for modname in walk_package(package_name, path):
252 with use_chdir(cwd):
253 mod = load_module(modname) 198 mod = load_module(modname)
254 for obj in mod.__dict__.values(): 199 for obj in mod.__dict__.values():
255 if util.is_test_generator(obj): 200 if util.is_test_generator(obj):
256 test_gens.append(obj) 201 test_gens.append(obj)
257 elif _is_unittest(obj): 202 elif _is_unittest(obj):
258 test_gens.append(UnittestTestCase(obj)) 203 test_gens.append(UnittestTestCase(obj))
259 return test_gens 204 return test_gens
260 205
261 206
262 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, 207 def gen_loop_process(testing_contexts, test_queue, result_queue, opts,
263 cover_ctx, temp_dir): 208 kill_switch, cover_ctx, temp_dir):
264 """Generate `Test`s from |gens|, and feed them into |test_queue|. 209 """Generate `Test`s from |gens|, and feed them into |test_queue|.
265 210
266 Non-Test instances will be translated into `UnknownError` objects. 211 Non-Test instances will be translated into `UnknownError` objects.
267 212
268 Args: 213 Args:
269 gens: list of generators yielding Test() instances. 214 testing_contexts (list of PackageTestingContext): describe tests to
215 process.
270 test_queue (multiprocessing.Queue): 216 test_queue (multiprocessing.Queue):
271 result_queue (multiprocessing.Queue): 217 result_queue (multiprocessing.Queue):
272 opts (argparse.Namespace): 218 opts (argparse.Namespace):
273 kill_switch (multiprocessing.Event): 219 kill_switch (multiprocessing.Event):
274 cover_ctx (cover.CoverageContext().create_subprocess_context) 220 cover_ctx (cover.CoverageContext().create_subprocess_context)
275 """ 221 """
222
223 SENTINEL = object()
276 tempfile.tempdir = temp_dir 224 tempfile.tempdir = temp_dir
277 225
278 # Implicitly append '*' to globs that don't specify it.
279 globs = ['%s%s' % (g, '*' if '*' not in g else '') for g in opts.test_glob]
280
281 matcher = re.compile(
282 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g)
283 for g in globs if g[0] != '-'))
284 if matcher.pattern == '^$':
285 matcher = re.compile('^.*$')
286
287 neg_matcher = re.compile(
288 '^%s$' % '|'.join('(?:%s)' % glob.fnmatch.translate(g[1:])
289 for g in globs if g[0] == '-'))
290
291 SENTINEL = object()
292
293 def generate_tests(): 226 def generate_tests():
294 paths_seen = set()
295 seen_tests = False 227 seen_tests = False
296 try: 228 try:
297 for gen in gens: 229 for testing_context in testing_contexts:
298 gen_cover_ctx = cover_ctx(include=util.get_cover_list(gen)) 230 for subpath, matcher in testing_context.itermatchers():
231 paths_seen = set()
299 232
300 with gen_cover_ctx: 233 with cover_ctx:
301 gen_inst = gen() 234 gens = get_test_gens_package(testing_context, subpath=subpath)
302 235
303 while not kill_switch.is_set(): 236 for gen in gens:
304 with gen_cover_ctx: 237 gen_cover_ctx = cover_ctx(include=util.get_cover_list(gen))
305 root_test = next(gen_inst, SENTINEL)
306 238
307 if root_test is SENTINEL: 239 with gen_cover_ctx:
308 break 240 gen_inst = gen()
309 241
310 if kill_switch.is_set(): 242 while not kill_switch.is_set():
311 break 243 with gen_cover_ctx:
244 root_test = next(gen_inst, SENTINEL)
312 245
313 ok_tests = [] 246 if root_test is SENTINEL:
247 break
314 248
315 if isinstance(root_test, MultiTest): 249 if kill_switch.is_set():
316 subtests = root_test.tests 250 break
317 else:
318 subtests = [root_test]
319 251
320 for subtest in subtests: 252 ok_tests = []
321 if not isinstance(subtest, Test):
322 result_queue.put_nowait(
323 UnknownError(
324 'Got non-[Multi]Test isinstance from generator: %r.'
325 % subtest))
326 continue
327 253
328 test_path = subtest.expect_path() 254 if isinstance(root_test, MultiTest):
329 if test_path is not None and test_path in paths_seen: 255 subtests = root_test.tests
330 result_queue.put_nowait( 256 else:
331 TestError(subtest, 'Duplicate expectation path.')) 257 subtests = [root_test]
332 else:
333 if test_path is not None:
334 paths_seen.add(test_path)
335 name = subtest.name
336 if not neg_matcher.match(name) and matcher.match(name):
337 ok_tests.append(subtest)
338 258
339 if ok_tests: 259 for subtest in subtests:
340 seen_tests = True 260 if not isinstance(subtest, Test):
341 yield root_test.restrict(ok_tests) 261 result_queue.put_nowait(
262 UnknownError(
263 'Got non-[Multi]Test isinstance from generator: %r.'
264 % subtest))
265 continue
266
267 test_path = subtest.expect_path()
268 if test_path is not None and test_path in paths_seen:
269 result_queue.put_nowait(
270 TestError(subtest, 'Duplicate expectation path.'))
271 else:
272 if test_path is not None:
273 paths_seen.add(test_path)
274 name = subtest.name
275 # if not neg_matcher.match(name) and matcher.match(name):
276 if matcher.match(name):
277 ok_tests.append(subtest)
278
279 if ok_tests:
280 seen_tests = True
281 yield root_test.restrict(ok_tests)
342 282
343 if not seen_tests: 283 if not seen_tests:
344 result_queue.put_nowait(NoMatchingTestsError()) 284 result_queue.put_nowait(NoMatchingTestsError())
285
345 except KeyboardInterrupt: 286 except KeyboardInterrupt:
346 pass 287 pass
347 288
348 next_stage = (result_queue if opts.handler.SKIP_RUNLOOP else test_queue) 289 next_stage = (result_queue if opts.handler.SKIP_RUNLOOP else test_queue)
349 opts.handler.gen_stage_loop(opts, generate_tests(), next_stage.put_nowait, 290 opts.handler.gen_stage_loop(opts, generate_tests(), next_stage.put_nowait,
350 result_queue.put_nowait) 291 result_queue.put_nowait)
351 292
352 293
353 def run_loop_process(test_queue, result_queue, opts, 294 def run_loop_process(test_queue, result_queue, opts,
354 kill_switch, test_gen_finished, 295 kill_switch, test_gen_finished,
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
432 logstream.getvalue().splitlines())) 373 logstream.getvalue().splitlines()))
433 except KeyboardInterrupt: 374 except KeyboardInterrupt:
434 pass 375 pass
435 376
436 opts.handler.run_stage_loop( 377 opts.handler.run_stage_loop(
437 opts, 378 opts,
438 generate_tests_results(opts.handler.run_stage_loop_ctx), 379 generate_tests_results(opts.handler.run_stage_loop_ctx),
439 result_queue.put_nowait) 380 result_queue.put_nowait)
440 381
441 382
442 def result_loop_single_path(cover_ctx, kill_switch, result_queue, opts, 383 def result_loop_single_context(cover_ctx, kill_switch, result_queue, opts,
443 path, path_is_package): 384 processing_context):
444 """Run the specified operation on a single path. 385 """Run the specified operation on a single path.
445 386
446 The path provided by the `path` argument is considered to be either a Python 387 The path provided by the `path` argument is considered to be either a Python
447 package or a directory containing Python packages depending on the value of 388 package or a directory containing Python packages depending on the value of
448 the `path_is_package` flag. 389 the `path_is_package` flag.
449 390
450 The current working directory is changed (`os.chdir`) to either `path` or the 391 The current working directory is changed (`os.chdir`) to either `path` or the
451 parent of `path` whether `path_is_package` is False or True respectively. 392 parent of `path` whether `path_is_package` is False or True respectively.
452 393
453 This function is meant to be run as a dedicated process. Calling it twice 394 This function is meant to be run as a dedicated process. Calling it twice
454 in the same process is not supported. 395 in the same process is not supported.
455 396
456 Args: 397 Args:
457 cover_ctx: 398 cover_ctx:
458 kill_switch (multiprocessing.Event): 399 kill_switch (multiprocessing.Event):
459 result_queue (multiprocessing.Queue): 400 result_queue (multiprocessing.Queue):
460 opts: output of argparse.ArgumentParser.parse_args (see main.py) 401 opts: output of argparse.ArgumentParser.parse_args (see main.py)
461 path (str): path a a Python package or a directory containing Python 402 processing_context (ProcessingContext): the task to perform.
462 packages. 403 """
463 path_is_package (boolean): tells whether 'path' is a package or not. 404 sys.path.insert(0, processing_context.cwd)
464 """
465 assert isinstance(path, basestring), 'path must be a string'
466
467 if path_is_package:
468 work_path = os.path.dirname(os.path.abspath(path))
469 else:
470 work_path = path
471
472 with cover_ctx:
473 if path_is_package:
474 test_gens = get_test_gens_package(path, work_path)
475 else:
476 test_gens = get_test_gens_directory(path, work_path)
477 405
478 # This flag is set when test generation has finished. 406 # This flag is set when test generation has finished.
479 test_gen_finished = multiprocessing.Event() 407 test_gen_finished = multiprocessing.Event()
480 test_queue = multiprocessing.Queue() 408 test_queue = multiprocessing.Queue()
481 409
482 with TempDir() as temp_dir: 410 with TempDir() as temp_dir:
483 test_gen_args = ( 411 test_gen_args = (
484 test_gens, test_queue, result_queue, opts, 412 processing_context.testing_contexts, test_queue, result_queue, opts,
485 kill_switch, cover_ctx, temp_dir 413 kill_switch, cover_ctx, temp_dir
486 ) 414 )
487 415
488 procs = [] 416 procs = []
489 if opts.handler.SKIP_RUNLOOP: 417 if opts.handler.SKIP_RUNLOOP:
490 gen_loop_process(*test_gen_args) 418 gen_loop_process(*test_gen_args)
491 else: 419 else:
492 procs = [ 420 procs = [
493 multiprocessing.Process( 421 multiprocessing.Process(
494 target=run_loop_process, 422 target=run_loop_process,
495 args=(test_queue, result_queue, opts, 423 args=(test_queue, result_queue, opts,
496 kill_switch, test_gen_finished, cover_ctx, temp_dir, 424 kill_switch, test_gen_finished, cover_ctx, temp_dir,
497 work_path), 425 processing_context.cwd),
498 name='run_loop_process %d' % job_num) 426 name='run_loop_process %d' % job_num)
499 for job_num in xrange(opts.jobs) 427 for job_num in xrange(opts.jobs)
500 ] 428 ]
501 429
502 for p in procs: 430 for p in procs:
503 p.daemon = True 431 p.daemon = True
504 p.start() 432 p.start()
505 433
506 gen_loop_process(*test_gen_args) 434 gen_loop_process(*test_gen_args)
507 # Signal all run_loop_process that they can exit. 435 # Signal all run_loop_process that they can exit.
508 test_gen_finished.set() 436 test_gen_finished.set()
509 437
510 for p in procs: 438 for p in procs:
511 p.join() 439 p.join()
512 440
513 441
514 def result_loop(cover_ctx, opts): 442 def result_loop(cover_ctx, opts):
515 """Run the specified operation in all paths in parallel. 443 """Run the specified operation in all paths in parallel.
516 444
517 Directories and packages to process are defined in opts.directory and 445 Directories and packages to process are defined in opts.directory and
518 opts.package. 446 opts.package.
519 447
520 The operation to perform (list/test/debug/train) is defined by opts.handler. 448 The operation to perform (list/test/debug/train) is defined by opts.handler.
521 """ 449 """
522 450
451 processing_contexts = listing.get_runtime_contexts(opts.test_glob)
452
523 def ensure_echo_on(): 453 def ensure_echo_on():
524 """Restore echo on in the terminal. 454 """Restore echo on in the terminal.
525 455
526 This is useful when killing a pdb session with C-c. 456 This is useful when killing a pdb session with C-c.
527 """ 457 """
528 try: 458 try:
529 import termios 459 import termios
530 except ImportError: 460 except ImportError:
531 termios = None 461 termios = None
532 if termios: 462 if termios:
(...skipping 16 matching lines...) Expand all
549 signal.signal(signal.SIGINT, signal.SIG_DFL) 479 signal.signal(signal.SIGINT, signal.SIG_DFL)
550 signal.signal(signal.SIGTERM, signal.SIG_DFL) 480 signal.signal(signal.SIGTERM, signal.SIG_DFL)
551 481
552 signal.signal(signal.SIGINT, handle_killswitch) 482 signal.signal(signal.SIGINT, handle_killswitch)
553 signal.signal(signal.SIGTERM, handle_killswitch) 483 signal.signal(signal.SIGTERM, handle_killswitch)
554 484
555 result_queue = multiprocessing.Queue() 485 result_queue = multiprocessing.Queue()
556 486
557 procs = [ 487 procs = [
558 multiprocessing.Process( 488 multiprocessing.Process(
559 target=result_loop_single_path, 489 target=result_loop_single_context,
560 args=(cover_ctx, kill_switch, result_queue, opts, os.path.abspath(p), 490 args=(cover_ctx, kill_switch, result_queue, opts, c)
561 False)
562 ) 491 )
563 for p in opts.directory 492 for c in processing_contexts
564 ] + [
565 multiprocessing.Process(
566 target=result_loop_single_path,
567 args=(cover_ctx, kill_switch, result_queue, opts, os.path.abspath(p),
568 True)
569 )
570 for p in opts.package
571 ] 493 ]
572 494
573 error = False 495 error = False
574 496
575 try: 497 try:
576 def generate_objects(procs): 498 def generate_objects(procs):
577 for p in procs: 499 for p in procs:
578 p.start() 500 p.start()
579 while not kill_switch.is_set(): 501 while not kill_switch.is_set():
580 try: 502 try:
(...skipping 22 matching lines...) Expand all
603 525
604 if procs: 526 if procs:
605 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) 527 error = opts.handler.result_stage_loop(opts, generate_objects(procs))
606 except ResultStageAbort: 528 except ResultStageAbort:
607 pass 529 pass
608 530
609 if not kill_switch.is_set() and not result_queue.empty(): 531 if not kill_switch.is_set() and not result_queue.empty():
610 error = True 532 error = True
611 533
612 return error, kill_switch.is_set() 534 return error, kill_switch.is_set()
OLDNEW
« no previous file with comments | « expect_tests/main.py ('k') | expect_tests/test/data/.expect_tests.cfg » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698