##// END OF EJS Templates
run-tests: add abort flag...
Matt Mackall -
r19273:f3effc49 default
parent child Browse files
Show More
@@ -1,1354 +1,1358 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 shutil
50 50 import subprocess
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55 import random
56 56 import re
57 57 import threading
58 58 import killdaemons as killmod
59 59 import cPickle as pickle
60 60 import Queue as queue
61 61
62 62 processlock = threading.Lock()
63 63
64 64 closefds = os.name == 'posix'
65 65 def Popen4(cmd, wd, timeout, env=None):
66 66 processlock.acquire()
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.STDOUT)
71 71 processlock.release()
72 72
73 73 p.fromchild = p.stdout
74 74 p.tochild = p.stdin
75 75 p.childerr = p.stderr
76 76
77 77 p.timeout = False
78 78 if timeout:
79 79 def t():
80 80 start = time.time()
81 81 while time.time() - start < timeout and p.returncode is None:
82 82 time.sleep(.1)
83 83 p.timeout = True
84 84 if p.returncode is None:
85 85 terminate(p)
86 86 threading.Thread(target=t).start()
87 87
88 88 return p
89 89
90 90 # reserved exit code to skip test (used by hghave)
91 91 SKIPPED_STATUS = 80
92 92 SKIPPED_PREFIX = 'skipped: '
93 93 FAILED_PREFIX = 'hghave check failed: '
94 94 PYTHON = sys.executable.replace('\\', '/')
95 95 IMPL_PATH = 'PYTHONPATH'
96 96 if 'java' in sys.platform:
97 97 IMPL_PATH = 'JYTHONPATH'
98 98
99 99 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
100 100 "gunzip", "bunzip2", "sed"]
101 101
102 102 defaults = {
103 103 'jobs': ('HGTEST_JOBS', 1),
104 104 'timeout': ('HGTEST_TIMEOUT', 180),
105 105 'port': ('HGTEST_PORT', 20059),
106 106 'shell': ('HGTEST_SHELL', 'sh'),
107 107 }
108 108
109 109 def parselistfiles(files, listtype, warn=True):
110 110 entries = dict()
111 111 for filename in files:
112 112 try:
113 113 path = os.path.expanduser(os.path.expandvars(filename))
114 114 f = open(path, "r")
115 115 except IOError, err:
116 116 if err.errno != errno.ENOENT:
117 117 raise
118 118 if warn:
119 119 print "warning: no such %s file: %s" % (listtype, filename)
120 120 continue
121 121
122 122 for line in f.readlines():
123 123 line = line.split('#', 1)[0].strip()
124 124 if line:
125 125 entries[line] = filename
126 126
127 127 f.close()
128 128 return entries
129 129
130 130 def parseargs():
131 131 parser = optparse.OptionParser("%prog [options] [tests]")
132 132
133 133 # keep these sorted
134 134 parser.add_option("--blacklist", action="append",
135 135 help="skip tests listed in the specified blacklist file")
136 136 parser.add_option("--whitelist", action="append",
137 137 help="always run tests listed in the specified whitelist file")
138 138 parser.add_option("-C", "--annotate", action="store_true",
139 139 help="output files annotated with coverage")
140 140 parser.add_option("--child", type="int",
141 141 help="run as child process, summary to given fd")
142 142 parser.add_option("-c", "--cover", action="store_true",
143 143 help="print a test coverage report")
144 144 parser.add_option("-d", "--debug", action="store_true",
145 145 help="debug mode: write output of test scripts to console"
146 146 " rather than capturing and diff'ing it (disables timeout)")
147 147 parser.add_option("-f", "--first", action="store_true",
148 148 help="exit on the first test failure")
149 149 parser.add_option("-H", "--htmlcov", action="store_true",
150 150 help="create an HTML report of the coverage of the files")
151 151 parser.add_option("--inotify", action="store_true",
152 152 help="enable inotify extension when running tests")
153 153 parser.add_option("-i", "--interactive", action="store_true",
154 154 help="prompt to accept changed output")
155 155 parser.add_option("-j", "--jobs", type="int",
156 156 help="number of jobs to run in parallel"
157 157 " (default: $%s or %d)" % defaults['jobs'])
158 158 parser.add_option("--keep-tmpdir", action="store_true",
159 159 help="keep temporary directory after running tests")
160 160 parser.add_option("-k", "--keywords",
161 161 help="run tests matching keywords")
162 162 parser.add_option("-l", "--local", action="store_true",
163 163 help="shortcut for --with-hg=<testdir>/../hg")
164 164 parser.add_option("-n", "--nodiff", action="store_true",
165 165 help="skip showing test changes")
166 166 parser.add_option("-p", "--port", type="int",
167 167 help="port on which servers should listen"
168 168 " (default: $%s or %d)" % defaults['port'])
169 169 parser.add_option("--compiler", type="string",
170 170 help="compiler to build with")
171 171 parser.add_option("--pure", action="store_true",
172 172 help="use pure Python code instead of C extensions")
173 173 parser.add_option("-R", "--restart", action="store_true",
174 174 help="restart at last error")
175 175 parser.add_option("-r", "--retest", action="store_true",
176 176 help="retest failed tests")
177 177 parser.add_option("-S", "--noskips", action="store_true",
178 178 help="don't report skip tests verbosely")
179 179 parser.add_option("--shell", type="string",
180 180 help="shell to use (default: $%s or %s)" % defaults['shell'])
181 181 parser.add_option("-t", "--timeout", type="int",
182 182 help="kill errant tests after TIMEOUT seconds"
183 183 " (default: $%s or %d)" % defaults['timeout'])
184 184 parser.add_option("--time", action="store_true",
185 185 help="time how long each test takes")
186 186 parser.add_option("--tmpdir", type="string",
187 187 help="run tests in the given temporary directory"
188 188 " (implies --keep-tmpdir)")
189 189 parser.add_option("-v", "--verbose", action="store_true",
190 190 help="output verbose messages")
191 191 parser.add_option("--view", type="string",
192 192 help="external diff viewer")
193 193 parser.add_option("--with-hg", type="string",
194 194 metavar="HG",
195 195 help="test using specified hg script rather than a "
196 196 "temporary installation")
197 197 parser.add_option("-3", "--py3k-warnings", action="store_true",
198 198 help="enable Py3k warnings on Python 2.6+")
199 199 parser.add_option('--extra-config-opt', action="append",
200 200 help='set the given config opt in the test hgrc')
201 201 parser.add_option('--random', action="store_true",
202 202 help='run tests in random order')
203 203
204 204 for option, (envvar, default) in defaults.items():
205 205 defaults[option] = type(default)(os.environ.get(envvar, default))
206 206 parser.set_defaults(**defaults)
207 207 (options, args) = parser.parse_args()
208 208
209 209 # jython is always pure
210 210 if 'java' in sys.platform or '__pypy__' in sys.modules:
211 211 options.pure = True
212 212
213 213 if options.with_hg:
214 214 options.with_hg = os.path.expanduser(options.with_hg)
215 215 if not (os.path.isfile(options.with_hg) and
216 216 os.access(options.with_hg, os.X_OK)):
217 217 parser.error('--with-hg must specify an executable hg script')
218 218 if not os.path.basename(options.with_hg) == 'hg':
219 219 sys.stderr.write('warning: --with-hg should specify an hg script\n')
220 220 if options.local:
221 221 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
222 222 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
223 223 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
224 224 parser.error('--local specified, but %r not found or not executable'
225 225 % hgbin)
226 226 options.with_hg = hgbin
227 227
228 228 options.anycoverage = options.cover or options.annotate or options.htmlcov
229 229 if options.anycoverage:
230 230 try:
231 231 import coverage
232 232 covver = version.StrictVersion(coverage.__version__).version
233 233 if covver < (3, 3):
234 234 parser.error('coverage options require coverage 3.3 or later')
235 235 except ImportError:
236 236 parser.error('coverage options now require the coverage package')
237 237
238 238 if options.anycoverage and options.local:
239 239 # this needs some path mangling somewhere, I guess
240 240 parser.error("sorry, coverage options do not work when --local "
241 241 "is specified")
242 242
243 243 global verbose
244 244 if options.verbose:
245 245 if options.jobs > 1 or options.child is not None:
246 246 verbose = "[%d]" % os.getpid()
247 247 else:
248 248 verbose = ''
249 249
250 250 if options.tmpdir:
251 251 options.tmpdir = os.path.expanduser(options.tmpdir)
252 252
253 253 if options.jobs < 1:
254 254 parser.error('--jobs must be positive')
255 255 if options.interactive and options.jobs > 1:
256 256 print '(--interactive overrides --jobs)'
257 257 options.jobs = 1
258 258 if options.interactive and options.debug:
259 259 parser.error("-i/--interactive and -d/--debug are incompatible")
260 260 if options.debug:
261 261 if options.timeout != defaults['timeout']:
262 262 sys.stderr.write(
263 263 'warning: --timeout option ignored with --debug\n')
264 264 options.timeout = 0
265 265 if options.time:
266 266 sys.stderr.write(
267 267 'warning: --time option ignored with --debug\n')
268 268 options.time = False
269 269 if options.py3k_warnings:
270 270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
271 271 parser.error('--py3k-warnings can only be used on Python 2.6+')
272 272 if options.blacklist:
273 273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
274 274 if options.whitelist:
275 275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
276 276 warn=options.child is None)
277 277 else:
278 278 options.whitelisted = {}
279 279
280 280 return (options, args)
281 281
282 282 def rename(src, dst):
283 283 """Like os.rename(), trade atomicity and opened files friendliness
284 284 for existing destination support.
285 285 """
286 286 shutil.copy(src, dst)
287 287 os.remove(src)
288 288
289 289 def parsehghaveoutput(lines):
290 290 '''Parse hghave log lines.
291 291 Return tuple of lists (missing, failed):
292 292 * the missing/unknown features
293 293 * the features for which existence check failed'''
294 294 missing = []
295 295 failed = []
296 296 for line in lines:
297 297 if line.startswith(SKIPPED_PREFIX):
298 298 line = line.splitlines()[0]
299 299 missing.append(line[len(SKIPPED_PREFIX):])
300 300 elif line.startswith(FAILED_PREFIX):
301 301 line = line.splitlines()[0]
302 302 failed.append(line[len(FAILED_PREFIX):])
303 303
304 304 return missing, failed
305 305
306 306 def showdiff(expected, output, ref, err):
307 307 print
308 308 for line in difflib.unified_diff(expected, output, ref, err):
309 309 sys.stdout.write(line)
310 310
311 311 verbose = False
312 312 def vlog(*msg):
313 313 if verbose is not False:
314 314 iolock.acquire()
315 315 if verbose:
316 316 print verbose,
317 317 for m in msg:
318 318 print m,
319 319 print
320 320 sys.stdout.flush()
321 321 iolock.release()
322 322
323 323 def log(*msg):
324 324 iolock.acquire()
325 325 if verbose:
326 326 print verbose,
327 327 for m in msg:
328 328 print m,
329 329 print
330 330 sys.stdout.flush()
331 331 iolock.release()
332 332
333 333 def findprogram(program):
334 334 """Search PATH for a executable program"""
335 335 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
336 336 name = os.path.join(p, program)
337 337 if os.name == 'nt' or os.access(name, os.X_OK):
338 338 return name
339 339 return None
340 340
341 341 def createhgrc(path, options):
342 342 # create a fresh hgrc
343 343 hgrc = open(path, 'w+')
344 344 hgrc.write('[ui]\n')
345 345 hgrc.write('slash = True\n')
346 346 hgrc.write('interactive = False\n')
347 347 hgrc.write('[defaults]\n')
348 348 hgrc.write('backout = -d "0 0"\n')
349 349 hgrc.write('commit = -d "0 0"\n')
350 350 hgrc.write('tag = -d "0 0"\n')
351 351 if options.inotify:
352 352 hgrc.write('[extensions]\n')
353 353 hgrc.write('inotify=\n')
354 354 hgrc.write('[inotify]\n')
355 355 hgrc.write('pidfile=daemon.pids')
356 356 hgrc.write('appendpid=True\n')
357 357 if options.extra_config_opt:
358 358 for opt in options.extra_config_opt:
359 359 section, key = opt.split('.', 1)
360 360 assert '=' in key, ('extra config opt %s must '
361 361 'have an = for assignment' % opt)
362 362 hgrc.write('[%s]\n%s\n' % (section, key))
363 363 hgrc.close()
364 364
365 365 def createenv(options, testtmp):
366 366 env = os.environ.copy()
367 367 env['TESTTMP'] = testtmp
368 368 env['HOME'] = testtmp
369 369 env["HGPORT"] = str(options.port)
370 370 env["HGPORT1"] = str(options.port + 1)
371 371 env["HGPORT2"] = str(options.port + 2)
372 372 env["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
373 373 env["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
374 374 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
375 375 env["HGMERGE"] = "internal:merge"
376 376 env["HGUSER"] = "test"
377 377 env["HGENCODING"] = "ascii"
378 378 env["HGENCODINGMODE"] = "strict"
379 379
380 380 # Reset some environment variables to well-known values so that
381 381 # the tests produce repeatable output.
382 382 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
383 383 env['TZ'] = 'GMT'
384 384 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
385 385 env['COLUMNS'] = '80'
386 386 env['TERM'] = 'xterm'
387 387
388 388 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
389 389 'NO_PROXY').split():
390 390 if k in env:
391 391 del env[k]
392 392
393 393 # unset env related to hooks
394 394 for k in env.keys():
395 395 if k.startswith('HG_'):
396 396 del env[k]
397 397
398 398 return env
399 399
400 400 def checktools():
401 401 # Before we go any further, check for pre-requisite tools
402 402 # stuff from coreutils (cat, rm, etc) are not tested
403 403 for p in requiredtools:
404 404 if os.name == 'nt' and not p.endswith('.exe'):
405 405 p += '.exe'
406 406 found = findprogram(p)
407 407 if found:
408 408 vlog("# Found prerequisite", p, "at", found)
409 409 else:
410 410 print "WARNING: Did not find prerequisite tool: "+p
411 411
412 412 def terminate(proc):
413 413 """Terminate subprocess (with fallback for Python versions < 2.6)"""
414 414 vlog('# Terminating process %d' % proc.pid)
415 415 try:
416 416 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
417 417 except OSError:
418 418 pass
419 419
420 420 def killdaemons(pidfile):
421 421 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
422 422 logfn=vlog)
423 423
424 424 def cleanup(options):
425 425 if not options.keep_tmpdir:
426 426 vlog("# Cleaning up HGTMP", HGTMP)
427 427 shutil.rmtree(HGTMP, True)
428 428
429 429 def usecorrectpython():
430 430 # some tests run python interpreter. they must use same
431 431 # interpreter we use or bad things will happen.
432 432 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
433 433 if getattr(os, 'symlink', None):
434 434 vlog("# Making python executable in test path a symlink to '%s'" %
435 435 sys.executable)
436 436 mypython = os.path.join(BINDIR, pyexename)
437 437 try:
438 438 if os.readlink(mypython) == sys.executable:
439 439 return
440 440 os.unlink(mypython)
441 441 except OSError, err:
442 442 if err.errno != errno.ENOENT:
443 443 raise
444 444 if findprogram(pyexename) != sys.executable:
445 445 try:
446 446 os.symlink(sys.executable, mypython)
447 447 except OSError, err:
448 448 # child processes may race, which is harmless
449 449 if err.errno != errno.EEXIST:
450 450 raise
451 451 else:
452 452 exedir, exename = os.path.split(sys.executable)
453 453 vlog("# Modifying search path to find %s as %s in '%s'" %
454 454 (exename, pyexename, exedir))
455 455 path = os.environ['PATH'].split(os.pathsep)
456 456 while exedir in path:
457 457 path.remove(exedir)
458 458 os.environ['PATH'] = os.pathsep.join([exedir] + path)
459 459 if not findprogram(pyexename):
460 460 print "WARNING: Cannot find %s in search path" % pyexename
461 461
462 462 def installhg(options):
463 463 vlog("# Performing temporary installation of HG")
464 464 installerrs = os.path.join("tests", "install.err")
465 465 compiler = ''
466 466 if options.compiler:
467 467 compiler = '--compiler ' + options.compiler
468 468 pure = options.pure and "--pure" or ""
469 469
470 470 # Run installer in hg root
471 471 script = os.path.realpath(sys.argv[0])
472 472 hgroot = os.path.dirname(os.path.dirname(script))
473 473 os.chdir(hgroot)
474 474 nohome = '--home=""'
475 475 if os.name == 'nt':
476 476 # The --home="" trick works only on OS where os.sep == '/'
477 477 # because of a distutils convert_path() fast-path. Avoid it at
478 478 # least on Windows for now, deal with .pydistutils.cfg bugs
479 479 # when they happen.
480 480 nohome = ''
481 481 cmd = ('%(exe)s setup.py %(pure)s clean --all'
482 482 ' build %(compiler)s --build-base="%(base)s"'
483 483 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
484 484 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
485 485 % dict(exe=sys.executable, pure=pure, compiler=compiler,
486 486 base=os.path.join(HGTMP, "build"),
487 487 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
488 488 nohome=nohome, logfile=installerrs))
489 489 vlog("# Running", cmd)
490 490 if os.system(cmd) == 0:
491 491 if not options.verbose:
492 492 os.remove(installerrs)
493 493 else:
494 494 f = open(installerrs)
495 495 for line in f:
496 496 print line,
497 497 f.close()
498 498 sys.exit(1)
499 499 os.chdir(TESTDIR)
500 500
501 501 usecorrectpython()
502 502
503 503 vlog("# Installing dummy diffstat")
504 504 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
505 505 f.write('#!' + sys.executable + '\n'
506 506 'import sys\n'
507 507 'files = 0\n'
508 508 'for line in sys.stdin:\n'
509 509 ' if line.startswith("diff "):\n'
510 510 ' files += 1\n'
511 511 'sys.stdout.write("files patched: %d\\n" % files)\n')
512 512 f.close()
513 513 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
514 514
515 515 if options.py3k_warnings and not options.anycoverage:
516 516 vlog("# Updating hg command to enable Py3k Warnings switch")
517 517 f = open(os.path.join(BINDIR, 'hg'), 'r')
518 518 lines = [line.rstrip() for line in f]
519 519 lines[0] += ' -3'
520 520 f.close()
521 521 f = open(os.path.join(BINDIR, 'hg'), 'w')
522 522 for line in lines:
523 523 f.write(line + '\n')
524 524 f.close()
525 525
526 526 hgbat = os.path.join(BINDIR, 'hg.bat')
527 527 if os.path.isfile(hgbat):
528 528 # hg.bat expects to be put in bin/scripts while run-tests.py
529 529 # installation layout put it in bin/ directly. Fix it
530 530 f = open(hgbat, 'rb')
531 531 data = f.read()
532 532 f.close()
533 533 if '"%~dp0..\python" "%~dp0hg" %*' in data:
534 534 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
535 535 '"%~dp0python" "%~dp0hg" %*')
536 536 f = open(hgbat, 'wb')
537 537 f.write(data)
538 538 f.close()
539 539 else:
540 540 print 'WARNING: cannot fix hg.bat reference to python.exe'
541 541
542 542 if options.anycoverage:
543 543 custom = os.path.join(TESTDIR, 'sitecustomize.py')
544 544 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
545 545 vlog('# Installing coverage trigger to %s' % target)
546 546 shutil.copyfile(custom, target)
547 547 rc = os.path.join(TESTDIR, '.coveragerc')
548 548 vlog('# Installing coverage rc to %s' % rc)
549 549 os.environ['COVERAGE_PROCESS_START'] = rc
550 550 fn = os.path.join(INST, '..', '.coverage')
551 551 os.environ['COVERAGE_FILE'] = fn
552 552
553 553 def outputtimes(options):
554 554 vlog('# Producing time report')
555 555 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
556 556 cols = '%7.3f %s'
557 557 print '\n%-7s %s' % ('Time', 'Test')
558 558 for test, timetaken in times:
559 559 print cols % (timetaken, test)
560 560
561 561 def outputcoverage(options):
562 562
563 563 vlog('# Producing coverage report')
564 564 os.chdir(PYTHONDIR)
565 565
566 566 def covrun(*args):
567 567 cmd = 'coverage %s' % ' '.join(args)
568 568 vlog('# Running: %s' % cmd)
569 569 os.system(cmd)
570 570
571 571 if options.child:
572 572 return
573 573
574 574 covrun('-c')
575 575 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
576 576 covrun('-i', '-r', '"--omit=%s"' % omit) # report
577 577 if options.htmlcov:
578 578 htmldir = os.path.join(TESTDIR, 'htmlcov')
579 579 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
580 580 if options.annotate:
581 581 adir = os.path.join(TESTDIR, 'annotated')
582 582 if not os.path.isdir(adir):
583 583 os.mkdir(adir)
584 584 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
585 585
586 586 def pytest(test, wd, options, replacements, env):
587 587 py3kswitch = options.py3k_warnings and ' -3' or ''
588 588 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
589 589 vlog("# Running", cmd)
590 590 if os.name == 'nt':
591 591 replacements.append((r'\r\n', '\n'))
592 592 return run(cmd, wd, options, replacements, env)
593 593
594 594 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
595 595 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
596 596 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
597 597 escapemap.update({'\\': '\\\\', '\r': r'\r'})
598 598 def escapef(m):
599 599 return escapemap[m.group(0)]
600 600 def stringescape(s):
601 601 return escapesub(escapef, s)
602 602
603 603 def rematch(el, l):
604 604 try:
605 605 # use \Z to ensure that the regex matches to the end of the string
606 606 if os.name == 'nt':
607 607 return re.match(el + r'\r?\n\Z', l)
608 608 return re.match(el + r'\n\Z', l)
609 609 except re.error:
610 610 # el is an invalid regex
611 611 return False
612 612
613 613 def globmatch(el, l):
614 614 # The only supported special characters are * and ? plus / which also
615 615 # matches \ on windows. Escaping of these caracters is supported.
616 616 if el + '\n' == l:
617 617 if os.name == 'nt':
618 618 # matching on "/" is not needed for this line
619 619 log("\nInfo, unnecessary glob: %s (glob)" % el)
620 620 return True
621 621 i, n = 0, len(el)
622 622 res = ''
623 623 while i < n:
624 624 c = el[i]
625 625 i += 1
626 626 if c == '\\' and el[i] in '*?\\/':
627 627 res += el[i - 1:i + 1]
628 628 i += 1
629 629 elif c == '*':
630 630 res += '.*'
631 631 elif c == '?':
632 632 res += '.'
633 633 elif c == '/' and os.name == 'nt':
634 634 res += '[/\\\\]'
635 635 else:
636 636 res += re.escape(c)
637 637 return rematch(res, l)
638 638
639 639 def linematch(el, l):
640 640 if el == l: # perfect match (fast)
641 641 return True
642 642 if el:
643 643 if el.endswith(" (esc)\n"):
644 644 el = el[:-7].decode('string-escape') + '\n'
645 645 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
646 646 return True
647 647 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
648 648 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
649 649 return True
650 650 return False
651 651
652 652 def tsttest(test, wd, options, replacements, env):
653 653 # We generate a shell script which outputs unique markers to line
654 654 # up script results with our source. These markers include input
655 655 # line number and the last return code
656 656 salt = "SALT" + str(time.time())
657 657 def addsalt(line, inpython):
658 658 if inpython:
659 659 script.append('%s %d 0\n' % (salt, line))
660 660 else:
661 661 script.append('echo %s %s $?\n' % (salt, line))
662 662
663 663 # After we run the shell script, we re-unify the script output
664 664 # with non-active parts of the source, with synchronization by our
665 665 # SALT line number markers. The after table contains the
666 666 # non-active components, ordered by line number
667 667 after = {}
668 668 pos = prepos = -1
669 669
670 670 # Expected shellscript output
671 671 expected = {}
672 672
673 673 # We keep track of whether or not we're in a Python block so we
674 674 # can generate the surrounding doctest magic
675 675 inpython = False
676 676
677 677 # True or False when in a true or false conditional section
678 678 skipping = None
679 679
680 680 def hghave(reqs):
681 681 # TODO: do something smarter when all other uses of hghave is gone
682 682 tdir = TESTDIR.replace('\\', '/')
683 683 proc = Popen4('%s -c "%s/hghave %s"' %
684 684 (options.shell, tdir, ' '.join(reqs)), wd, 0)
685 685 stdout, stderr = proc.communicate()
686 686 ret = proc.wait()
687 687 if wifexited(ret):
688 688 ret = os.WEXITSTATUS(ret)
689 689 if ret == 2:
690 690 print stdout
691 691 sys.exit(1)
692 692 return ret == 0
693 693
694 694 f = open(test)
695 695 t = f.readlines()
696 696 f.close()
697 697
698 698 script = []
699 699 if options.debug:
700 700 script.append('set -x\n')
701 701 if os.getenv('MSYSTEM'):
702 702 script.append('alias pwd="pwd -W"\n')
703 703 n = 0
704 704 for n, l in enumerate(t):
705 705 if not l.endswith('\n'):
706 706 l += '\n'
707 707 if l.startswith('#if'):
708 708 if skipping is not None:
709 709 after.setdefault(pos, []).append(' !!! nested #if\n')
710 710 skipping = not hghave(l.split()[1:])
711 711 after.setdefault(pos, []).append(l)
712 712 elif l.startswith('#else'):
713 713 if skipping is None:
714 714 after.setdefault(pos, []).append(' !!! missing #if\n')
715 715 skipping = not skipping
716 716 after.setdefault(pos, []).append(l)
717 717 elif l.startswith('#endif'):
718 718 if skipping is None:
719 719 after.setdefault(pos, []).append(' !!! missing #if\n')
720 720 skipping = None
721 721 after.setdefault(pos, []).append(l)
722 722 elif skipping:
723 723 after.setdefault(pos, []).append(l)
724 724 elif l.startswith(' >>> '): # python inlines
725 725 after.setdefault(pos, []).append(l)
726 726 prepos = pos
727 727 pos = n
728 728 if not inpython:
729 729 # we've just entered a Python block, add the header
730 730 inpython = True
731 731 addsalt(prepos, False) # make sure we report the exit code
732 732 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
733 733 addsalt(n, True)
734 734 script.append(l[2:])
735 735 elif l.startswith(' ... '): # python inlines
736 736 after.setdefault(prepos, []).append(l)
737 737 script.append(l[2:])
738 738 elif l.startswith(' $ '): # commands
739 739 if inpython:
740 740 script.append("EOF\n")
741 741 inpython = False
742 742 after.setdefault(pos, []).append(l)
743 743 prepos = pos
744 744 pos = n
745 745 addsalt(n, False)
746 746 cmd = l[4:].split()
747 747 if len(cmd) == 2 and cmd[0] == 'cd':
748 748 l = ' $ cd %s || exit 1\n' % cmd[1]
749 749 script.append(l[4:])
750 750 elif l.startswith(' > '): # continuations
751 751 after.setdefault(prepos, []).append(l)
752 752 script.append(l[4:])
753 753 elif l.startswith(' '): # results
754 754 # queue up a list of expected results
755 755 expected.setdefault(pos, []).append(l[2:])
756 756 else:
757 757 if inpython:
758 758 script.append("EOF\n")
759 759 inpython = False
760 760 # non-command/result - queue up for merged output
761 761 after.setdefault(pos, []).append(l)
762 762
763 763 if inpython:
764 764 script.append("EOF\n")
765 765 if skipping is not None:
766 766 after.setdefault(pos, []).append(' !!! missing #endif\n')
767 767 addsalt(n + 1, False)
768 768
769 769 # Write out the script and execute it
770 770 fd, name = tempfile.mkstemp(suffix='hg-tst')
771 771 try:
772 772 for l in script:
773 773 os.write(fd, l)
774 774 os.close(fd)
775 775
776 776 cmd = '%s "%s"' % (options.shell, name)
777 777 vlog("# Running", cmd)
778 778 exitcode, output = run(cmd, wd, options, replacements, env)
779 779 # do not merge output if skipped, return hghave message instead
780 780 # similarly, with --debug, output is None
781 781 if exitcode == SKIPPED_STATUS or output is None:
782 782 return exitcode, output
783 783 finally:
784 784 os.remove(name)
785 785
786 786 # Merge the script output back into a unified test
787 787
788 788 pos = -1
789 789 postout = []
790 790 ret = 0
791 791 for l in output:
792 792 lout, lcmd = l, None
793 793 if salt in l:
794 794 lout, lcmd = l.split(salt, 1)
795 795
796 796 if lout:
797 797 if not lout.endswith('\n'):
798 798 lout += ' (no-eol)\n'
799 799
800 800 # find the expected output at the current position
801 801 el = None
802 802 if pos in expected and expected[pos]:
803 803 el = expected[pos].pop(0)
804 804
805 805 if linematch(el, lout):
806 806 postout.append(" " + el)
807 807 else:
808 808 if needescape(lout):
809 809 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
810 810 postout.append(" " + lout) # let diff deal with it
811 811
812 812 if lcmd:
813 813 # add on last return code
814 814 ret = int(lcmd.split()[1])
815 815 if ret != 0:
816 816 postout.append(" [%s]\n" % ret)
817 817 if pos in after:
818 818 # merge in non-active test bits
819 819 postout += after.pop(pos)
820 820 pos = int(lcmd.split()[0])
821 821
822 822 if pos in after:
823 823 postout += after.pop(pos)
824 824
825 825 return exitcode, postout
826 826
827 827 wifexited = getattr(os, "WIFEXITED", lambda x: False)
828 828 def run(cmd, wd, options, replacements, env):
829 829 """Run command in a sub-process, capturing the output (stdout and stderr).
830 830 Return a tuple (exitcode, output). output is None in debug mode."""
831 831 # TODO: Use subprocess.Popen if we're running on Python 2.4
832 832 if options.debug:
833 833 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
834 834 ret = proc.wait()
835 835 return (ret, None)
836 836
837 837 proc = Popen4(cmd, wd, options.timeout, env)
838 838 def cleanup():
839 839 terminate(proc)
840 840 ret = proc.wait()
841 841 if ret == 0:
842 842 ret = signal.SIGTERM << 8
843 843 killdaemons(env['DAEMON_PIDS'])
844 844 return ret
845 845
846 846 output = ''
847 847 proc.tochild.close()
848 848
849 849 try:
850 850 output = proc.fromchild.read()
851 851 except KeyboardInterrupt:
852 852 vlog('# Handling keyboard interrupt')
853 853 cleanup()
854 854 raise
855 855
856 856 ret = proc.wait()
857 857 if wifexited(ret):
858 858 ret = os.WEXITSTATUS(ret)
859 859
860 860 if proc.timeout:
861 861 ret = 'timeout'
862 862
863 863 if ret:
864 864 killdaemons(env['DAEMON_PIDS'])
865 865
866 if abort:
867 raise KeyboardInterrupt()
868
866 869 for s, r in replacements:
867 870 output = re.sub(s, r, output)
868 871 return ret, output.splitlines(True)
869 872
870 873 def runone(options, test):
871 874 '''returns a result element: (code, test, msg)'''
872 875
873 876 def skip(msg):
874 877 if options.verbose:
875 878 log("\nSkipping %s: %s" % (testpath, msg))
876 879 return 's', test, msg
877 880
878 881 def fail(msg, ret):
879 882 if not options.nodiff:
880 883 log("\nERROR: %s %s" % (testpath, msg))
881 884 if (not ret and options.interactive
882 885 and os.path.exists(testpath + ".err")):
883 886 iolock.acquire()
884 887 print "Accept this change? [n] ",
885 888 answer = sys.stdin.readline().strip()
886 889 iolock.release()
887 890 if answer.lower() in "y yes".split():
888 891 if test.endswith(".t"):
889 892 rename(testpath + ".err", testpath)
890 893 else:
891 894 rename(testpath + ".err", testpath + ".out")
892 895 return '.', test, ''
893 896 return '!', test, msg
894 897
895 898 def success():
896 899 return '.', test, ''
897 900
898 901 def ignore(msg):
899 902 return 'i', test, msg
900 903
901 904 def describe(ret):
902 905 if ret < 0:
903 906 return 'killed by signal %d' % -ret
904 907 return 'returned error code %d' % ret
905 908
906 909 testpath = os.path.join(TESTDIR, test)
907 910 err = os.path.join(TESTDIR, test + ".err")
908 911 lctest = test.lower()
909 912
910 913 if not os.path.exists(testpath):
911 914 return skip("doesn't exist")
912 915
913 916 if not (options.whitelisted and test in options.whitelisted):
914 917 if options.blacklist and test in options.blacklist:
915 918 return skip("blacklisted")
916 919
917 920 if options.retest and not os.path.exists(test + ".err"):
918 921 ignore("not retesting")
919 922 return None
920 923
921 924 if options.keywords:
922 925 fp = open(test)
923 926 t = fp.read().lower() + test.lower()
924 927 fp.close()
925 928 for k in options.keywords.lower().split():
926 929 if k in t:
927 930 break
928 931 else:
929 932 ignore("doesn't match keyword")
930 933 return None
931 934
932 935 for ext, func, out in testtypes:
933 936 if lctest.startswith("test-") and lctest.endswith(ext):
934 937 runner = func
935 938 ref = os.path.join(TESTDIR, test + out)
936 939 break
937 940 else:
938 941 return skip("unknown test type")
939 942
940 943 vlog("# Test", test)
941 944
942 945 if os.path.exists(err):
943 946 os.remove(err) # Remove any previous output files
944 947
945 948 # Make a tmp subdirectory to work in
946 949 testtmp = os.path.join(HGTMP, os.path.basename(test))
947 950 os.mkdir(testtmp)
948 951
949 952 replacements = [
950 953 (r':%s\b' % options.port, ':$HGPORT'),
951 954 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
952 955 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
953 956 ]
954 957 if os.name == 'nt':
955 958 replacements.append(
956 959 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
957 960 c in '/\\' and r'[/\\]' or
958 961 c.isdigit() and c or
959 962 '\\' + c
960 963 for c in testtmp), '$TESTTMP'))
961 964 else:
962 965 replacements.append((re.escape(testtmp), '$TESTTMP'))
963 966
964 967 env = createenv(options, testtmp)
965 968 createhgrc(env['HGRCPATH'], options)
966 969
967 970 if options.time:
968 971 starttime = time.time()
969 972 ret, out = runner(testpath, testtmp, options, replacements, env)
970 973 if options.time:
971 974 endtime = time.time()
972 975 times.append((test, endtime - starttime))
973 976 vlog("# Ret was:", ret)
974 977
975 978 killdaemons(env['DAEMON_PIDS'])
976 979
977 980 skipped = (ret == SKIPPED_STATUS)
978 981
979 982 # If we're not in --debug mode and reference output file exists,
980 983 # check test output against it.
981 984 if options.debug:
982 985 refout = None # to match "out is None"
983 986 elif os.path.exists(ref):
984 987 f = open(ref, "r")
985 988 refout = f.read().splitlines(True)
986 989 f.close()
987 990 else:
988 991 refout = []
989 992
990 993 if (ret != 0 or out != refout) and not skipped and not options.debug:
991 994 # Save errors to a file for diagnosis
992 995 f = open(err, "wb")
993 996 for line in out:
994 997 f.write(line)
995 998 f.close()
996 999
997 1000 if skipped:
998 1001 if out is None: # debug mode: nothing to parse
999 1002 missing = ['unknown']
1000 1003 failed = None
1001 1004 else:
1002 1005 missing, failed = parsehghaveoutput(out)
1003 1006 if not missing:
1004 1007 missing = ['irrelevant']
1005 1008 if failed:
1006 1009 result = fail("hghave failed checking for %s" % failed[-1], ret)
1007 1010 skipped = False
1008 1011 else:
1009 1012 result = skip(missing[-1])
1010 1013 elif ret == 'timeout':
1011 1014 result = fail("timed out", ret)
1012 1015 elif out != refout:
1013 1016 if not options.nodiff:
1014 1017 iolock.acquire()
1015 1018 if options.view:
1016 1019 os.system("%s %s %s" % (options.view, ref, err))
1017 1020 else:
1018 1021 showdiff(refout, out, ref, err)
1019 1022 iolock.release()
1020 1023 if ret:
1021 1024 result = fail("output changed and " + describe(ret), ret)
1022 1025 else:
1023 1026 result = fail("output changed", ret)
1024 1027 elif ret:
1025 1028 result = fail(describe(ret), ret)
1026 1029 else:
1027 1030 result = success()
1028 1031
1029 1032 if not options.verbose:
1030 1033 iolock.acquire()
1031 1034 sys.stdout.write(result[0])
1032 1035 sys.stdout.flush()
1033 1036 iolock.release()
1034 1037
1035 1038 if not options.keep_tmpdir:
1036 1039 shutil.rmtree(testtmp, True)
1037 1040 return result
1038 1041
1039 1042 _hgpath = None
1040 1043
1041 1044 def _gethgpath():
1042 1045 """Return the path to the mercurial package that is actually found by
1043 1046 the current Python interpreter."""
1044 1047 global _hgpath
1045 1048 if _hgpath is not None:
1046 1049 return _hgpath
1047 1050
1048 1051 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1049 1052 pipe = os.popen(cmd % PYTHON)
1050 1053 try:
1051 1054 _hgpath = pipe.read().strip()
1052 1055 finally:
1053 1056 pipe.close()
1054 1057 return _hgpath
1055 1058
1056 1059 def _checkhglib(verb):
1057 1060 """Ensure that the 'mercurial' package imported by python is
1058 1061 the one we expect it to be. If not, print a warning to stderr."""
1059 1062 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1060 1063 actualhg = _gethgpath()
1061 1064 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1062 1065 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1063 1066 ' (expected %s)\n'
1064 1067 % (verb, actualhg, expecthg))
1065 1068
1066 1069 def runchildren(options, tests):
1067 1070 if INST:
1068 1071 installhg(options)
1069 1072 _checkhglib("Testing")
1070 1073 else:
1071 1074 usecorrectpython()
1072 1075
1073 1076 optcopy = dict(options.__dict__)
1074 1077 optcopy['jobs'] = 1
1075 1078
1076 1079 # Because whitelist has to override keyword matches, we have to
1077 1080 # actually load the whitelist in the children as well, so we allow
1078 1081 # the list of whitelist files to pass through and be parsed in the
1079 1082 # children, but not the dict of whitelisted tests resulting from
1080 1083 # the parse, used here to override blacklisted tests.
1081 1084 whitelist = optcopy['whitelisted'] or []
1082 1085 del optcopy['whitelisted']
1083 1086
1084 1087 blacklist = optcopy['blacklist'] or []
1085 1088 del optcopy['blacklist']
1086 1089 blacklisted = []
1087 1090
1088 1091 if optcopy['with_hg'] is None:
1089 1092 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1090 1093 optcopy.pop('anycoverage', None)
1091 1094
1092 1095 opts = []
1093 1096 for opt, value in optcopy.iteritems():
1094 1097 name = '--' + opt.replace('_', '-')
1095 1098 if value is True:
1096 1099 opts.append(name)
1097 1100 elif isinstance(value, list):
1098 1101 for v in value:
1099 1102 opts.append(name + '=' + str(v))
1100 1103 elif value is not None:
1101 1104 opts.append(name + '=' + str(value))
1102 1105
1103 1106 tests.reverse()
1104 1107 jobs = [[] for j in xrange(options.jobs)]
1105 1108 while tests:
1106 1109 for job in jobs:
1107 1110 if not tests:
1108 1111 break
1109 1112 test = tests.pop()
1110 1113 if test not in whitelist and test in blacklist:
1111 1114 blacklisted.append(test)
1112 1115 else:
1113 1116 job.append(test)
1114 1117
1115 1118 waitq = queue.Queue()
1116 1119
1117 1120 # windows lacks os.wait, so we must emulate it
1118 1121 def waitfor(proc, rfd):
1119 1122 fp = os.fdopen(rfd, 'rb')
1120 1123 return lambda: waitq.put((proc.pid, proc.wait(), fp))
1121 1124
1122 1125 for j, job in enumerate(jobs):
1123 1126 if not job:
1124 1127 continue
1125 1128 rfd, wfd = os.pipe()
1126 1129 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1127 1130 childtmp = os.path.join(HGTMP, 'child%d' % j)
1128 1131 childopts += ['--tmpdir', childtmp]
1129 1132 if options.keep_tmpdir:
1130 1133 childopts.append('--keep-tmpdir')
1131 1134 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1132 1135 vlog(' '.join(cmdline))
1133 1136 proc = subprocess.Popen(cmdline, executable=cmdline[0])
1134 1137 threading.Thread(target=waitfor(proc, rfd)).start()
1135 1138 os.close(wfd)
1136 1139 signal.signal(signal.SIGINT, signal.SIG_IGN)
1137 1140 failures = 0
1138 1141 passed, skipped, failed = 0, 0, 0
1139 1142 skips = []
1140 1143 fails = []
1141 1144 for job in jobs:
1142 1145 if not job:
1143 1146 continue
1144 1147 pid, status, fp = waitq.get()
1145 1148 try:
1146 1149 childresults = pickle.load(fp)
1147 1150 except (pickle.UnpicklingError, EOFError):
1148 1151 sys.exit(255)
1149 1152 else:
1150 1153 passed += len(childresults['.'])
1151 1154 skipped += len(childresults['s'])
1152 1155 failed += len(childresults['!'])
1153 1156 skips.extend(childresults['s'])
1154 1157 fails.extend(childresults['!'])
1155 1158 if options.time:
1156 1159 childtimes = pickle.load(fp)
1157 1160 times.extend(childtimes)
1158 1161
1159 1162 vlog('pid %d exited, status %d' % (pid, status))
1160 1163 failures |= status
1161 1164 print
1162 1165 skipped += len(blacklisted)
1163 1166 if not options.noskips:
1164 1167 for s in skips:
1165 1168 print "Skipped %s: %s" % (s[0], s[1])
1166 1169 for s in blacklisted:
1167 1170 print "Skipped %s: blacklisted" % s
1168 1171 for s in fails:
1169 1172 print "Failed %s: %s" % (s[0], s[1])
1170 1173
1171 1174 _checkhglib("Tested")
1172 1175 print "# Ran %d tests, %d skipped, %d failed." % (
1173 1176 passed + failed, skipped, failed)
1174 1177
1175 1178 if options.time:
1176 1179 outputtimes(options)
1177 1180 if options.anycoverage:
1178 1181 outputcoverage(options)
1179 1182 sys.exit(failures != 0)
1180 1183
1181 1184 results = {'.':[], '!':[], 's':[], 'i':[]}
1182 1185 resultslock = threading.Lock()
1183 1186 times = []
1184 1187 iolock = threading.Lock()
1188 abort = False
1185 1189
1186 1190 def runqueue(options, tests):
1187 1191 for test in tests:
1188 1192 code, test, msg = runone(options, test)
1189 1193 resultslock.acquire()
1190 1194 results[code].append((test, msg))
1191 1195 resultslock.release()
1192 1196
1193 1197 if options.first and code not in '.si':
1194 1198 break
1195 1199
1196 1200 def runtests(options, tests):
1197 1201 try:
1198 1202 if INST:
1199 1203 installhg(options)
1200 1204 _checkhglib("Testing")
1201 1205 else:
1202 1206 usecorrectpython()
1203 1207
1204 1208 if options.restart:
1205 1209 orig = list(tests)
1206 1210 while tests:
1207 1211 if os.path.exists(tests[0] + ".err"):
1208 1212 break
1209 1213 tests.pop(0)
1210 1214 if not tests:
1211 1215 print "running all tests"
1212 1216 tests = orig
1213 1217
1214 1218 runqueue(options, tests)
1215 1219
1216 1220 failed = len(results['!'])
1217 1221 tested = len(results['.']) + failed
1218 1222 skipped = len(results['s'])
1219 1223 ignored = len(results['i'])
1220 1224
1221 1225 if options.child:
1222 1226 fp = os.fdopen(options.child, 'wb')
1223 1227 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1224 1228 if options.time:
1225 1229 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1226 1230 fp.close()
1227 1231 else:
1228 1232 print
1229 1233 for s in results['s']:
1230 1234 print "Skipped %s: %s" % s
1231 1235 for s in results['!']:
1232 1236 print "Failed %s: %s" % s
1233 1237 _checkhglib("Tested")
1234 1238 print "# Ran %d tests, %d skipped, %d failed." % (
1235 1239 tested, skipped + ignored, failed)
1236 1240 if options.time:
1237 1241 outputtimes(options)
1238 1242
1239 1243 if options.anycoverage:
1240 1244 outputcoverage(options)
1241 1245 except KeyboardInterrupt:
1242 1246 failed = True
1243 1247 if not options.child:
1244 1248 print "\ninterrupted!"
1245 1249
1246 1250 if failed:
1247 1251 sys.exit(1)
1248 1252
1249 1253 testtypes = [('.py', pytest, '.out'),
1250 1254 ('.t', tsttest, '')]
1251 1255
1252 1256 def main():
1253 1257 (options, args) = parseargs()
1254 1258 if not options.child:
1255 1259 os.umask(022)
1256 1260
1257 1261 checktools()
1258 1262
1259 1263 if len(args) == 0:
1260 1264 args = sorted(t for t in os.listdir(".")
1261 1265 if t.startswith("test-")
1262 1266 and (t.endswith(".py") or t.endswith(".t")))
1263 1267
1264 1268 tests = args
1265 1269
1266 1270 if options.random:
1267 1271 random.shuffle(tests)
1268 1272
1269 1273 if 'PYTHONHASHSEED' not in os.environ:
1270 1274 # use a random python hash seed all the time
1271 1275 # we do the randomness ourself to know what seed is used
1272 1276 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1273 1277 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1274 1278
1275 1279 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1276 1280 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1277 1281 if options.tmpdir:
1278 1282 if not options.child:
1279 1283 options.keep_tmpdir = True
1280 1284 tmpdir = options.tmpdir
1281 1285 if os.path.exists(tmpdir):
1282 1286 # Meaning of tmpdir has changed since 1.3: we used to create
1283 1287 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1284 1288 # tmpdir already exists.
1285 1289 sys.exit("error: temp dir %r already exists" % tmpdir)
1286 1290
1287 1291 # Automatically removing tmpdir sounds convenient, but could
1288 1292 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1289 1293 # or "--tmpdir=$HOME".
1290 1294 #vlog("# Removing temp dir", tmpdir)
1291 1295 #shutil.rmtree(tmpdir)
1292 1296 os.makedirs(tmpdir)
1293 1297 else:
1294 1298 d = None
1295 1299 if os.name == 'nt':
1296 1300 # without this, we get the default temp dir location, but
1297 1301 # in all lowercase, which causes troubles with paths (issue3490)
1298 1302 d = os.getenv('TMP')
1299 1303 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1300 1304 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1301 1305
1302 1306 if options.with_hg:
1303 1307 INST = None
1304 1308 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1305 1309
1306 1310 # This looks redundant with how Python initializes sys.path from
1307 1311 # the location of the script being executed. Needed because the
1308 1312 # "hg" specified by --with-hg is not the only Python script
1309 1313 # executed in the test suite that needs to import 'mercurial'
1310 1314 # ... which means it's not really redundant at all.
1311 1315 PYTHONDIR = BINDIR
1312 1316 else:
1313 1317 INST = os.path.join(HGTMP, "install")
1314 1318 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1315 1319 PYTHONDIR = os.path.join(INST, "lib", "python")
1316 1320
1317 1321 os.environ["BINDIR"] = BINDIR
1318 1322 os.environ["PYTHON"] = PYTHON
1319 1323
1320 1324 if not options.child:
1321 1325 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1322 1326 os.environ["PATH"] = os.pathsep.join(path)
1323 1327
1324 1328 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1325 1329 # can run .../tests/run-tests.py test-foo where test-foo
1326 1330 # adds an extension to HGRC
1327 1331 pypath = [PYTHONDIR, TESTDIR]
1328 1332 # We have to augment PYTHONPATH, rather than simply replacing
1329 1333 # it, in case external libraries are only available via current
1330 1334 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1331 1335 # are in /opt/subversion.)
1332 1336 oldpypath = os.environ.get(IMPL_PATH)
1333 1337 if oldpypath:
1334 1338 pypath.append(oldpypath)
1335 1339 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1336 1340
1337 1341 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1338 1342
1339 1343 vlog("# Using TESTDIR", TESTDIR)
1340 1344 vlog("# Using HGTMP", HGTMP)
1341 1345 vlog("# Using PATH", os.environ["PATH"])
1342 1346 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1343 1347
1344 1348 try:
1345 1349 if len(tests) > 1 and options.jobs > 1:
1346 1350 runchildren(options, tests)
1347 1351 else:
1348 1352 runtests(options, tests)
1349 1353 finally:
1350 1354 time.sleep(.1)
1351 1355 cleanup(options)
1352 1356
1353 1357 if __name__ == '__main__':
1354 1358 main()
General Comments 0
You need to be logged in to leave comments. Login now