--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -7,23 +7,32 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-import os, sys, shutil, re
+import difflib
+import errno
+import optparse
+import os
+import popen2
+import re
+import shutil
+import signal
+import sys
import tempfile
-import difflib
-import popen2
-from optparse import OptionParser
+import time
required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
-parser = OptionParser("%prog [options] [tests]")
+parser = optparse.OptionParser("%prog [options] [tests]")
parser.add_option("-v", "--verbose", action="store_true",
help="output verbose messages")
+parser.add_option("-t", "--timeout", type="int",
+ help="output verbose messages")
parser.add_option("-c", "--cover", action="store_true",
help="print a test coverage report")
parser.add_option("-s", "--cover_stdlib", action="store_true",
help="print a test coverage report inc. standard libraries")
parser.add_option("-C", "--annotate", action="store_true",
help="output files annotated with coverage")
+parser.set_defaults(timeout=30)
(options, args) = parser.parse_args()
verbose = options.verbose
coverage = options.cover or options.cover_stdlib or options.annotate
@@ -159,6 +168,12 @@ def output_coverage():
vlog("# Running: "+cmd)
os.system(cmd)
+class Timeout(Exception):
+ pass
+
+def alarmed(signum, frame):
+ raise Timeout
+
def run(cmd):
"""Run command in a sub-process, capturing the output (stdout and stderr).
Return the exist code, and output."""
@@ -172,9 +187,17 @@ def run(cmd):
ret = 0
else:
proc = popen2.Popen4(cmd)
- proc.tochild.close()
- output = proc.fromchild.read()
- ret = proc.wait()
+ try:
+ output = ''
+ proc.tochild.close()
+ output = proc.fromchild.read()
+ ret = proc.wait()
+ except Timeout:
+ vlog('# Process %d timed out - killing it' % proc.pid)
+ os.kill(proc.pid, signal.SIGTERM)
+ ret = proc.wait()
+ if ret == 0:
+ ret = signal.SIGTERM << 8
return ret, splitnewlines(output)
def run_one(test):
@@ -204,10 +227,16 @@ def run_one(test):
if os.name == 'nt' and test.endswith(".bat"):
cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
+ if options.timeout > 0:
+ signal.alarm(options.timeout)
+
vlog("# Running", cmd)
ret, out = run(cmd)
vlog("# Ret was:", ret)
+ if options.timeout > 0:
+ signal.alarm(0)
+
diffret = 0
# If reference output file exists, check test output against it
if os.path.exists(ref):
@@ -231,6 +260,30 @@ def run_one(test):
f.write(line)
f.close()
+ # Kill off any leftover daemon processes
+ try:
+ fp = file(DAEMON_PIDS)
+ for line in fp:
+ try:
+ pid = int(line)
+ except ValueError:
+ continue
+ try:
+ os.kill(pid, 0)
+ vlog('# Killing daemon process %d' % pid)
+ os.kill(pid, signal.SIGTERM)
+ time.sleep(0.25)
+ os.kill(pid, 0)
+ vlog('# Daemon process %d is stuck - really killing it' % pid)
+ os.kill(pid, signal.SIGKILL)
+ except OSError, err:
+ if err.errno != errno.ESRCH:
+ raise
+ fp.close()
+ os.unlink(DAEMON_PIDS)
+ except IOError:
+ pass
+
os.chdir(TESTDIR)
shutil.rmtree(tmpd, True)
return ret == 0
@@ -252,6 +305,8 @@ os.environ["HGRCPATH"] = ""
TESTDIR = os.environ["TESTDIR"] = os.getcwd()
HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
+DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
+
vlog("# Using TESTDIR", TESTDIR)
vlog("# Using HGTMP", HGTMP)
@@ -264,6 +319,15 @@ try:
try:
install_hg()
+ if options.timeout > 0:
+ try:
+ signal.signal(signal.SIGALRM, alarmed)
+ vlog('# Running tests with %d-second timeout' %
+ options.timeout)
+ except AttributeError:
+ print 'WARNING: cannot run tests with timeouts'
+ options.timeout = 0
+
tests = 0
failed = 0