##// END OF EJS Templates
Provide a relevant description for --timeout.
Will Maier -
r2672:118b198c default
parent child Browse files
Show More
@@ -1,352 +1,352 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 import difflib
11 11 import errno
12 12 import optparse
13 13 import os
14 14 import popen2
15 15 import re
16 16 import shutil
17 17 import signal
18 18 import sys
19 19 import tempfile
20 20 import time
21 21
22 22 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed", "merge"]
23 23
24 24 parser = optparse.OptionParser("%prog [options] [tests]")
25 25 parser.add_option("-v", "--verbose", action="store_true",
26 26 help="output verbose messages")
27 27 parser.add_option("-t", "--timeout", type="int",
28 help="output verbose messages")
28 help="kill errant tests after TIMEOUT seconds")
29 29 parser.add_option("-c", "--cover", action="store_true",
30 30 help="print a test coverage report")
31 31 parser.add_option("-s", "--cover_stdlib", action="store_true",
32 32 help="print a test coverage report inc. standard libraries")
33 33 parser.add_option("-C", "--annotate", action="store_true",
34 34 help="output files annotated with coverage")
35 35 parser.set_defaults(timeout=180)
36 36 (options, args) = parser.parse_args()
37 37 verbose = options.verbose
38 38 coverage = options.cover or options.cover_stdlib or options.annotate
39 39
40 40 def vlog(*msg):
41 41 if verbose:
42 42 for m in msg:
43 43 print m,
44 44 print
45 45
46 46 def splitnewlines(text):
47 47 '''like str.splitlines, but only split on newlines.
48 48 keep line endings.'''
49 49 i = 0
50 50 lines = []
51 51 while True:
52 52 n = text.find('\n', i)
53 53 if n == -1:
54 54 last = text[i:]
55 55 if last:
56 56 lines.append(last)
57 57 return lines
58 58 lines.append(text[i:n+1])
59 59 i = n + 1
60 60
61 61 def show_diff(expected, output):
62 62 for line in difflib.unified_diff(expected, output,
63 63 "Expected output", "Test output"):
64 64 sys.stdout.write(line)
65 65
66 66 def find_program(program):
67 67 """Search PATH for a executable program"""
68 68 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
69 69 name = os.path.join(p, program)
70 70 if os.access(name, os.X_OK):
71 71 return name
72 72 return None
73 73
74 74 def check_required_tools():
75 75 # Before we go any further, check for pre-requisite tools
76 76 # stuff from coreutils (cat, rm, etc) are not tested
77 77 for p in required_tools:
78 78 if os.name == 'nt':
79 79 p += '.exe'
80 80 found = find_program(p)
81 81 if found:
82 82 vlog("# Found prerequisite", p, "at", found)
83 83 else:
84 84 print "WARNING: Did not find prerequisite tool: "+p
85 85
86 86 def cleanup_exit():
87 87 if verbose:
88 88 print "# Cleaning up HGTMP", HGTMP
89 89 shutil.rmtree(HGTMP, True)
90 90
91 91 def use_correct_python():
92 92 # some tests run python interpreter. they must use same
93 93 # interpreter we use or bad things will happen.
94 94 exedir, exename = os.path.split(sys.executable)
95 95 if exename == 'python':
96 96 path = find_program('python')
97 97 if os.path.dirname(path) == exedir:
98 98 return
99 99 vlog('# Making python executable in test path use correct Python')
100 100 my_python = os.path.join(BINDIR, 'python')
101 101 try:
102 102 os.symlink(sys.executable, my_python)
103 103 except AttributeError:
104 104 # windows fallback
105 105 shutil.copyfile(sys.executable, my_python)
106 106 shutil.copymode(sys.executable, my_python)
107 107
108 108 def install_hg():
109 109 vlog("# Performing temporary installation of HG")
110 110 installerrs = os.path.join("tests", "install.err")
111 111
112 112 os.chdir("..") # Get back to hg root
113 113 cmd = ('%s setup.py clean --all'
114 114 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
115 115 % (sys.executable, INST, PYTHONDIR, installerrs))
116 116 vlog("# Running", cmd)
117 117 if os.system(cmd) == 0:
118 118 if not verbose:
119 119 os.remove(installerrs)
120 120 else:
121 121 f = open(installerrs)
122 122 for line in f:
123 123 print line,
124 124 f.close()
125 125 sys.exit(1)
126 126 os.chdir(TESTDIR)
127 127
128 128 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
129 129 os.environ["PYTHONPATH"] = PYTHONDIR
130 130
131 131 use_correct_python()
132 132
133 133 if coverage:
134 134 vlog("# Installing coverage wrapper")
135 135 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
136 136 if os.path.exists(COVERAGE_FILE):
137 137 os.unlink(COVERAGE_FILE)
138 138 # Create a wrapper script to invoke hg via coverage.py
139 139 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
140 140 f = open(os.path.join(BINDIR, 'hg'), 'w')
141 141 f.write('#!' + sys.executable + '\n')
142 142 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
143 143 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
144 144 os.path.join(TESTDIR, 'coverage.py'),
145 145 os.path.join(BINDIR, '_hg.py')))
146 146 f.close()
147 147 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
148 148
149 149 def output_coverage():
150 150 vlog("# Producing coverage report")
151 151 omit = [BINDIR, TESTDIR, PYTHONDIR]
152 152 if not options.cover_stdlib:
153 153 # Exclude as system paths (ignoring empty strings seen on win)
154 154 omit += [x for x in sys.path if x != '']
155 155 omit = ','.join(omit)
156 156 os.chdir(PYTHONDIR)
157 157 cmd = '"%s" "%s" -r "--omit=%s"' % (
158 158 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
159 159 vlog("# Running: "+cmd)
160 160 os.system(cmd)
161 161 if options.annotate:
162 162 adir = os.path.join(TESTDIR, 'annotated')
163 163 if not os.path.isdir(adir):
164 164 os.mkdir(adir)
165 165 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
166 166 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
167 167 adir, omit)
168 168 vlog("# Running: "+cmd)
169 169 os.system(cmd)
170 170
171 171 class Timeout(Exception):
172 172 pass
173 173
174 174 def alarmed(signum, frame):
175 175 raise Timeout
176 176
177 177 def run(cmd):
178 178 """Run command in a sub-process, capturing the output (stdout and stderr).
179 179 Return the exist code, and output."""
180 180 # TODO: Use subprocess.Popen if we're running on Python 2.4
181 181 if os.name == 'nt':
182 182 tochild, fromchild = os.popen4(cmd)
183 183 tochild.close()
184 184 output = fromchild.read()
185 185 ret = fromchild.close()
186 186 if ret == None:
187 187 ret = 0
188 188 else:
189 189 proc = popen2.Popen4(cmd)
190 190 try:
191 191 output = ''
192 192 proc.tochild.close()
193 193 output = proc.fromchild.read()
194 194 ret = proc.wait()
195 195 except Timeout:
196 196 vlog('# Process %d timed out - killing it' % proc.pid)
197 197 os.kill(proc.pid, signal.SIGTERM)
198 198 ret = proc.wait()
199 199 if ret == 0:
200 200 ret = signal.SIGTERM << 8
201 201 return ret, splitnewlines(output)
202 202
203 203 def run_one(test):
204 204 vlog("# Test", test)
205 205 if not verbose:
206 206 sys.stdout.write('.')
207 207 sys.stdout.flush()
208 208
209 209 err = os.path.join(TESTDIR, test+".err")
210 210 ref = os.path.join(TESTDIR, test+".out")
211 211
212 212 if os.path.exists(err):
213 213 os.remove(err) # Remove any previous output files
214 214
215 215 # Make a tmp subdirectory to work in
216 216 tmpd = os.path.join(HGTMP, test)
217 217 os.mkdir(tmpd)
218 218 os.chdir(tmpd)
219 219
220 220 if test.endswith(".py"):
221 221 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
222 222 else:
223 223 cmd = '"%s"' % (os.path.join(TESTDIR, test))
224 224
225 225 # To reliably get the error code from batch files on WinXP,
226 226 # the "cmd /c call" prefix is needed. Grrr
227 227 if os.name == 'nt' and test.endswith(".bat"):
228 228 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
229 229
230 230 if options.timeout > 0:
231 231 signal.alarm(options.timeout)
232 232
233 233 vlog("# Running", cmd)
234 234 ret, out = run(cmd)
235 235 vlog("# Ret was:", ret)
236 236
237 237 if options.timeout > 0:
238 238 signal.alarm(0)
239 239
240 240 diffret = 0
241 241 # If reference output file exists, check test output against it
242 242 if os.path.exists(ref):
243 243 f = open(ref, "r")
244 244 ref_out = splitnewlines(f.read())
245 245 f.close()
246 246 else:
247 247 ref_out = ['']
248 248 if out != ref_out:
249 249 diffret = 1
250 250 print "\nERROR: %s output changed" % (test)
251 251 show_diff(ref_out, out)
252 252 if ret:
253 253 print "\nERROR: %s failed with error code %d" % (test, ret)
254 254 elif diffret:
255 255 ret = diffret
256 256
257 257 if ret != 0: # Save errors to a file for diagnosis
258 258 f = open(err, "wb")
259 259 for line in out:
260 260 f.write(line)
261 261 f.close()
262 262
263 263 # Kill off any leftover daemon processes
264 264 try:
265 265 fp = file(DAEMON_PIDS)
266 266 for line in fp:
267 267 try:
268 268 pid = int(line)
269 269 except ValueError:
270 270 continue
271 271 try:
272 272 os.kill(pid, 0)
273 273 vlog('# Killing daemon process %d' % pid)
274 274 os.kill(pid, signal.SIGTERM)
275 275 time.sleep(0.25)
276 276 os.kill(pid, 0)
277 277 vlog('# Daemon process %d is stuck - really killing it' % pid)
278 278 os.kill(pid, signal.SIGKILL)
279 279 except OSError, err:
280 280 if err.errno != errno.ESRCH:
281 281 raise
282 282 fp.close()
283 283 os.unlink(DAEMON_PIDS)
284 284 except IOError:
285 285 pass
286 286
287 287 os.chdir(TESTDIR)
288 288 shutil.rmtree(tmpd, True)
289 289 return ret == 0
290 290
291 291
292 292 os.umask(022)
293 293
294 294 check_required_tools()
295 295
296 296 # Reset some environment variables to well-known values so that
297 297 # the tests produce repeatable output.
298 298 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
299 299 os.environ['TZ'] = 'GMT'
300 300
301 301 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
302 302 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
303 303 os.environ["HGUSER"] = "test"
304 304 os.environ["HGRCPATH"] = ""
305 305
306 306 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
307 307 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
308 308 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
309 309
310 310 vlog("# Using TESTDIR", TESTDIR)
311 311 vlog("# Using HGTMP", HGTMP)
312 312
313 313 INST = os.path.join(HGTMP, "install")
314 314 BINDIR = os.path.join(INST, "bin")
315 315 PYTHONDIR = os.path.join(INST, "lib", "python")
316 316 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
317 317
318 318 try:
319 319 try:
320 320 install_hg()
321 321
322 322 if options.timeout > 0:
323 323 try:
324 324 signal.signal(signal.SIGALRM, alarmed)
325 325 vlog('# Running tests with %d-second timeout' %
326 326 options.timeout)
327 327 except AttributeError:
328 328 print 'WARNING: cannot run tests with timeouts'
329 329 options.timeout = 0
330 330
331 331 tests = 0
332 332 failed = 0
333 333
334 334 if len(args) == 0:
335 335 args = os.listdir(".")
336 336 for test in args:
337 337 if test.startswith("test-") and not '~' in test and not '.' in test:
338 338 if not run_one(test):
339 339 failed += 1
340 340 tests += 1
341 341
342 342 print "\n# Ran %d tests, %d failed." % (tests, failed)
343 343 if coverage:
344 344 output_coverage()
345 345 except KeyboardInterrupt:
346 346 failed = True
347 347 print "\ninterrupted!"
348 348 finally:
349 349 cleanup_exit()
350 350
351 351 if failed:
352 352 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now