##// END OF EJS Templates
run-tests: don't replace PYTHONPATH, just augment it....
Greg Ward -
r8687:78ab2a12 default
parent child Browse files
Show More
@@ -1,791 +1,794 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 covrun('-i', '-r', '"--omit=%s"' % omit) # report
335 335 if options.annotate:
336 336 adir = os.path.join(TESTDIR, 'annotated')
337 337 if not os.path.isdir(adir):
338 338 os.mkdir(adir)
339 339 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
340 340
341 341 class Timeout(Exception):
342 342 pass
343 343
344 344 def alarmed(signum, frame):
345 345 raise Timeout
346 346
347 347 def run(cmd, options):
348 348 """Run command in a sub-process, capturing the output (stdout and stderr).
349 349 Return the exist code, and output."""
350 350 # TODO: Use subprocess.Popen if we're running on Python 2.4
351 351 if os.name == 'nt' or sys.platform.startswith('java'):
352 352 tochild, fromchild = os.popen4(cmd)
353 353 tochild.close()
354 354 output = fromchild.read()
355 355 ret = fromchild.close()
356 356 if ret == None:
357 357 ret = 0
358 358 else:
359 359 proc = Popen4(cmd)
360 360 try:
361 361 output = ''
362 362 proc.tochild.close()
363 363 output = proc.fromchild.read()
364 364 ret = proc.wait()
365 365 if os.WIFEXITED(ret):
366 366 ret = os.WEXITSTATUS(ret)
367 367 except Timeout:
368 368 vlog('# Process %d timed out - killing it' % proc.pid)
369 369 os.kill(proc.pid, signal.SIGTERM)
370 370 ret = proc.wait()
371 371 if ret == 0:
372 372 ret = signal.SIGTERM << 8
373 373 output += ("\n### Abort: timeout after %d seconds.\n"
374 374 % options.timeout)
375 375 return ret, splitnewlines(output)
376 376
377 377 def runone(options, test, skips, fails):
378 378 '''tristate output:
379 379 None -> skipped
380 380 True -> passed
381 381 False -> failed'''
382 382
383 383 def skip(msg):
384 384 if not options.verbose:
385 385 skips.append((test, msg))
386 386 else:
387 387 print "\nSkipping %s: %s" % (test, msg)
388 388 return None
389 389
390 390 def fail(msg):
391 391 fails.append((test, msg))
392 392 if not options.nodiff:
393 393 print "\nERROR: %s %s" % (test, msg)
394 394 return None
395 395
396 396 vlog("# Test", test)
397 397
398 398 # create a fresh hgrc
399 399 hgrc = file(HGRCPATH, 'w+')
400 400 hgrc.write('[ui]\n')
401 401 hgrc.write('slash = True\n')
402 402 hgrc.write('[defaults]\n')
403 403 hgrc.write('backout = -d "0 0"\n')
404 404 hgrc.write('commit = -d "0 0"\n')
405 405 hgrc.write('tag = -d "0 0"\n')
406 406 hgrc.close()
407 407
408 408 err = os.path.join(TESTDIR, test+".err")
409 409 ref = os.path.join(TESTDIR, test+".out")
410 410 testpath = os.path.join(TESTDIR, test)
411 411
412 412 if os.path.exists(err):
413 413 os.remove(err) # Remove any previous output files
414 414
415 415 # Make a tmp subdirectory to work in
416 416 tmpd = os.path.join(HGTMP, test)
417 417 os.mkdir(tmpd)
418 418 os.chdir(tmpd)
419 419
420 420 try:
421 421 tf = open(testpath)
422 422 firstline = tf.readline().rstrip()
423 423 tf.close()
424 424 except:
425 425 firstline = ''
426 426 lctest = test.lower()
427 427
428 428 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
429 429 cmd = '%s "%s"' % (PYTHON, testpath)
430 430 elif lctest.endswith('.bat'):
431 431 # do not run batch scripts on non-windows
432 432 if os.name != 'nt':
433 433 return skip("batch script")
434 434 # To reliably get the error code from batch files on WinXP,
435 435 # the "cmd /c call" prefix is needed. Grrr
436 436 cmd = 'cmd /c call "%s"' % testpath
437 437 else:
438 438 # do not run shell scripts on windows
439 439 if os.name == 'nt':
440 440 return skip("shell script")
441 441 # do not try to run non-executable programs
442 442 if not os.path.exists(testpath):
443 443 return fail("does not exist")
444 444 elif not os.access(testpath, os.X_OK):
445 445 return skip("not executable")
446 446 cmd = '"%s"' % testpath
447 447
448 448 if options.timeout > 0:
449 449 signal.alarm(options.timeout)
450 450
451 451 vlog("# Running", cmd)
452 452 ret, out = run(cmd, options)
453 453 vlog("# Ret was:", ret)
454 454
455 455 if options.timeout > 0:
456 456 signal.alarm(0)
457 457
458 458 mark = '.'
459 459
460 460 skipped = (ret == SKIPPED_STATUS)
461 461 # If reference output file exists, check test output against it
462 462 if os.path.exists(ref):
463 463 f = open(ref, "r")
464 464 refout = splitnewlines(f.read())
465 465 f.close()
466 466 else:
467 467 refout = []
468 468 if skipped:
469 469 mark = 's'
470 470 missing, failed = parsehghaveoutput(out)
471 471 if not missing:
472 472 missing = ['irrelevant']
473 473 if failed:
474 474 fail("hghave failed checking for %s" % failed[-1])
475 475 skipped = False
476 476 else:
477 477 skip(missing[-1])
478 478 elif out != refout:
479 479 mark = '!'
480 480 if ret:
481 481 fail("output changed and returned error code %d" % ret)
482 482 else:
483 483 fail("output changed")
484 484 if not options.nodiff:
485 485 showdiff(refout, out)
486 486 ret = 1
487 487 elif ret:
488 488 mark = '!'
489 489 fail("returned error code %d" % ret)
490 490
491 491 if not options.verbose:
492 492 sys.stdout.write(mark)
493 493 sys.stdout.flush()
494 494
495 495 if ret != 0 and not skipped:
496 496 # Save errors to a file for diagnosis
497 497 f = open(err, "wb")
498 498 for line in out:
499 499 f.write(line)
500 500 f.close()
501 501
502 502 # Kill off any leftover daemon processes
503 503 try:
504 504 fp = file(DAEMON_PIDS)
505 505 for line in fp:
506 506 try:
507 507 pid = int(line)
508 508 except ValueError:
509 509 continue
510 510 try:
511 511 os.kill(pid, 0)
512 512 vlog('# Killing daemon process %d' % pid)
513 513 os.kill(pid, signal.SIGTERM)
514 514 time.sleep(0.25)
515 515 os.kill(pid, 0)
516 516 vlog('# Daemon process %d is stuck - really killing it' % pid)
517 517 os.kill(pid, signal.SIGKILL)
518 518 except OSError, err:
519 519 if err.errno != errno.ESRCH:
520 520 raise
521 521 fp.close()
522 522 os.unlink(DAEMON_PIDS)
523 523 except IOError:
524 524 pass
525 525
526 526 os.chdir(TESTDIR)
527 527 if not options.keep_tmpdir:
528 528 shutil.rmtree(tmpd, True)
529 529 if skipped:
530 530 return None
531 531 return ret == 0
532 532
533 533 _hgpath = None
534 534
535 535 def _gethgpath():
536 536 """Return the path to the mercurial package that is actually found by
537 537 the current Python interpreter."""
538 538 global _hgpath
539 539 if _hgpath is not None:
540 540 return _hgpath
541 541
542 542 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
543 543 pipe = os.popen(cmd % PYTHON)
544 544 try:
545 545 _hgpath = pipe.read().strip()
546 546 finally:
547 547 pipe.close()
548 548 return _hgpath
549 549
550 550 def _checkhglib(verb):
551 551 """Ensure that the 'mercurial' package imported by python is
552 552 the one we expect it to be. If not, print a warning to stderr."""
553 553 expecthg = os.path.join(PYTHONDIR, 'mercurial')
554 554 actualhg = _gethgpath()
555 555 if actualhg != expecthg:
556 556 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
557 557 ' (expected %s)\n'
558 558 % (verb, actualhg, expecthg))
559 559
560 560 def runchildren(options, tests):
561 561 if INST:
562 562 installhg(options)
563 563 _checkhglib("Testing")
564 564
565 565 optcopy = dict(options.__dict__)
566 566 optcopy['jobs'] = 1
567 567 if optcopy['with_hg'] is None:
568 568 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
569 569 opts = []
570 570 for opt, value in optcopy.iteritems():
571 571 name = '--' + opt.replace('_', '-')
572 572 if value is True:
573 573 opts.append(name)
574 574 elif value is not None:
575 575 opts.append(name + '=' + str(value))
576 576
577 577 tests.reverse()
578 578 jobs = [[] for j in xrange(options.jobs)]
579 579 while tests:
580 580 for job in jobs:
581 581 if not tests: break
582 582 job.append(tests.pop())
583 583 fps = {}
584 584 for j, job in enumerate(jobs):
585 585 if not job:
586 586 continue
587 587 rfd, wfd = os.pipe()
588 588 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
589 589 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
590 590 vlog(' '.join(cmdline))
591 591 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
592 592 os.close(wfd)
593 593 failures = 0
594 594 tested, skipped, failed = 0, 0, 0
595 595 skips = []
596 596 fails = []
597 597 while fps:
598 598 pid, status = os.wait()
599 599 fp = fps.pop(pid)
600 600 l = fp.read().splitlines()
601 601 test, skip, fail = map(int, l[:3])
602 602 split = -fail or len(l)
603 603 for s in l[3:split]:
604 604 skips.append(s.split(" ", 1))
605 605 for s in l[split:]:
606 606 fails.append(s.split(" ", 1))
607 607 tested += test
608 608 skipped += skip
609 609 failed += fail
610 610 vlog('pid %d exited, status %d' % (pid, status))
611 611 failures |= status
612 612 print
613 613 for s in skips:
614 614 print "Skipped %s: %s" % (s[0], s[1])
615 615 for s in fails:
616 616 print "Failed %s: %s" % (s[0], s[1])
617 617
618 618 _checkhglib("Tested")
619 619 print "# Ran %d tests, %d skipped, %d failed." % (
620 620 tested, skipped, failed)
621 621 sys.exit(failures != 0)
622 622
623 623 def runtests(options, tests):
624 624 global DAEMON_PIDS, HGRCPATH
625 625 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
626 626 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
627 627
628 628 try:
629 629 if INST:
630 630 installhg(options)
631 631 _checkhglib("Testing")
632 632
633 633 if options.timeout > 0:
634 634 try:
635 635 signal.signal(signal.SIGALRM, alarmed)
636 636 vlog('# Running each test with %d second timeout' %
637 637 options.timeout)
638 638 except AttributeError:
639 639 print 'WARNING: cannot run tests with timeouts'
640 640 options.timeout = 0
641 641
642 642 tested = 0
643 643 failed = 0
644 644 skipped = 0
645 645
646 646 if options.restart:
647 647 orig = list(tests)
648 648 while tests:
649 649 if os.path.exists(tests[0] + ".err"):
650 650 break
651 651 tests.pop(0)
652 652 if not tests:
653 653 print "running all tests"
654 654 tests = orig
655 655
656 656 skips = []
657 657 fails = []
658 658 for test in tests:
659 659 if options.retest and not os.path.exists(test + ".err"):
660 660 skipped += 1
661 661 continue
662 662 ret = runone(options, test, skips, fails)
663 663 if ret is None:
664 664 skipped += 1
665 665 elif not ret:
666 666 if options.interactive:
667 667 print "Accept this change? [n] ",
668 668 answer = sys.stdin.readline().strip()
669 669 if answer.lower() in "y yes".split():
670 670 rename(test + ".err", test + ".out")
671 671 tested += 1
672 672 fails.pop()
673 673 continue
674 674 failed += 1
675 675 if options.first:
676 676 break
677 677 tested += 1
678 678
679 679 if options.child:
680 680 fp = os.fdopen(options.child, 'w')
681 681 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
682 682 for s in skips:
683 683 fp.write("%s %s\n" % s)
684 684 for s in fails:
685 685 fp.write("%s %s\n" % s)
686 686 fp.close()
687 687 else:
688 688 print
689 689 for s in skips:
690 690 print "Skipped %s: %s" % s
691 691 for s in fails:
692 692 print "Failed %s: %s" % s
693 693 _checkhglib("Tested")
694 694 print "# Ran %d tests, %d skipped, %d failed." % (
695 695 tested, skipped, failed)
696 696
697 697 if options.anycoverage:
698 698 outputcoverage(options)
699 699 except KeyboardInterrupt:
700 700 failed = True
701 701 print "\ninterrupted!"
702 702
703 703 if failed:
704 704 sys.exit(1)
705 705
706 706 def main():
707 707 (options, args) = parseargs()
708 708 if not options.child:
709 709 os.umask(022)
710 710
711 711 checktools()
712 712
713 713 # Reset some environment variables to well-known values so that
714 714 # the tests produce repeatable output.
715 715 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
716 716 os.environ['TZ'] = 'GMT'
717 717 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
718 718 os.environ['CDPATH'] = ''
719 719
720 720 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
721 721 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
722 722 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
723 723 options.tmpdir))
724 724 DAEMON_PIDS = None
725 725 HGRCPATH = None
726 726
727 727 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
728 728 os.environ["HGMERGE"] = "internal:merge"
729 729 os.environ["HGUSER"] = "test"
730 730 os.environ["HGENCODING"] = "ascii"
731 731 os.environ["HGENCODINGMODE"] = "strict"
732 732 os.environ["HGPORT"] = str(options.port)
733 733 os.environ["HGPORT1"] = str(options.port + 1)
734 734 os.environ["HGPORT2"] = str(options.port + 2)
735 735
736 736 if options.with_hg:
737 737 INST = None
738 738 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
739 739
740 740 # This looks redundant with how Python initializes sys.path from
741 741 # the location of the script being executed. Needed because the
742 742 # "hg" specified by --with-hg is not the only Python script
743 743 # executed in the test suite that needs to import 'mercurial'
744 744 # ... which means it's not really redundant at all.
745 745 PYTHONDIR = BINDIR
746 746 else:
747 747 INST = os.path.join(HGTMP, "install")
748 748 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
749 749 PYTHONDIR = os.path.join(INST, "lib", "python")
750 750
751 751 os.environ["BINDIR"] = BINDIR
752 752 os.environ["PYTHON"] = PYTHON
753 753
754 754 if not options.child:
755 755 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
756 756 os.environ["PATH"] = os.pathsep.join(path)
757 757
758 # Deliberately override existing PYTHONPATH: do not want success
759 # to depend on what happens to be in caller's environment.
760 os.environ["PYTHONPATH"] = PYTHONDIR
758 # We have to augment PYTHONPATH, rather than simply replacing
759 # it, in case external libraries are only available via current
760 # PYTHONPATH. (In particular, the Subversion bindings on OS X
761 # are in /opt/subversion.)
762 os.environ["PYTHONPATH"] = (PYTHONDIR + os.pathsep +
763 os.environ.get("PYTHONPATH", ""))
761 764
762 765 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
763 766
764 767 if len(args) == 0:
765 768 args = os.listdir(".")
766 769 args.sort()
767 770
768 771 tests = []
769 772 for test in args:
770 773 if (test.startswith("test-") and '~' not in test and
771 774 ('.' not in test or test.endswith('.py') or
772 775 test.endswith('.bat'))):
773 776 tests.append(test)
774 777 if not tests:
775 778 print "# Ran 0 tests, 0 skipped, 0 failed."
776 779 return
777 780
778 781 vlog("# Using TESTDIR", TESTDIR)
779 782 vlog("# Using HGTMP", HGTMP)
780 783 vlog("# Using PATH", os.environ["PATH"])
781 784 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
782 785
783 786 try:
784 787 if len(tests) > 1 and options.jobs > 1:
785 788 runchildren(options, tests)
786 789 else:
787 790 runtests(options, tests)
788 791 finally:
789 792 cleanup(options)
790 793
791 794 main()
General Comments 0
You need to be logged in to leave comments. Login now