##// END OF EJS Templates
tests: delete parallel coverage files after combining
Dirkjan Ochtman -
r8696:73aa0510 default
parent child Browse files
Show More
@@ -1,794 +1,798 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 of the
8 8 # GNU General Public License version 2, incorporated herein by reference.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 #
37 37 # (You could use any subset of the tests: test-s* happens to match
38 38 # enough that it's worth doing parallel runs, few enough that it
39 39 # completes fairly quickly, includes both shell and Python scripts, and
40 40 # includes some scripts that run daemon processes.)
41 41
42 42 import difflib
43 43 import errno
44 44 import optparse
45 45 import os
46 46 import subprocess
47 47 import shutil
48 48 import signal
49 49 import sys
50 50 import tempfile
51 51 import time
52 52
53 53 closefds = os.name == 'posix'
54 54 def Popen4(cmd, bufsize=-1):
55 55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
56 56 close_fds=closefds,
57 57 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
58 58 stderr=subprocess.STDOUT)
59 59 p.fromchild = p.stdout
60 60 p.tochild = p.stdin
61 61 p.childerr = p.stderr
62 62 return p
63 63
64 64 # reserved exit code to skip test (used by hghave)
65 65 SKIPPED_STATUS = 80
66 66 SKIPPED_PREFIX = 'skipped: '
67 67 FAILED_PREFIX = 'hghave check failed: '
68 68 PYTHON = sys.executable
69 69
70 70 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
71 71
72 72 defaults = {
73 73 'jobs': ('HGTEST_JOBS', 1),
74 74 'timeout': ('HGTEST_TIMEOUT', 180),
75 75 'port': ('HGTEST_PORT', 20059),
76 76 }
77 77
78 78 def parseargs():
79 79 parser = optparse.OptionParser("%prog [options] [tests]")
80 80 parser.add_option("-C", "--annotate", action="store_true",
81 81 help="output files annotated with coverage")
82 82 parser.add_option("--child", type="int",
83 83 help="run as child process, summary to given fd")
84 84 parser.add_option("-c", "--cover", action="store_true",
85 85 help="print a test coverage report")
86 86 parser.add_option("-f", "--first", action="store_true",
87 87 help="exit on the first test failure")
88 88 parser.add_option("-i", "--interactive", action="store_true",
89 89 help="prompt to accept changed output")
90 90 parser.add_option("-j", "--jobs", type="int",
91 91 help="number of jobs to run in parallel"
92 92 " (default: $%s or %d)" % defaults['jobs'])
93 93 parser.add_option("--keep-tmpdir", action="store_true",
94 94 help="keep temporary directory after running tests"
95 95 " (best used with --tmpdir)")
96 96 parser.add_option("-R", "--restart", action="store_true",
97 97 help="restart at last error")
98 98 parser.add_option("-p", "--port", type="int",
99 99 help="port on which servers should listen"
100 100 " (default: $%s or %d)" % defaults['port'])
101 101 parser.add_option("-r", "--retest", action="store_true",
102 102 help="retest failed tests")
103 103 parser.add_option("-s", "--cover_stdlib", action="store_true",
104 104 help="print a test coverage report inc. standard libraries")
105 105 parser.add_option("-t", "--timeout", type="int",
106 106 help="kill errant tests after TIMEOUT seconds"
107 107 " (default: $%s or %d)" % defaults['timeout'])
108 108 parser.add_option("--tmpdir", type="string",
109 109 help="run tests in the given temporary directory")
110 110 parser.add_option("-v", "--verbose", action="store_true",
111 111 help="output verbose messages")
112 112 parser.add_option("-n", "--nodiff", action="store_true",
113 113 help="skip showing test changes")
114 114 parser.add_option("--with-hg", type="string",
115 115 metavar="HG",
116 116 help="test using specified hg script rather than a "
117 117 "temporary installation")
118 118 parser.add_option("--local", action="store_true",
119 119 help="shortcut for --with-hg=<testdir>/../hg")
120 120 parser.add_option("--pure", action="store_true",
121 121 help="use pure Python code instead of C extensions")
122 122
123 123 for option, default in defaults.items():
124 124 defaults[option] = int(os.environ.get(*default))
125 125 parser.set_defaults(**defaults)
126 126 (options, args) = parser.parse_args()
127 127
128 128 if options.with_hg:
129 129 if not (os.path.isfile(options.with_hg) and
130 130 os.access(options.with_hg, os.X_OK)):
131 131 parser.error('--with-hg must specify an executable hg script')
132 132 if not os.path.basename(options.with_hg) == 'hg':
133 133 sys.stderr.write('warning: --with-hg should specify an hg script')
134 134 if options.local:
135 135 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
136 136 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
137 137 if not os.access(hgbin, os.X_OK):
138 138 parser.error('--local specified, but %r not found or not executable'
139 139 % hgbin)
140 140 options.with_hg = hgbin
141 141
142 142 options.anycoverage = (options.cover or
143 143 options.cover_stdlib or
144 144 options.annotate)
145 145
146 146 if options.anycoverage and options.with_hg:
147 147 # I'm not sure if this is a fundamental limitation or just a
148 148 # bug. But I don't want to waste people's time and energy doing
149 149 # test runs that don't give the results they want.
150 150 parser.error("sorry, coverage options do not work when --with-hg "
151 151 "or --local specified")
152 152
153 153 global vlog
154 154 if options.verbose:
155 155 if options.jobs > 1 or options.child is not None:
156 156 pid = "[%d]" % os.getpid()
157 157 else:
158 158 pid = None
159 159 def vlog(*msg):
160 160 if pid:
161 161 print pid,
162 162 for m in msg:
163 163 print m,
164 164 print
165 165 else:
166 166 vlog = lambda *msg: None
167 167
168 168 if options.jobs < 1:
169 169 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
170 170 sys.exit(1)
171 171 if options.interactive and options.jobs > 1:
172 172 print '(--interactive overrides --jobs)'
173 173 options.jobs = 1
174 174
175 175 return (options, args)
176 176
177 177 def rename(src, dst):
178 178 """Like os.rename(), trade atomicity and opened files friendliness
179 179 for existing destination support.
180 180 """
181 181 shutil.copy(src, dst)
182 182 os.remove(src)
183 183
184 184 def splitnewlines(text):
185 185 '''like str.splitlines, but only split on newlines.
186 186 keep line endings.'''
187 187 i = 0
188 188 lines = []
189 189 while True:
190 190 n = text.find('\n', i)
191 191 if n == -1:
192 192 last = text[i:]
193 193 if last:
194 194 lines.append(last)
195 195 return lines
196 196 lines.append(text[i:n+1])
197 197 i = n + 1
198 198
199 199 def parsehghaveoutput(lines):
200 200 '''Parse hghave log lines.
201 201 Return tuple of lists (missing, failed):
202 202 * the missing/unknown features
203 203 * the features for which existence check failed'''
204 204 missing = []
205 205 failed = []
206 206 for line in lines:
207 207 if line.startswith(SKIPPED_PREFIX):
208 208 line = line.splitlines()[0]
209 209 missing.append(line[len(SKIPPED_PREFIX):])
210 210 elif line.startswith(FAILED_PREFIX):
211 211 line = line.splitlines()[0]
212 212 failed.append(line[len(FAILED_PREFIX):])
213 213
214 214 return missing, failed
215 215
216 216 def showdiff(expected, output):
217 217 for line in difflib.unified_diff(expected, output,
218 218 "Expected output", "Test output"):
219 219 sys.stdout.write(line)
220 220
221 221 def findprogram(program):
222 222 """Search PATH for a executable program"""
223 223 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
224 224 name = os.path.join(p, program)
225 225 if os.access(name, os.X_OK):
226 226 return name
227 227 return None
228 228
229 229 def checktools():
230 230 # Before we go any further, check for pre-requisite tools
231 231 # stuff from coreutils (cat, rm, etc) are not tested
232 232 for p in requiredtools:
233 233 if os.name == 'nt':
234 234 p += '.exe'
235 235 found = findprogram(p)
236 236 if found:
237 237 vlog("# Found prerequisite", p, "at", found)
238 238 else:
239 239 print "WARNING: Did not find prerequisite tool: "+p
240 240
241 241 def cleanup(options):
242 242 if not options.keep_tmpdir:
243 243 vlog("# Cleaning up HGTMP", HGTMP)
244 244 shutil.rmtree(HGTMP, True)
245 245
246 246 def usecorrectpython():
247 247 # some tests run python interpreter. they must use same
248 248 # interpreter we use or bad things will happen.
249 249 exedir, exename = os.path.split(sys.executable)
250 250 if exename == 'python':
251 251 path = findprogram('python')
252 252 if os.path.dirname(path) == exedir:
253 253 return
254 254 vlog('# Making python executable in test path use correct Python')
255 255 mypython = os.path.join(BINDIR, 'python')
256 256 try:
257 257 os.symlink(sys.executable, mypython)
258 258 except AttributeError:
259 259 # windows fallback
260 260 shutil.copyfile(sys.executable, mypython)
261 261 shutil.copymode(sys.executable, mypython)
262 262
263 263 def installhg(options):
264 264 vlog("# Performing temporary installation of HG")
265 265 installerrs = os.path.join("tests", "install.err")
266 266 pure = options.pure and "--pure" or ""
267 267
268 268 # Run installer in hg root
269 269 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
270 270 cmd = ('%s setup.py %s clean --all'
271 271 ' install --force --prefix="%s" --install-lib="%s"'
272 272 ' --install-scripts="%s" >%s 2>&1'
273 273 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
274 274 vlog("# Running", cmd)
275 275 if os.system(cmd) == 0:
276 276 if not options.verbose:
277 277 os.remove(installerrs)
278 278 else:
279 279 f = open(installerrs)
280 280 for line in f:
281 281 print line,
282 282 f.close()
283 283 sys.exit(1)
284 284 os.chdir(TESTDIR)
285 285
286 286 usecorrectpython()
287 287
288 288 vlog("# Installing dummy diffstat")
289 289 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
290 290 f.write('#!' + sys.executable + '\n'
291 291 'import sys\n'
292 292 'files = 0\n'
293 293 'for line in sys.stdin:\n'
294 294 ' if line.startswith("diff "):\n'
295 295 ' files += 1\n'
296 296 'sys.stdout.write("files patched: %d\\n" % files)\n')
297 297 f.close()
298 298 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
299 299
300 300 if options.anycoverage:
301 301 vlog("# Installing coverage wrapper")
302 302 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
303 303 if os.path.exists(COVERAGE_FILE):
304 304 os.unlink(COVERAGE_FILE)
305 305 # Create a wrapper script to invoke hg via coverage.py
306 306 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
307 307 f = open(os.path.join(BINDIR, 'hg'), 'w')
308 308 f.write('#!' + sys.executable + '\n')
309 309 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
310 310 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
311 311 (os.path.join(TESTDIR, 'coverage.py'),
312 312 os.path.join(BINDIR, '_hg.py')))
313 313 f.close()
314 314 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
315 315
316 316 def outputcoverage(options):
317 317
318 318 vlog('# Producing coverage report')
319 319 os.chdir(PYTHONDIR)
320 320
321 321 def covrun(*args):
322 322 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
323 323 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
324 324 vlog('# Running: %s' % cmd)
325 325 os.system(cmd)
326 326
327 327 omit = [BINDIR, TESTDIR, PYTHONDIR]
328 328 if not options.cover_stdlib:
329 329 # Exclude as system paths (ignoring empty strings seen on win)
330 330 omit += [x for x in sys.path if x != '']
331 331 omit = ','.join(omit)
332 332
333 333 covrun('-c') # combine from parallel processes
334 for fn in os.listdir(TESTDIR):
335 if fn.startswith('.coverage.'):
336 os.unlink(os.path.join(TESTDIR, fn))
337
334 338 covrun('-i', '-r', '"--omit=%s"' % omit) # report
335 339 if options.annotate:
336 340 adir = os.path.join(TESTDIR, 'annotated')
337 341 if not os.path.isdir(adir):
338 342 os.mkdir(adir)
339 343 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
340 344
341 345 class Timeout(Exception):
342 346 pass
343 347
344 348 def alarmed(signum, frame):
345 349 raise Timeout
346 350
347 351 def run(cmd, options):
348 352 """Run command in a sub-process, capturing the output (stdout and stderr).
349 353 Return the exist code, and output."""
350 354 # TODO: Use subprocess.Popen if we're running on Python 2.4
351 355 if os.name == 'nt' or sys.platform.startswith('java'):
352 356 tochild, fromchild = os.popen4(cmd)
353 357 tochild.close()
354 358 output = fromchild.read()
355 359 ret = fromchild.close()
356 360 if ret == None:
357 361 ret = 0
358 362 else:
359 363 proc = Popen4(cmd)
360 364 try:
361 365 output = ''
362 366 proc.tochild.close()
363 367 output = proc.fromchild.read()
364 368 ret = proc.wait()
365 369 if os.WIFEXITED(ret):
366 370 ret = os.WEXITSTATUS(ret)
367 371 except Timeout:
368 372 vlog('# Process %d timed out - killing it' % proc.pid)
369 373 os.kill(proc.pid, signal.SIGTERM)
370 374 ret = proc.wait()
371 375 if ret == 0:
372 376 ret = signal.SIGTERM << 8
373 377 output += ("\n### Abort: timeout after %d seconds.\n"
374 378 % options.timeout)
375 379 return ret, splitnewlines(output)
376 380
377 381 def runone(options, test, skips, fails):
378 382 '''tristate output:
379 383 None -> skipped
380 384 True -> passed
381 385 False -> failed'''
382 386
383 387 def skip(msg):
384 388 if not options.verbose:
385 389 skips.append((test, msg))
386 390 else:
387 391 print "\nSkipping %s: %s" % (test, msg)
388 392 return None
389 393
390 394 def fail(msg):
391 395 fails.append((test, msg))
392 396 if not options.nodiff:
393 397 print "\nERROR: %s %s" % (test, msg)
394 398 return None
395 399
396 400 vlog("# Test", test)
397 401
398 402 # create a fresh hgrc
399 403 hgrc = file(HGRCPATH, 'w+')
400 404 hgrc.write('[ui]\n')
401 405 hgrc.write('slash = True\n')
402 406 hgrc.write('[defaults]\n')
403 407 hgrc.write('backout = -d "0 0"\n')
404 408 hgrc.write('commit = -d "0 0"\n')
405 409 hgrc.write('tag = -d "0 0"\n')
406 410 hgrc.close()
407 411
408 412 err = os.path.join(TESTDIR, test+".err")
409 413 ref = os.path.join(TESTDIR, test+".out")
410 414 testpath = os.path.join(TESTDIR, test)
411 415
412 416 if os.path.exists(err):
413 417 os.remove(err) # Remove any previous output files
414 418
415 419 # Make a tmp subdirectory to work in
416 420 tmpd = os.path.join(HGTMP, test)
417 421 os.mkdir(tmpd)
418 422 os.chdir(tmpd)
419 423
420 424 try:
421 425 tf = open(testpath)
422 426 firstline = tf.readline().rstrip()
423 427 tf.close()
424 428 except:
425 429 firstline = ''
426 430 lctest = test.lower()
427 431
428 432 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
429 433 cmd = '%s "%s"' % (PYTHON, testpath)
430 434 elif lctest.endswith('.bat'):
431 435 # do not run batch scripts on non-windows
432 436 if os.name != 'nt':
433 437 return skip("batch script")
434 438 # To reliably get the error code from batch files on WinXP,
435 439 # the "cmd /c call" prefix is needed. Grrr
436 440 cmd = 'cmd /c call "%s"' % testpath
437 441 else:
438 442 # do not run shell scripts on windows
439 443 if os.name == 'nt':
440 444 return skip("shell script")
441 445 # do not try to run non-executable programs
442 446 if not os.path.exists(testpath):
443 447 return fail("does not exist")
444 448 elif not os.access(testpath, os.X_OK):
445 449 return skip("not executable")
446 450 cmd = '"%s"' % testpath
447 451
448 452 if options.timeout > 0:
449 453 signal.alarm(options.timeout)
450 454
451 455 vlog("# Running", cmd)
452 456 ret, out = run(cmd, options)
453 457 vlog("# Ret was:", ret)
454 458
455 459 if options.timeout > 0:
456 460 signal.alarm(0)
457 461
458 462 mark = '.'
459 463
460 464 skipped = (ret == SKIPPED_STATUS)
461 465 # If reference output file exists, check test output against it
462 466 if os.path.exists(ref):
463 467 f = open(ref, "r")
464 468 refout = splitnewlines(f.read())
465 469 f.close()
466 470 else:
467 471 refout = []
468 472 if skipped:
469 473 mark = 's'
470 474 missing, failed = parsehghaveoutput(out)
471 475 if not missing:
472 476 missing = ['irrelevant']
473 477 if failed:
474 478 fail("hghave failed checking for %s" % failed[-1])
475 479 skipped = False
476 480 else:
477 481 skip(missing[-1])
478 482 elif out != refout:
479 483 mark = '!'
480 484 if ret:
481 485 fail("output changed and returned error code %d" % ret)
482 486 else:
483 487 fail("output changed")
484 488 if not options.nodiff:
485 489 showdiff(refout, out)
486 490 ret = 1
487 491 elif ret:
488 492 mark = '!'
489 493 fail("returned error code %d" % ret)
490 494
491 495 if not options.verbose:
492 496 sys.stdout.write(mark)
493 497 sys.stdout.flush()
494 498
495 499 if ret != 0 and not skipped:
496 500 # Save errors to a file for diagnosis
497 501 f = open(err, "wb")
498 502 for line in out:
499 503 f.write(line)
500 504 f.close()
501 505
502 506 # Kill off any leftover daemon processes
503 507 try:
504 508 fp = file(DAEMON_PIDS)
505 509 for line in fp:
506 510 try:
507 511 pid = int(line)
508 512 except ValueError:
509 513 continue
510 514 try:
511 515 os.kill(pid, 0)
512 516 vlog('# Killing daemon process %d' % pid)
513 517 os.kill(pid, signal.SIGTERM)
514 518 time.sleep(0.25)
515 519 os.kill(pid, 0)
516 520 vlog('# Daemon process %d is stuck - really killing it' % pid)
517 521 os.kill(pid, signal.SIGKILL)
518 522 except OSError, err:
519 523 if err.errno != errno.ESRCH:
520 524 raise
521 525 fp.close()
522 526 os.unlink(DAEMON_PIDS)
523 527 except IOError:
524 528 pass
525 529
526 530 os.chdir(TESTDIR)
527 531 if not options.keep_tmpdir:
528 532 shutil.rmtree(tmpd, True)
529 533 if skipped:
530 534 return None
531 535 return ret == 0
532 536
533 537 _hgpath = None
534 538
535 539 def _gethgpath():
536 540 """Return the path to the mercurial package that is actually found by
537 541 the current Python interpreter."""
538 542 global _hgpath
539 543 if _hgpath is not None:
540 544 return _hgpath
541 545
542 546 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
543 547 pipe = os.popen(cmd % PYTHON)
544 548 try:
545 549 _hgpath = pipe.read().strip()
546 550 finally:
547 551 pipe.close()
548 552 return _hgpath
549 553
550 554 def _checkhglib(verb):
551 555 """Ensure that the 'mercurial' package imported by python is
552 556 the one we expect it to be. If not, print a warning to stderr."""
553 557 expecthg = os.path.join(PYTHONDIR, 'mercurial')
554 558 actualhg = _gethgpath()
555 559 if actualhg != expecthg:
556 560 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
557 561 ' (expected %s)\n'
558 562 % (verb, actualhg, expecthg))
559 563
560 564 def runchildren(options, tests):
561 565 if INST:
562 566 installhg(options)
563 567 _checkhglib("Testing")
564 568
565 569 optcopy = dict(options.__dict__)
566 570 optcopy['jobs'] = 1
567 571 if optcopy['with_hg'] is None:
568 572 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
569 573 opts = []
570 574 for opt, value in optcopy.iteritems():
571 575 name = '--' + opt.replace('_', '-')
572 576 if value is True:
573 577 opts.append(name)
574 578 elif value is not None:
575 579 opts.append(name + '=' + str(value))
576 580
577 581 tests.reverse()
578 582 jobs = [[] for j in xrange(options.jobs)]
579 583 while tests:
580 584 for job in jobs:
581 585 if not tests: break
582 586 job.append(tests.pop())
583 587 fps = {}
584 588 for j, job in enumerate(jobs):
585 589 if not job:
586 590 continue
587 591 rfd, wfd = os.pipe()
588 592 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
589 593 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
590 594 vlog(' '.join(cmdline))
591 595 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
592 596 os.close(wfd)
593 597 failures = 0
594 598 tested, skipped, failed = 0, 0, 0
595 599 skips = []
596 600 fails = []
597 601 while fps:
598 602 pid, status = os.wait()
599 603 fp = fps.pop(pid)
600 604 l = fp.read().splitlines()
601 605 test, skip, fail = map(int, l[:3])
602 606 split = -fail or len(l)
603 607 for s in l[3:split]:
604 608 skips.append(s.split(" ", 1))
605 609 for s in l[split:]:
606 610 fails.append(s.split(" ", 1))
607 611 tested += test
608 612 skipped += skip
609 613 failed += fail
610 614 vlog('pid %d exited, status %d' % (pid, status))
611 615 failures |= status
612 616 print
613 617 for s in skips:
614 618 print "Skipped %s: %s" % (s[0], s[1])
615 619 for s in fails:
616 620 print "Failed %s: %s" % (s[0], s[1])
617 621
618 622 _checkhglib("Tested")
619 623 print "# Ran %d tests, %d skipped, %d failed." % (
620 624 tested, skipped, failed)
621 625 sys.exit(failures != 0)
622 626
623 627 def runtests(options, tests):
624 628 global DAEMON_PIDS, HGRCPATH
625 629 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
626 630 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
627 631
628 632 try:
629 633 if INST:
630 634 installhg(options)
631 635 _checkhglib("Testing")
632 636
633 637 if options.timeout > 0:
634 638 try:
635 639 signal.signal(signal.SIGALRM, alarmed)
636 640 vlog('# Running each test with %d second timeout' %
637 641 options.timeout)
638 642 except AttributeError:
639 643 print 'WARNING: cannot run tests with timeouts'
640 644 options.timeout = 0
641 645
642 646 tested = 0
643 647 failed = 0
644 648 skipped = 0
645 649
646 650 if options.restart:
647 651 orig = list(tests)
648 652 while tests:
649 653 if os.path.exists(tests[0] + ".err"):
650 654 break
651 655 tests.pop(0)
652 656 if not tests:
653 657 print "running all tests"
654 658 tests = orig
655 659
656 660 skips = []
657 661 fails = []
658 662 for test in tests:
659 663 if options.retest and not os.path.exists(test + ".err"):
660 664 skipped += 1
661 665 continue
662 666 ret = runone(options, test, skips, fails)
663 667 if ret is None:
664 668 skipped += 1
665 669 elif not ret:
666 670 if options.interactive:
667 671 print "Accept this change? [n] ",
668 672 answer = sys.stdin.readline().strip()
669 673 if answer.lower() in "y yes".split():
670 674 rename(test + ".err", test + ".out")
671 675 tested += 1
672 676 fails.pop()
673 677 continue
674 678 failed += 1
675 679 if options.first:
676 680 break
677 681 tested += 1
678 682
679 683 if options.child:
680 684 fp = os.fdopen(options.child, 'w')
681 685 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
682 686 for s in skips:
683 687 fp.write("%s %s\n" % s)
684 688 for s in fails:
685 689 fp.write("%s %s\n" % s)
686 690 fp.close()
687 691 else:
688 692 print
689 693 for s in skips:
690 694 print "Skipped %s: %s" % s
691 695 for s in fails:
692 696 print "Failed %s: %s" % s
693 697 _checkhglib("Tested")
694 698 print "# Ran %d tests, %d skipped, %d failed." % (
695 699 tested, skipped, failed)
696 700
697 701 if options.anycoverage:
698 702 outputcoverage(options)
699 703 except KeyboardInterrupt:
700 704 failed = True
701 705 print "\ninterrupted!"
702 706
703 707 if failed:
704 708 sys.exit(1)
705 709
706 710 def main():
707 711 (options, args) = parseargs()
708 712 if not options.child:
709 713 os.umask(022)
710 714
711 715 checktools()
712 716
713 717 # Reset some environment variables to well-known values so that
714 718 # the tests produce repeatable output.
715 719 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
716 720 os.environ['TZ'] = 'GMT'
717 721 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
718 722 os.environ['CDPATH'] = ''
719 723
720 724 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
721 725 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
722 726 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
723 727 options.tmpdir))
724 728 DAEMON_PIDS = None
725 729 HGRCPATH = None
726 730
727 731 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
728 732 os.environ["HGMERGE"] = "internal:merge"
729 733 os.environ["HGUSER"] = "test"
730 734 os.environ["HGENCODING"] = "ascii"
731 735 os.environ["HGENCODINGMODE"] = "strict"
732 736 os.environ["HGPORT"] = str(options.port)
733 737 os.environ["HGPORT1"] = str(options.port + 1)
734 738 os.environ["HGPORT2"] = str(options.port + 2)
735 739
736 740 if options.with_hg:
737 741 INST = None
738 742 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
739 743
740 744 # This looks redundant with how Python initializes sys.path from
741 745 # the location of the script being executed. Needed because the
742 746 # "hg" specified by --with-hg is not the only Python script
743 747 # executed in the test suite that needs to import 'mercurial'
744 748 # ... which means it's not really redundant at all.
745 749 PYTHONDIR = BINDIR
746 750 else:
747 751 INST = os.path.join(HGTMP, "install")
748 752 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
749 753 PYTHONDIR = os.path.join(INST, "lib", "python")
750 754
751 755 os.environ["BINDIR"] = BINDIR
752 756 os.environ["PYTHON"] = PYTHON
753 757
754 758 if not options.child:
755 759 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
756 760 os.environ["PATH"] = os.pathsep.join(path)
757 761
758 762 # We have to augment PYTHONPATH, rather than simply replacing
759 763 # it, in case external libraries are only available via current
760 764 # PYTHONPATH. (In particular, the Subversion bindings on OS X
761 765 # are in /opt/subversion.)
762 766 os.environ["PYTHONPATH"] = (PYTHONDIR + os.pathsep +
763 767 os.environ.get("PYTHONPATH", ""))
764 768
765 769 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
766 770
767 771 if len(args) == 0:
768 772 args = os.listdir(".")
769 773 args.sort()
770 774
771 775 tests = []
772 776 for test in args:
773 777 if (test.startswith("test-") and '~' not in test and
774 778 ('.' not in test or test.endswith('.py') or
775 779 test.endswith('.bat'))):
776 780 tests.append(test)
777 781 if not tests:
778 782 print "# Ran 0 tests, 0 skipped, 0 failed."
779 783 return
780 784
781 785 vlog("# Using TESTDIR", TESTDIR)
782 786 vlog("# Using HGTMP", HGTMP)
783 787 vlog("# Using PATH", os.environ["PATH"])
784 788 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
785 789
786 790 try:
787 791 if len(tests) > 1 and options.jobs > 1:
788 792 runchildren(options, tests)
789 793 else:
790 794 runtests(options, tests)
791 795 finally:
792 796 cleanup(options)
793 797
794 798 main()
General Comments 0
You need to be logged in to leave comments. Login now