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