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