##// END OF EJS Templates
run-tests.py: tell coverage.py to ignore errors...
Alexis S. L. Carvalho -
r4318:b95a4211 default
parent child Browse files
Show More
@@ -1,421 +1,421
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 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.add_option("-r", "--retest", action="store_true",
36 36 help="retest failed tests")
37 37 parser.add_option("-f", "--first", action="store_true",
38 38 help="exit on the first test failure")
39 39 parser.add_option("-R", "--restart", action="store_true",
40 40 help="restart at last error")
41 41 parser.add_option("-i", "--interactive", action="store_true",
42 42 help="prompt to accept changed output")
43 43
44 44 parser.set_defaults(timeout=180)
45 45 (options, args) = parser.parse_args()
46 46 verbose = options.verbose
47 47 coverage = options.cover or options.cover_stdlib or options.annotate
48 48
49 49 def vlog(*msg):
50 50 if verbose:
51 51 for m in msg:
52 52 print m,
53 53 print
54 54
55 55 def splitnewlines(text):
56 56 '''like str.splitlines, but only split on newlines.
57 57 keep line endings.'''
58 58 i = 0
59 59 lines = []
60 60 while True:
61 61 n = text.find('\n', i)
62 62 if n == -1:
63 63 last = text[i:]
64 64 if last:
65 65 lines.append(last)
66 66 return lines
67 67 lines.append(text[i:n+1])
68 68 i = n + 1
69 69
70 70 def show_diff(expected, output):
71 71 for line in difflib.unified_diff(expected, output,
72 72 "Expected output", "Test output"):
73 73 sys.stdout.write(line)
74 74
75 75 def find_program(program):
76 76 """Search PATH for a executable program"""
77 77 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
78 78 name = os.path.join(p, program)
79 79 if os.access(name, os.X_OK):
80 80 return name
81 81 return None
82 82
83 83 def check_required_tools():
84 84 # Before we go any further, check for pre-requisite tools
85 85 # stuff from coreutils (cat, rm, etc) are not tested
86 86 for p in required_tools:
87 87 if os.name == 'nt':
88 88 p += '.exe'
89 89 found = find_program(p)
90 90 if found:
91 91 vlog("# Found prerequisite", p, "at", found)
92 92 else:
93 93 print "WARNING: Did not find prerequisite tool: "+p
94 94
95 95 def cleanup_exit():
96 96 if verbose:
97 97 print "# Cleaning up HGTMP", HGTMP
98 98 shutil.rmtree(HGTMP, True)
99 99
100 100 def use_correct_python():
101 101 # some tests run python interpreter. they must use same
102 102 # interpreter we use or bad things will happen.
103 103 exedir, exename = os.path.split(sys.executable)
104 104 if exename == 'python':
105 105 path = find_program('python')
106 106 if os.path.dirname(path) == exedir:
107 107 return
108 108 vlog('# Making python executable in test path use correct Python')
109 109 my_python = os.path.join(BINDIR, 'python')
110 110 try:
111 111 os.symlink(sys.executable, my_python)
112 112 except AttributeError:
113 113 # windows fallback
114 114 shutil.copyfile(sys.executable, my_python)
115 115 shutil.copymode(sys.executable, my_python)
116 116
117 117 def install_hg():
118 118 vlog("# Performing temporary installation of HG")
119 119 installerrs = os.path.join("tests", "install.err")
120 120
121 121 os.chdir("..") # Get back to hg root
122 122 cmd = ('%s setup.py clean --all'
123 123 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
124 124 % (sys.executable, INST, PYTHONDIR, installerrs))
125 125 vlog("# Running", cmd)
126 126 if os.system(cmd) == 0:
127 127 if not verbose:
128 128 os.remove(installerrs)
129 129 else:
130 130 f = open(installerrs)
131 131 for line in f:
132 132 print line,
133 133 f.close()
134 134 sys.exit(1)
135 135 os.chdir(TESTDIR)
136 136
137 137 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
138 138 os.environ["PYTHONPATH"] = PYTHONDIR
139 139
140 140 use_correct_python()
141 141
142 142 if coverage:
143 143 vlog("# Installing coverage wrapper")
144 144 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
145 145 if os.path.exists(COVERAGE_FILE):
146 146 os.unlink(COVERAGE_FILE)
147 147 # Create a wrapper script to invoke hg via coverage.py
148 148 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
149 149 f = open(os.path.join(BINDIR, 'hg'), 'w')
150 150 f.write('#!' + sys.executable + '\n')
151 151 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
152 152 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
153 153 os.path.join(TESTDIR, 'coverage.py'),
154 154 os.path.join(BINDIR, '_hg.py')))
155 155 f.close()
156 156 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
157 157
158 158 def output_coverage():
159 159 vlog("# Producing coverage report")
160 160 omit = [BINDIR, TESTDIR, PYTHONDIR]
161 161 if not options.cover_stdlib:
162 162 # Exclude as system paths (ignoring empty strings seen on win)
163 163 omit += [x for x in sys.path if x != '']
164 164 omit = ','.join(omit)
165 165 os.chdir(PYTHONDIR)
166 cmd = '"%s" "%s" -r "--omit=%s"' % (
166 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
167 167 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
168 168 vlog("# Running: "+cmd)
169 169 os.system(cmd)
170 170 if options.annotate:
171 171 adir = os.path.join(TESTDIR, 'annotated')
172 172 if not os.path.isdir(adir):
173 173 os.mkdir(adir)
174 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
174 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
175 175 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
176 176 adir, omit)
177 177 vlog("# Running: "+cmd)
178 178 os.system(cmd)
179 179
180 180 class Timeout(Exception):
181 181 pass
182 182
183 183 def alarmed(signum, frame):
184 184 raise Timeout
185 185
186 186 def run(cmd):
187 187 """Run command in a sub-process, capturing the output (stdout and stderr).
188 188 Return the exist code, and output."""
189 189 # TODO: Use subprocess.Popen if we're running on Python 2.4
190 190 if os.name == 'nt':
191 191 tochild, fromchild = os.popen4(cmd)
192 192 tochild.close()
193 193 output = fromchild.read()
194 194 ret = fromchild.close()
195 195 if ret == None:
196 196 ret = 0
197 197 else:
198 198 proc = popen2.Popen4(cmd)
199 199 try:
200 200 output = ''
201 201 proc.tochild.close()
202 202 output = proc.fromchild.read()
203 203 ret = proc.wait()
204 204 except Timeout:
205 205 vlog('# Process %d timed out - killing it' % proc.pid)
206 206 os.kill(proc.pid, signal.SIGTERM)
207 207 ret = proc.wait()
208 208 if ret == 0:
209 209 ret = signal.SIGTERM << 8
210 210 return ret, splitnewlines(output)
211 211
212 212 def run_one(test):
213 213 '''tristate output:
214 214 None -> skipped
215 215 True -> passed
216 216 False -> failed'''
217 217
218 218 vlog("# Test", test)
219 219 if not verbose:
220 220 sys.stdout.write('.')
221 221 sys.stdout.flush()
222 222
223 223 # create a fresh hgrc
224 224 hgrc = file(HGRCPATH, 'w+')
225 225 hgrc.close()
226 226
227 227 err = os.path.join(TESTDIR, test+".err")
228 228 ref = os.path.join(TESTDIR, test+".out")
229 229
230 230 if os.path.exists(err):
231 231 os.remove(err) # Remove any previous output files
232 232
233 233 # Make a tmp subdirectory to work in
234 234 tmpd = os.path.join(HGTMP, test)
235 235 os.mkdir(tmpd)
236 236 os.chdir(tmpd)
237 237
238 238 lctest = test.lower()
239 239
240 240 if lctest.endswith('.py'):
241 241 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
242 242 elif lctest.endswith('.bat'):
243 243 # do not run batch scripts on non-windows
244 244 if os.name != 'nt':
245 245 print '\nSkipping %s: batch script' % test
246 246 return None
247 247 # To reliably get the error code from batch files on WinXP,
248 248 # the "cmd /c call" prefix is needed. Grrr
249 249 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
250 250 else:
251 251 # do not run shell scripts on windows
252 252 if os.name == 'nt':
253 253 print '\nSkipping %s: shell script' % test
254 254 return None
255 255 # do not try to run non-executable programs
256 256 if not os.access(os.path.join(TESTDIR, test), os.X_OK):
257 257 print '\nSkipping %s: not executable' % test
258 258 return None
259 259 cmd = '"%s"' % (os.path.join(TESTDIR, test))
260 260
261 261 if options.timeout > 0:
262 262 signal.alarm(options.timeout)
263 263
264 264 vlog("# Running", cmd)
265 265 ret, out = run(cmd)
266 266 vlog("# Ret was:", ret)
267 267
268 268 if options.timeout > 0:
269 269 signal.alarm(0)
270 270
271 271 diffret = 0
272 272 # If reference output file exists, check test output against it
273 273 if os.path.exists(ref):
274 274 f = open(ref, "r")
275 275 ref_out = splitnewlines(f.read())
276 276 f.close()
277 277 else:
278 278 ref_out = []
279 279 if out != ref_out:
280 280 diffret = 1
281 281 print "\nERROR: %s output changed" % (test)
282 282 show_diff(ref_out, out)
283 283 if ret:
284 284 print "\nERROR: %s failed with error code %d" % (test, ret)
285 285 elif diffret:
286 286 ret = diffret
287 287
288 288 if ret != 0: # Save errors to a file for diagnosis
289 289 f = open(err, "wb")
290 290 for line in out:
291 291 f.write(line)
292 292 f.close()
293 293
294 294 # Kill off any leftover daemon processes
295 295 try:
296 296 fp = file(DAEMON_PIDS)
297 297 for line in fp:
298 298 try:
299 299 pid = int(line)
300 300 except ValueError:
301 301 continue
302 302 try:
303 303 os.kill(pid, 0)
304 304 vlog('# Killing daemon process %d' % pid)
305 305 os.kill(pid, signal.SIGTERM)
306 306 time.sleep(0.25)
307 307 os.kill(pid, 0)
308 308 vlog('# Daemon process %d is stuck - really killing it' % pid)
309 309 os.kill(pid, signal.SIGKILL)
310 310 except OSError, err:
311 311 if err.errno != errno.ESRCH:
312 312 raise
313 313 fp.close()
314 314 os.unlink(DAEMON_PIDS)
315 315 except IOError:
316 316 pass
317 317
318 318 os.chdir(TESTDIR)
319 319 shutil.rmtree(tmpd, True)
320 320 return ret == 0
321 321
322 322
323 323 os.umask(022)
324 324
325 325 check_required_tools()
326 326
327 327 # Reset some environment variables to well-known values so that
328 328 # the tests produce repeatable output.
329 329 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
330 330 os.environ['TZ'] = 'GMT'
331 331
332 332 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
333 333 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
334 334 os.environ["HGUSER"] = "test"
335 335 os.environ["HGENCODING"] = "ascii"
336 336 os.environ["HGENCODINGMODE"] = "strict"
337 337
338 338 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
339 339 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
340 340 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
341 341 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
342 342
343 343 vlog("# Using TESTDIR", TESTDIR)
344 344 vlog("# Using HGTMP", HGTMP)
345 345
346 346 INST = os.path.join(HGTMP, "install")
347 347 BINDIR = os.path.join(INST, "bin")
348 348 PYTHONDIR = os.path.join(INST, "lib", "python")
349 349 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
350 350
351 351 try:
352 352 try:
353 353 install_hg()
354 354
355 355 if options.timeout > 0:
356 356 try:
357 357 signal.signal(signal.SIGALRM, alarmed)
358 358 vlog('# Running tests with %d-second timeout' %
359 359 options.timeout)
360 360 except AttributeError:
361 361 print 'WARNING: cannot run tests with timeouts'
362 362 options.timeout = 0
363 363
364 364 tested = 0
365 365 failed = 0
366 366 skipped = 0
367 367
368 368 if len(args) == 0:
369 369 args = os.listdir(".")
370 370 args.sort()
371 371
372 372
373 373 tests = []
374 374 for test in args:
375 375 if (test.startswith("test-") and '~' not in test and
376 376 ('.' not in test or test.endswith('.py') or
377 377 test.endswith('.bat'))):
378 378 tests.append(test)
379 379
380 380 if options.restart:
381 381 orig = list(tests)
382 382 while tests:
383 383 if os.path.exists(tests[0] + ".err"):
384 384 break
385 385 tests.pop(0)
386 386 if not tests:
387 387 print "running all tests"
388 388 tests = orig
389 389
390 390 for test in tests:
391 391 if options.retest and not os.path.exists(test + ".err"):
392 392 skipped += 1
393 393 continue
394 394 ret = run_one(test)
395 395 if ret is None:
396 396 skipped += 1
397 397 elif not ret:
398 398 if options.interactive:
399 399 print "Accept this change? [n] ",
400 400 answer = sys.stdin.readline().strip()
401 401 if answer.lower() in "y yes".split():
402 402 os.rename(test + ".err", test + ".out")
403 403 tested += 1
404 404 continue
405 405 failed += 1
406 406 if options.first:
407 407 break
408 408 tested += 1
409 409
410 410 print "\n# Ran %d tests, %d skipped, %d failed." % (tested, skipped,
411 411 failed)
412 412 if coverage:
413 413 output_coverage()
414 414 except KeyboardInterrupt:
415 415 failed = True
416 416 print "\ninterrupted!"
417 417 finally:
418 418 cleanup_exit()
419 419
420 420 if failed:
421 421 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now