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