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 Queue | 5 import Queue |
6 import glob | 6 import glob |
7 import logging | 7 import logging |
8 import multiprocessing | 8 import multiprocessing |
9 import os | 9 import os |
10 import re | 10 import re |
11 import shutil | 11 import shutil |
12 import signal | 12 import signal |
13 import sys | 13 import sys |
14 import tempfile | 14 import tempfile |
15 import traceback | 15 import traceback |
16 | 16 |
17 from cStringIO import StringIO | 17 from cStringIO import StringIO |
18 | 18 |
| 19 from expect_tests.tempdir import TempDir |
19 from expect_tests.type_definitions import ( | 20 from expect_tests.type_definitions import ( |
20 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, | 21 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, |
21 Result, ResultStageAbort) | 22 Result, ResultStageAbort) |
22 | 23 |
23 from expect_tests import util | 24 from expect_tests import util |
24 | 25 |
25 | 26 |
26 OLD_TEMP_DIR = None | |
27 TEMP_PID_FILE = '.expect_tests_pidfile' | |
28 | |
29 | |
30 class ResetableStringIO(object): | 27 class ResetableStringIO(object): |
31 def __init__(self): | 28 def __init__(self): |
32 self._stream = StringIO() | 29 self._stream = StringIO() |
33 | 30 |
34 def reset(self): | 31 def reset(self): |
35 self._stream = StringIO() | 32 self._stream = StringIO() |
36 | 33 |
37 def __getattr__(self, key): | 34 def __getattr__(self, key): |
38 return getattr(self._stream, key) | 35 return getattr(self._stream, key) |
39 | 36 |
40 | 37 |
41 def set_temp_dir(): | |
42 """Provides an automatically-cleaned temporary directory base for tests.""" | |
43 global OLD_TEMP_DIR | |
44 | |
45 if OLD_TEMP_DIR: | |
46 return | |
47 | |
48 suffix = '.expect_tests_temp' | |
49 tempdir = tempfile.mkdtemp(suffix) | |
50 for p in glob.glob(os.path.join(os.path.dirname(tempdir), '*'+suffix)): | |
51 if p == tempdir: | |
52 continue | |
53 | |
54 pfile = os.path.join(p, TEMP_PID_FILE) | |
55 if os.path.exists(pfile): | |
56 with open(pfile, 'rb') as f: | |
57 try: | |
58 os.kill(int(f.read()), 0) | |
59 continue | |
60 except (OSError, TypeError): | |
61 pass | |
62 | |
63 shutil.rmtree(p, ignore_errors=True) | |
64 | |
65 with open(os.path.join(tempdir, TEMP_PID_FILE), 'wb') as f: | |
66 f.write(str(os.getpid())) | |
67 | |
68 OLD_TEMP_DIR = tempfile.tempdir | |
69 tempfile.tempdir = tempdir | |
70 | |
71 | |
72 def clear_temp_dir(): | |
73 global OLD_TEMP_DIR | |
74 if OLD_TEMP_DIR: | |
75 # Try to nuke the pidfile first | |
76 pfile = os.path.join(tempfile.tempdir, TEMP_PID_FILE) | |
77 try: | |
78 os.unlink(pfile) | |
79 except OSError as e: | |
80 print >> sys.stderr, "Error removing %r: %s" % (pfile, e) | |
81 shutil.rmtree(tempfile.tempdir, ignore_errors=True) | |
82 tempfile.temdir = OLD_TEMP_DIR | |
83 OLD_TEMP_DIR = None | |
84 | |
85 | |
86 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, | 38 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, |
87 cover_ctx, temp_dir): | 39 cover_ctx, temp_dir): |
88 """Generate `Test`s from |gens|, and feed them into |test_queue|. | 40 """Generate `Test`s from |gens|, and feed them into |test_queue|. |
89 | 41 |
90 Non-Test instances will be translated into `UnknownError` objects. | 42 Non-Test instances will be translated into `UnknownError` objects. |
91 | 43 |
92 On completion, feed |opts.jobs| None objects into |test_queue|. | 44 On completion, feed |opts.jobs| None objects into |test_queue|. |
93 | 45 |
94 @param gens: list of generators yielding Test() instances. | 46 @param gens: list of generators yielding Test() instances. |
95 @type test_queue: multiprocessing.Queue() | 47 @type test_queue: multiprocessing.Queue() |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
255 kill_switch.set() | 207 kill_switch.set() |
256 # Reset the signal to DFL so that double ctrl-C kills us for sure. | 208 # Reset the signal to DFL so that double ctrl-C kills us for sure. |
257 signal.signal(signal.SIGINT, signal.SIG_DFL) | 209 signal.signal(signal.SIGINT, signal.SIG_DFL) |
258 signal.signal(signal.SIGTERM, signal.SIG_DFL) | 210 signal.signal(signal.SIGTERM, signal.SIG_DFL) |
259 signal.signal(signal.SIGINT, handle_killswitch) | 211 signal.signal(signal.SIGINT, handle_killswitch) |
260 signal.signal(signal.SIGTERM, handle_killswitch) | 212 signal.signal(signal.SIGTERM, handle_killswitch) |
261 | 213 |
262 test_queue = multiprocessing.Queue() | 214 test_queue = multiprocessing.Queue() |
263 result_queue = multiprocessing.Queue() | 215 result_queue = multiprocessing.Queue() |
264 | 216 |
265 set_temp_dir() | 217 with TempDir() as temp_dir: |
| 218 test_gen_args = ( |
| 219 test_gens, test_queue, result_queue, opts, kill_switch, cover_ctx, |
| 220 temp_dir |
| 221 ) |
266 | 222 |
267 test_gen_args = ( | 223 procs = [] |
268 test_gens, test_queue, result_queue, opts, kill_switch, cover_ctx, | 224 if opts.handler.SKIP_RUNLOOP: |
269 tempfile.tempdir | 225 gen_loop_process(*test_gen_args) |
270 ) | 226 else: |
| 227 procs = [multiprocessing.Process( |
| 228 target=gen_loop_process, args=test_gen_args)] |
271 | 229 |
272 procs = [] | 230 procs += [ |
273 if opts.handler.SKIP_RUNLOOP: | 231 multiprocessing.Process( |
274 gen_loop_process(*test_gen_args) | 232 target=run_loop_process, args=( |
275 else: | 233 test_queue, result_queue, opts, kill_switch, cover_ctx, |
276 procs = [multiprocessing.Process( | 234 temp_dir)) |
277 target=gen_loop_process, args=test_gen_args)] | 235 for _ in xrange(opts.jobs) |
| 236 ] |
278 | 237 |
279 procs += [ | 238 for p in procs: |
280 multiprocessing.Process( | 239 p.daemon = True |
281 target=run_loop_process, args=( | 240 p.start() |
282 test_queue, result_queue, opts, kill_switch, cover_ctx, | |
283 tempfile.tempdir)) | |
284 for _ in xrange(opts.jobs) | |
285 ] | |
286 | 241 |
287 for p in procs: | 242 error = False |
288 p.daemon = True | |
289 p.start() | |
290 | 243 |
291 error = False | 244 try: |
| 245 def generate_objects(): |
| 246 while not kill_switch.is_set(): |
| 247 while not kill_switch.is_set(): |
| 248 try: |
| 249 yield result_queue.get(timeout=0.1) |
| 250 except Queue.Empty: |
| 251 break |
292 | 252 |
293 try: | 253 if not any(p.is_alive() for p in procs): |
294 def generate_objects(): | 254 break |
295 while not kill_switch.is_set(): | 255 |
| 256 # Get everything still in the queue. Still need timeout, but since |
| 257 # nothing is going to be adding stuff to the queue, use a very short |
| 258 # timeout. |
296 while not kill_switch.is_set(): | 259 while not kill_switch.is_set(): |
297 try: | 260 try: |
298 yield result_queue.get(timeout=0.1) | 261 yield result_queue.get(timeout=0.00001) |
299 except Queue.Empty: | 262 except Queue.Empty: |
300 break | 263 break |
301 | 264 |
302 if not any(p.is_alive() for p in procs): | 265 if kill_switch.is_set(): |
303 break | 266 raise ResultStageAbort() |
304 | 267 error = opts.handler.result_stage_loop(opts, generate_objects()) |
305 # Get everything still in the queue. Still need timeout, but since nothing | 268 except ResultStageAbort: |
306 # is going to be adding stuff to the queue, use a very short timeout. | 269 pass |
307 while not kill_switch.is_set(): | |
308 try: | |
309 yield result_queue.get(timeout=0.00001) | |
310 except Queue.Empty: | |
311 break | |
312 | |
313 if kill_switch.is_set(): | |
314 raise ResultStageAbort() | |
315 error = opts.handler.result_stage_loop(opts, generate_objects()) | |
316 except ResultStageAbort: | |
317 pass | |
318 finally: | |
319 clear_temp_dir() | |
320 | 270 |
321 for p in procs: | 271 for p in procs: |
322 p.join() | 272 p.join() |
323 | 273 |
324 if not kill_switch.is_set() and not result_queue.empty(): | 274 if not kill_switch.is_set() and not result_queue.empty(): |
325 error = True | 275 error = True |
326 | 276 |
327 return error, kill_switch.is_set() | 277 return error, kill_switch.is_set() |
OLD | NEW |