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