OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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() |
OLD | NEW |