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