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