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