Mercurial > hg
comparison tests/run-tests.py @ 5384:e3a0c092b4e2
Allow tests to run in parallel.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Fri, 05 Oct 2007 12:17:01 -0700 |
parents | 7cdc896fdcd5 |
children | 557e4a916e12 |
comparison
equal
deleted
inserted
replaced
5383:7cdc896fdcd5 | 5384:e3a0c092b4e2 |
---|---|
25 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] | 25 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] |
26 | 26 |
27 parser = optparse.OptionParser("%prog [options] [tests]") | 27 parser = optparse.OptionParser("%prog [options] [tests]") |
28 parser.add_option("-C", "--annotate", action="store_true", | 28 parser.add_option("-C", "--annotate", action="store_true", |
29 help="output files annotated with coverage") | 29 help="output files annotated with coverage") |
30 parser.add_option("--child", type="int", | |
31 help="run as child process, summary to given fd") | |
30 parser.add_option("-c", "--cover", action="store_true", | 32 parser.add_option("-c", "--cover", action="store_true", |
31 help="print a test coverage report") | 33 help="print a test coverage report") |
32 parser.add_option("-f", "--first", action="store_true", | 34 parser.add_option("-f", "--first", action="store_true", |
33 help="exit on the first test failure") | 35 help="exit on the first test failure") |
34 parser.add_option("-i", "--interactive", action="store_true", | 36 parser.add_option("-i", "--interactive", action="store_true", |
35 help="prompt to accept changed output") | 37 help="prompt to accept changed output") |
38 parser.add_option("-j", "--jobs", type="int", | |
39 help="number of jobs to run in parallel") | |
36 parser.add_option("-R", "--restart", action="store_true", | 40 parser.add_option("-R", "--restart", action="store_true", |
37 help="restart at last error") | 41 help="restart at last error") |
42 parser.add_option("-p", "--port", type="int", | |
43 help="port on which servers should listen") | |
38 parser.add_option("-r", "--retest", action="store_true", | 44 parser.add_option("-r", "--retest", action="store_true", |
39 help="retest failed tests") | 45 help="retest failed tests") |
40 parser.add_option("-s", "--cover_stdlib", action="store_true", | 46 parser.add_option("-s", "--cover_stdlib", action="store_true", |
41 help="print a test coverage report inc. standard libraries") | 47 help="print a test coverage report inc. standard libraries") |
42 parser.add_option("-t", "--timeout", type="int", | 48 parser.add_option("-t", "--timeout", type="int", |
43 help="kill errant tests after TIMEOUT seconds") | 49 help="kill errant tests after TIMEOUT seconds") |
44 parser.add_option("-v", "--verbose", action="store_true", | 50 parser.add_option("-v", "--verbose", action="store_true", |
45 help="output verbose messages") | 51 help="output verbose messages") |
46 | 52 parser.add_option("--with-hg", type="string", |
47 parser.set_defaults(timeout=180) | 53 help="test existing install at given location") |
54 | |
55 parser.set_defaults(jobs=1, port=20059, timeout=180) | |
48 (options, args) = parser.parse_args() | 56 (options, args) = parser.parse_args() |
49 verbose = options.verbose | 57 verbose = options.verbose |
50 coverage = options.cover or options.cover_stdlib or options.annotate | 58 coverage = options.cover or options.cover_stdlib or options.annotate |
51 python = sys.executable | 59 python = sys.executable |
60 | |
61 if options.jobs < 1: | |
62 print >> sys.stderr, 'ERROR: -j/--jobs must be positive' | |
63 sys.exit(1) | |
64 if options.interactive and options.jobs > 1: | |
65 print >> sys.stderr, 'ERROR: cannot mix -interactive and --jobs > 1' | |
66 sys.exit(1) | |
52 | 67 |
53 def vlog(*msg): | 68 def vlog(*msg): |
54 if verbose: | 69 if verbose: |
55 for m in msg: | 70 for m in msg: |
56 print m, | 71 print m, |
366 shutil.rmtree(tmpd, True) | 381 shutil.rmtree(tmpd, True) |
367 if skipped: | 382 if skipped: |
368 return None | 383 return None |
369 return ret == 0 | 384 return ret == 0 |
370 | 385 |
371 | 386 if not options.child: |
372 os.umask(022) | 387 os.umask(022) |
373 | 388 |
374 check_required_tools() | 389 check_required_tools() |
375 | 390 |
376 # Reset some environment variables to well-known values so that | 391 # Reset some environment variables to well-known values so that |
377 # the tests produce repeatable output. | 392 # the tests produce repeatable output. |
378 os.environ['LANG'] = os.environ['LC_ALL'] = 'C' | 393 os.environ['LANG'] = os.environ['LC_ALL'] = 'C' |
379 os.environ['TZ'] = 'GMT' | 394 os.environ['TZ'] = 'GMT' |
380 | 395 |
381 TESTDIR = os.environ["TESTDIR"] = os.getcwd() | 396 TESTDIR = os.environ["TESTDIR"] = os.getcwd() |
382 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") | 397 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") |
383 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') | 398 DAEMON_PIDS = None |
384 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') | 399 HGRCPATH = None |
385 | 400 |
386 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' | 401 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' |
387 os.environ["HGMERGE"] = ('python "%s" -L my -L other' | 402 os.environ["HGMERGE"] = ('python "%s" -L my -L other' |
388 % os.path.join(TESTDIR, os.path.pardir, 'contrib', | 403 % os.path.join(TESTDIR, os.path.pardir, |
389 'simplemerge')) | 404 'contrib', 'simplemerge')) |
390 os.environ["HGUSER"] = "test" | 405 os.environ["HGUSER"] = "test" |
391 os.environ["HGENCODING"] = "ascii" | 406 os.environ["HGENCODING"] = "ascii" |
392 os.environ["HGENCODINGMODE"] = "strict" | 407 os.environ["HGENCODINGMODE"] = "strict" |
393 | 408 os.environ["HGPORT"] = str(options.port) |
394 vlog("# Using TESTDIR", TESTDIR) | 409 os.environ["HGPORT1"] = str(options.port + 1) |
395 vlog("# Using HGTMP", HGTMP) | 410 os.environ["HGPORT2"] = str(options.port + 2) |
396 | 411 |
397 INST = os.path.join(HGTMP, "install") | 412 if options.with_hg: |
413 INST = options.with_hg | |
414 else: | |
415 INST = os.path.join(HGTMP, "install") | |
398 BINDIR = os.path.join(INST, "bin") | 416 BINDIR = os.path.join(INST, "bin") |
399 PYTHONDIR = os.path.join(INST, "lib", "python") | 417 PYTHONDIR = os.path.join(INST, "lib", "python") |
400 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") | 418 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") |
401 | 419 |
402 try: | 420 def run_children(tests): |
421 if not options.with_hg: | |
422 install_hg() | |
423 | |
424 optcopy = dict(options.__dict__) | |
425 optcopy['jobs'] = 1 | |
426 optcopy['with_hg'] = INST | |
427 opts = [] | |
428 for opt, value in optcopy.iteritems(): | |
429 name = '--' + opt.replace('_', '-') | |
430 if value is True: | |
431 opts.append(name) | |
432 elif value is not None: | |
433 opts.append(name + '=' + str(value)) | |
434 | |
435 tests.reverse() | |
436 jobs = [[] for j in xrange(options.jobs)] | |
437 while tests: | |
438 for j in xrange(options.jobs): | |
439 if not tests: break | |
440 jobs[j].append(tests.pop()) | |
441 fps = {} | |
442 for j in xrange(len(jobs)): | |
443 job = jobs[j] | |
444 if not job: | |
445 continue | |
446 rfd, wfd = os.pipe() | |
447 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)] | |
448 cmdline = [python, sys.argv[0]] + opts + childopts + job | |
449 vlog(' '.join(cmdline)) | |
450 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r') | |
451 os.close(wfd) | |
452 failures = 0 | |
453 tested, skipped, failed = 0, 0, 0 | |
454 while fps: | |
455 pid, status = os.wait() | |
456 fp = fps.pop(pid) | |
457 test, skip, fail = map(int, fp.read().splitlines()) | |
458 tested += test | |
459 skipped += skip | |
460 failed += fail | |
461 vlog('pid %d exited, status %d' % (pid, status)) | |
462 failures |= status | |
463 print "\n# Ran %d tests, %d skipped, %d failed." % ( | |
464 tested, skipped, failed) | |
465 sys.exit(failures != 0) | |
466 | |
467 def run_tests(tests): | |
468 global DAEMON_PIDS, HGRCPATH | |
469 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') | |
470 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') | |
471 | |
403 try: | 472 try: |
404 install_hg() | 473 if not options.with_hg: |
474 install_hg() | |
405 | 475 |
406 if options.timeout > 0: | 476 if options.timeout > 0: |
407 try: | 477 try: |
408 signal.signal(signal.SIGALRM, alarmed) | 478 signal.signal(signal.SIGALRM, alarmed) |
409 vlog('# Running tests with %d-second timeout' % | 479 vlog('# Running tests with %d-second timeout' % |
413 options.timeout = 0 | 483 options.timeout = 0 |
414 | 484 |
415 tested = 0 | 485 tested = 0 |
416 failed = 0 | 486 failed = 0 |
417 skipped = 0 | 487 skipped = 0 |
418 | |
419 if len(args) == 0: | |
420 args = os.listdir(".") | |
421 args.sort() | |
422 | |
423 | |
424 tests = [] | |
425 for test in args: | |
426 if (test.startswith("test-") and '~' not in test and | |
427 ('.' not in test or test.endswith('.py') or | |
428 test.endswith('.bat'))): | |
429 tests.append(test) | |
430 | 488 |
431 if options.restart: | 489 if options.restart: |
432 orig = list(tests) | 490 orig = list(tests) |
433 while tests: | 491 while tests: |
434 if os.path.exists(tests[0] + ".err"): | 492 if os.path.exists(tests[0] + ".err"): |
456 failed += 1 | 514 failed += 1 |
457 if options.first: | 515 if options.first: |
458 break | 516 break |
459 tested += 1 | 517 tested += 1 |
460 | 518 |
461 print "\n# Ran %d tests, %d skipped, %d failed." % (tested, skipped, | 519 if options.child: |
462 failed) | 520 fp = os.fdopen(options.child, 'w') |
521 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed)) | |
522 fp.close() | |
523 else: | |
524 print "\n# Ran %d tests, %d skipped, %d failed." % ( | |
525 tested, skipped, failed) | |
526 | |
463 if coverage: | 527 if coverage: |
464 output_coverage() | 528 output_coverage() |
465 except KeyboardInterrupt: | 529 except KeyboardInterrupt: |
466 failed = True | 530 failed = True |
467 print "\ninterrupted!" | 531 print "\ninterrupted!" |
532 | |
533 if failed: | |
534 sys.exit(1) | |
535 | |
536 if len(args) == 0: | |
537 args = os.listdir(".") | |
538 args.sort() | |
539 | |
540 tests = [] | |
541 for test in args: | |
542 if (test.startswith("test-") and '~' not in test and | |
543 ('.' not in test or test.endswith('.py') or | |
544 test.endswith('.bat'))): | |
545 tests.append(test) | |
546 | |
547 vlog("# Using TESTDIR", TESTDIR) | |
548 vlog("# Using HGTMP", HGTMP) | |
549 | |
550 try: | |
551 if len(tests) > 1 and options.jobs > 1: | |
552 run_children(tests) | |
553 else: | |
554 run_tests(tests) | |
468 finally: | 555 finally: |
469 cleanup_exit() | 556 cleanup_exit() |
470 | |
471 if failed: | |
472 sys.exit(1) |