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