diff -r f3ab526d8761 -r 6f25a5834e14 tests/run-tests.py --- a/tests/run-tests.py Mon Apr 25 01:42:21 2011 +0100 +++ b/tests/run-tests.py Mon Apr 25 23:09:47 2011 +0100 @@ -53,16 +53,32 @@ import tempfile import time import re +import threading closefds = os.name == 'posix' -def Popen4(cmd, bufsize=-1): - p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, +def Popen4(cmd, timeout): + p = subprocess.Popen(cmd, shell=True, bufsize=-1, close_fds=closefds, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.fromchild = p.stdout p.tochild = p.stdin p.childerr = p.stderr + + if timeout: + p.timeout = False + def t(): + start = time.time() + while time.time() - start < timeout and p.returncode is None: + time.sleep(1) + p.timeout = True + if p.returncode is None: + try: + p.terminate() + except OSError: + pass + threading.Thread(target=t).start() + return p # reserved exit code to skip test (used by hghave) @@ -439,12 +455,6 @@ os.mkdir(adir) covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) -class Timeout(Exception): - pass - -def alarmed(signum, frame): - raise Timeout - def pytest(test, options, replacements): py3kswitch = options.py3k_warnings and ' -3' or '' cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) @@ -602,72 +612,90 @@ if ret is None: ret = 0 else: - proc = Popen4(cmd) + proc = Popen4(cmd, options.timeout) def cleanup(): - os.kill(proc.pid, signal.SIGTERM) + try: + proc.terminate() + except OSError: + pass ret = proc.wait() if ret == 0: ret = signal.SIGTERM << 8 killdaemons() return ret + output = '' + proc.tochild.close() + try: - output = '' - proc.tochild.close() output = proc.fromchild.read() - ret = proc.wait() - if wifexited(ret): - ret = os.WEXITSTATUS(ret) - except Timeout: - vlog('# Process %d timed out - killing it' % proc.pid) - cleanup() - ret = 'timeout' - output += ("\n### Abort: timeout after %d seconds.\n" - % options.timeout) except KeyboardInterrupt: vlog('# Handling keyboard interrupt') cleanup() raise + ret = proc.wait() + if wifexited(ret): + ret = os.WEXITSTATUS(ret) + + if proc.timeout: + ret = 'timeout' + + if ret: + killdaemons() + for s, r in replacements: output = re.sub(s, r, output) return ret, splitnewlines(output) -def runone(options, test, results): +def runone(options, test): '''tristate output: None -> skipped True -> passed False -> failed''' + global results, resultslock, iolock + testpath = os.path.join(TESTDIR, test) + def result(l, e): + resultslock.acquire() + results[l].append(e) + resultslock.release() + def skip(msg): if not options.verbose: - results['s'].append((test, msg)) + result('s', (test, msg)) else: + iolock.acquire() print "\nSkipping %s: %s" % (testpath, msg) + iolock.release() return None def fail(msg, ret): if not options.nodiff: + iolock.acquire() print "\nERROR: %s %s" % (testpath, msg) + iolock.release() if (not ret and options.interactive and os.path.exists(testpath + ".err")): + iolock.acquire() print "Accept this change? [n] ", answer = sys.stdin.readline().strip() + iolock.release() if answer.lower() in "y yes".split(): if test.endswith(".t"): rename(testpath + ".err", testpath) else: rename(testpath + ".err", testpath + ".out") return - results['f'].append((test, msg)) + result('f', (test, msg)) def success(): - results['p'].append(test) + result('p', test) def ignore(msg): - results['i'].append((test, msg)) + result('i', (test, msg)) if (test.startswith("test-") and '~' not in test and ('.' not in test or test.endswith('.py') or @@ -681,7 +709,7 @@ if options.blacklist: filename = options.blacklist.get(test) if filename is not None: - skipped.append((test, "blacklisted (%s)" % filename)) + skip("blacklisted") return None if options.retest and not os.path.exists(test + ".err"): @@ -747,9 +775,6 @@ os.mkdir(testtmp) os.chdir(testtmp) - if options.timeout > 0: - signal.alarm(options.timeout) - ret, out = runner(testpath, options, [ (re.escape(testtmp), '$TESTTMP'), (r':%s\b' % options.port, ':$HGPORT'), @@ -758,9 +783,6 @@ ]) vlog("# Ret was:", ret) - if options.timeout > 0: - signal.alarm(0) - mark = '.' if ret == 0: success() @@ -799,27 +821,32 @@ skipped = False else: skip(missing[-1]) + elif ret == 'timeout': + mark = 't' + fail("timed out", ret) elif out != refout: mark = '!' - if ret == 'timeout': - fail("timed out", ret) - elif ret: - fail("output changed and returned error code %d" % ret, ret) - else: - fail("output changed", ret) - if ret != 'timeout' and not options.nodiff: + if not options.nodiff: + iolock.acquire() if options.view: os.system("%s %s %s" % (options.view, ref, err)) else: showdiff(refout, out, ref, err) + iolock.release() + if ret: + fail("output changed and returned error code %d" % ret, ret) + else: + fail("output changed", ret) ret = 1 elif ret: mark = '!' fail("returned error code %d" % ret, ret) if not options.verbose: + iolock.acquire() sys.stdout.write(mark) sys.stdout.flush() + iolock.release() killdaemons() @@ -935,9 +962,13 @@ outputcoverage(options) sys.exit(failures != 0) +results = dict(p=[], f=[], s=[], i=[]) +resultslock = threading.Lock() +iolock = threading.Lock() + def runqueue(options, tests, results): for test in tests: - ret = runone(options, test, results) + ret = runone(options, test) if options.first and ret is not None and not ret: break @@ -946,22 +977,11 @@ DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') - results = dict(p=[], f=[], s=[], i=[]) - try: if INST: installhg(options) _checkhglib("Testing") - if options.timeout > 0: - try: - signal.signal(signal.SIGALRM, alarmed) - vlog('# Running each test with %d second timeout' % - options.timeout) - except AttributeError: - print 'WARNING: cannot run tests with timeouts' - options.timeout = 0 - if options.restart: orig = list(tests) while tests: