comparison tests/run-tests.py @ 2570:83cfd95eafb5

tests: add timeouts, make run-tests.py clean up dead daemon processes test timeout feature is needed for test with python 2.5 beta. if test does not complete in time (30 seconds is default), it is killed. some times daemon process used in test can be alive after the test is killed by user or by timeout. tests now record daemon pids into $DAEMON_PIDS and run-tests.py kills all living daemons after every test. final little change is to add newline to end of pid file printed by "hg serve", else "cat hg.pid >> $DAEMON_FILES" gives garbage.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Thu, 06 Jul 2006 11:45:34 -0700
parents 2264b2b077a1
children 6a961a54f953
comparison
equal deleted inserted replaced
2569:2264b2b077a1 2570:83cfd95eafb5
5 # Copyright 2006 Matt Mackall <mpm@selenic.com> 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 # 6 #
7 # This software may be used and distributed according to the terms 7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference. 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 import os, sys, shutil, re 10 import difflib
11 import errno
12 import optparse
13 import os
14 import popen2
15 import re
16 import shutil
17 import signal
18 import sys
11 import tempfile 19 import tempfile
12 import difflib 20 import time
13 import popen2
14 from optparse import OptionParser
15 21
16 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] 22 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
17 23
18 parser = OptionParser("%prog [options] [tests]") 24 parser = optparse.OptionParser("%prog [options] [tests]")
19 parser.add_option("-v", "--verbose", action="store_true", 25 parser.add_option("-v", "--verbose", action="store_true",
26 help="output verbose messages")
27 parser.add_option("-t", "--timeout", type="int",
20 help="output verbose messages") 28 help="output verbose messages")
21 parser.add_option("-c", "--cover", action="store_true", 29 parser.add_option("-c", "--cover", action="store_true",
22 help="print a test coverage report") 30 help="print a test coverage report")
23 parser.add_option("-s", "--cover_stdlib", action="store_true", 31 parser.add_option("-s", "--cover_stdlib", action="store_true",
24 help="print a test coverage report inc. standard libraries") 32 help="print a test coverage report inc. standard libraries")
25 parser.add_option("-C", "--annotate", action="store_true", 33 parser.add_option("-C", "--annotate", action="store_true",
26 help="output files annotated with coverage") 34 help="output files annotated with coverage")
35 parser.set_defaults(timeout=30)
27 (options, args) = parser.parse_args() 36 (options, args) = parser.parse_args()
28 verbose = options.verbose 37 verbose = options.verbose
29 coverage = options.cover or options.cover_stdlib or options.annotate 38 coverage = options.cover or options.cover_stdlib or options.annotate
30 39
31 def vlog(*msg): 40 def vlog(*msg):
157 sys.executable, os.path.join(TESTDIR, 'coverage.py'), 166 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
158 adir, omit) 167 adir, omit)
159 vlog("# Running: "+cmd) 168 vlog("# Running: "+cmd)
160 os.system(cmd) 169 os.system(cmd)
161 170
171 class Timeout(Exception):
172 pass
173
174 def alarmed(signum, frame):
175 raise Timeout
176
162 def run(cmd): 177 def run(cmd):
163 """Run command in a sub-process, capturing the output (stdout and stderr). 178 """Run command in a sub-process, capturing the output (stdout and stderr).
164 Return the exist code, and output.""" 179 Return the exist code, and output."""
165 # TODO: Use subprocess.Popen if we're running on Python 2.4 180 # TODO: Use subprocess.Popen if we're running on Python 2.4
166 if os.name == 'nt': 181 if os.name == 'nt':
170 ret = fromchild.close() 185 ret = fromchild.close()
171 if ret == None: 186 if ret == None:
172 ret = 0 187 ret = 0
173 else: 188 else:
174 proc = popen2.Popen4(cmd) 189 proc = popen2.Popen4(cmd)
175 proc.tochild.close() 190 try:
176 output = proc.fromchild.read() 191 output = ''
177 ret = proc.wait() 192 proc.tochild.close()
193 output = proc.fromchild.read()
194 ret = proc.wait()
195 except Timeout:
196 vlog('# Process %d timed out - killing it' % proc.pid)
197 os.kill(proc.pid, signal.SIGTERM)
198 ret = proc.wait()
199 if ret == 0:
200 ret = signal.SIGTERM << 8
178 return ret, splitnewlines(output) 201 return ret, splitnewlines(output)
179 202
180 def run_one(test): 203 def run_one(test):
181 vlog("# Test", test) 204 vlog("# Test", test)
182 if not verbose: 205 if not verbose:
202 # To reliably get the error code from batch files on WinXP, 225 # To reliably get the error code from batch files on WinXP,
203 # the "cmd /c call" prefix is needed. Grrr 226 # the "cmd /c call" prefix is needed. Grrr
204 if os.name == 'nt' and test.endswith(".bat"): 227 if os.name == 'nt' and test.endswith(".bat"):
205 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test)) 228 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
206 229
230 if options.timeout > 0:
231 signal.alarm(options.timeout)
232
207 vlog("# Running", cmd) 233 vlog("# Running", cmd)
208 ret, out = run(cmd) 234 ret, out = run(cmd)
209 vlog("# Ret was:", ret) 235 vlog("# Ret was:", ret)
236
237 if options.timeout > 0:
238 signal.alarm(0)
210 239
211 diffret = 0 240 diffret = 0
212 # If reference output file exists, check test output against it 241 # If reference output file exists, check test output against it
213 if os.path.exists(ref): 242 if os.path.exists(ref):
214 f = open(ref, "r") 243 f = open(ref, "r")
229 f = open(err, "wb") 258 f = open(err, "wb")
230 for line in out: 259 for line in out:
231 f.write(line) 260 f.write(line)
232 f.close() 261 f.close()
233 262
263 # Kill off any leftover daemon processes
264 try:
265 fp = file(DAEMON_PIDS)
266 for line in fp:
267 try:
268 pid = int(line)
269 except ValueError:
270 continue
271 try:
272 os.kill(pid, 0)
273 vlog('# Killing daemon process %d' % pid)
274 os.kill(pid, signal.SIGTERM)
275 time.sleep(0.25)
276 os.kill(pid, 0)
277 vlog('# Daemon process %d is stuck - really killing it' % pid)
278 os.kill(pid, signal.SIGKILL)
279 except OSError, err:
280 if err.errno != errno.ESRCH:
281 raise
282 fp.close()
283 os.unlink(DAEMON_PIDS)
284 except IOError:
285 pass
286
234 os.chdir(TESTDIR) 287 os.chdir(TESTDIR)
235 shutil.rmtree(tmpd, True) 288 shutil.rmtree(tmpd, True)
236 return ret == 0 289 return ret == 0
237 290
238 291
250 os.environ["HGUSER"] = "test" 303 os.environ["HGUSER"] = "test"
251 os.environ["HGRCPATH"] = "" 304 os.environ["HGRCPATH"] = ""
252 305
253 TESTDIR = os.environ["TESTDIR"] = os.getcwd() 306 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
254 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") 307 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
308 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
309
255 vlog("# Using TESTDIR", TESTDIR) 310 vlog("# Using TESTDIR", TESTDIR)
256 vlog("# Using HGTMP", HGTMP) 311 vlog("# Using HGTMP", HGTMP)
257 312
258 INST = os.path.join(HGTMP, "install") 313 INST = os.path.join(HGTMP, "install")
259 BINDIR = os.path.join(INST, "bin") 314 BINDIR = os.path.join(INST, "bin")
261 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") 316 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
262 317
263 try: 318 try:
264 try: 319 try:
265 install_hg() 320 install_hg()
321
322 if options.timeout > 0:
323 try:
324 signal.signal(signal.SIGALRM, alarmed)
325 vlog('# Running tests with %d-second timeout' %
326 options.timeout)
327 except AttributeError:
328 print 'WARNING: cannot run tests with timeouts'
329 options.timeout = 0
266 330
267 tests = 0 331 tests = 0
268 failed = 0 332 failed = 0
269 333
270 if len(args) == 0: 334 if len(args) == 0: