| 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 |