##// END OF EJS Templates
run-tests: move more variables to env
Matt Mackall -
r19272:16df87e2 default
parent child Browse files
Show More
@@ -1,1355 +1,1354 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 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
375 env["HGMERGE"] = "internal:merge"
376 env["HGUSER"] = "test"
377 env["HGENCODING"] = "ascii"
378 env["HGENCODINGMODE"] = "strict"
374 379
375 380 # Reset some environment variables to well-known values so that
376 381 # the tests produce repeatable output.
377 382 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
378 383 env['TZ'] = 'GMT'
379 384 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
380 385 env['COLUMNS'] = '80'
381 386 env['TERM'] = 'xterm'
382 387
383 388 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
384 389 'NO_PROXY').split():
385 390 if k in env:
386 391 del env[k]
387 392
388 393 # unset env related to hooks
389 394 for k in env.keys():
390 395 if k.startswith('HG_'):
391 396 del env[k]
392 397
393 398 return env
394 399
395 400 def checktools():
396 401 # Before we go any further, check for pre-requisite tools
397 402 # stuff from coreutils (cat, rm, etc) are not tested
398 403 for p in requiredtools:
399 404 if os.name == 'nt' and not p.endswith('.exe'):
400 405 p += '.exe'
401 406 found = findprogram(p)
402 407 if found:
403 408 vlog("# Found prerequisite", p, "at", found)
404 409 else:
405 410 print "WARNING: Did not find prerequisite tool: "+p
406 411
407 412 def terminate(proc):
408 413 """Terminate subprocess (with fallback for Python versions < 2.6)"""
409 414 vlog('# Terminating process %d' % proc.pid)
410 415 try:
411 416 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
412 417 except OSError:
413 418 pass
414 419
415 420 def killdaemons(pidfile):
416 421 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
417 422 logfn=vlog)
418 423
419 424 def cleanup(options):
420 425 if not options.keep_tmpdir:
421 426 vlog("# Cleaning up HGTMP", HGTMP)
422 427 shutil.rmtree(HGTMP, True)
423 428
424 429 def usecorrectpython():
425 430 # some tests run python interpreter. they must use same
426 431 # interpreter we use or bad things will happen.
427 432 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
428 433 if getattr(os, 'symlink', None):
429 434 vlog("# Making python executable in test path a symlink to '%s'" %
430 435 sys.executable)
431 436 mypython = os.path.join(BINDIR, pyexename)
432 437 try:
433 438 if os.readlink(mypython) == sys.executable:
434 439 return
435 440 os.unlink(mypython)
436 441 except OSError, err:
437 442 if err.errno != errno.ENOENT:
438 443 raise
439 444 if findprogram(pyexename) != sys.executable:
440 445 try:
441 446 os.symlink(sys.executable, mypython)
442 447 except OSError, err:
443 448 # child processes may race, which is harmless
444 449 if err.errno != errno.EEXIST:
445 450 raise
446 451 else:
447 452 exedir, exename = os.path.split(sys.executable)
448 453 vlog("# Modifying search path to find %s as %s in '%s'" %
449 454 (exename, pyexename, exedir))
450 455 path = os.environ['PATH'].split(os.pathsep)
451 456 while exedir in path:
452 457 path.remove(exedir)
453 458 os.environ['PATH'] = os.pathsep.join([exedir] + path)
454 459 if not findprogram(pyexename):
455 460 print "WARNING: Cannot find %s in search path" % pyexename
456 461
457 462 def installhg(options):
458 463 vlog("# Performing temporary installation of HG")
459 464 installerrs = os.path.join("tests", "install.err")
460 465 compiler = ''
461 466 if options.compiler:
462 467 compiler = '--compiler ' + options.compiler
463 468 pure = options.pure and "--pure" or ""
464 469
465 470 # Run installer in hg root
466 471 script = os.path.realpath(sys.argv[0])
467 472 hgroot = os.path.dirname(os.path.dirname(script))
468 473 os.chdir(hgroot)
469 474 nohome = '--home=""'
470 475 if os.name == 'nt':
471 476 # The --home="" trick works only on OS where os.sep == '/'
472 477 # because of a distutils convert_path() fast-path. Avoid it at
473 478 # least on Windows for now, deal with .pydistutils.cfg bugs
474 479 # when they happen.
475 480 nohome = ''
476 481 cmd = ('%(exe)s setup.py %(pure)s clean --all'
477 482 ' build %(compiler)s --build-base="%(base)s"'
478 483 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
479 484 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
480 485 % dict(exe=sys.executable, pure=pure, compiler=compiler,
481 486 base=os.path.join(HGTMP, "build"),
482 487 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
483 488 nohome=nohome, logfile=installerrs))
484 489 vlog("# Running", cmd)
485 490 if os.system(cmd) == 0:
486 491 if not options.verbose:
487 492 os.remove(installerrs)
488 493 else:
489 494 f = open(installerrs)
490 495 for line in f:
491 496 print line,
492 497 f.close()
493 498 sys.exit(1)
494 499 os.chdir(TESTDIR)
495 500
496 501 usecorrectpython()
497 502
498 503 vlog("# Installing dummy diffstat")
499 504 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
500 505 f.write('#!' + sys.executable + '\n'
501 506 'import sys\n'
502 507 'files = 0\n'
503 508 'for line in sys.stdin:\n'
504 509 ' if line.startswith("diff "):\n'
505 510 ' files += 1\n'
506 511 'sys.stdout.write("files patched: %d\\n" % files)\n')
507 512 f.close()
508 513 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
509 514
510 515 if options.py3k_warnings and not options.anycoverage:
511 516 vlog("# Updating hg command to enable Py3k Warnings switch")
512 517 f = open(os.path.join(BINDIR, 'hg'), 'r')
513 518 lines = [line.rstrip() for line in f]
514 519 lines[0] += ' -3'
515 520 f.close()
516 521 f = open(os.path.join(BINDIR, 'hg'), 'w')
517 522 for line in lines:
518 523 f.write(line + '\n')
519 524 f.close()
520 525
521 526 hgbat = os.path.join(BINDIR, 'hg.bat')
522 527 if os.path.isfile(hgbat):
523 528 # hg.bat expects to be put in bin/scripts while run-tests.py
524 529 # installation layout put it in bin/ directly. Fix it
525 530 f = open(hgbat, 'rb')
526 531 data = f.read()
527 532 f.close()
528 533 if '"%~dp0..\python" "%~dp0hg" %*' in data:
529 534 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
530 535 '"%~dp0python" "%~dp0hg" %*')
531 536 f = open(hgbat, 'wb')
532 537 f.write(data)
533 538 f.close()
534 539 else:
535 540 print 'WARNING: cannot fix hg.bat reference to python.exe'
536 541
537 542 if options.anycoverage:
538 543 custom = os.path.join(TESTDIR, 'sitecustomize.py')
539 544 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
540 545 vlog('# Installing coverage trigger to %s' % target)
541 546 shutil.copyfile(custom, target)
542 547 rc = os.path.join(TESTDIR, '.coveragerc')
543 548 vlog('# Installing coverage rc to %s' % rc)
544 549 os.environ['COVERAGE_PROCESS_START'] = rc
545 550 fn = os.path.join(INST, '..', '.coverage')
546 551 os.environ['COVERAGE_FILE'] = fn
547 552
548 553 def outputtimes(options):
549 554 vlog('# Producing time report')
550 555 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
551 556 cols = '%7.3f %s'
552 557 print '\n%-7s %s' % ('Time', 'Test')
553 558 for test, timetaken in times:
554 559 print cols % (timetaken, test)
555 560
556 561 def outputcoverage(options):
557 562
558 563 vlog('# Producing coverage report')
559 564 os.chdir(PYTHONDIR)
560 565
561 566 def covrun(*args):
562 567 cmd = 'coverage %s' % ' '.join(args)
563 568 vlog('# Running: %s' % cmd)
564 569 os.system(cmd)
565 570
566 571 if options.child:
567 572 return
568 573
569 574 covrun('-c')
570 575 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
571 576 covrun('-i', '-r', '"--omit=%s"' % omit) # report
572 577 if options.htmlcov:
573 578 htmldir = os.path.join(TESTDIR, 'htmlcov')
574 579 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
575 580 if options.annotate:
576 581 adir = os.path.join(TESTDIR, 'annotated')
577 582 if not os.path.isdir(adir):
578 583 os.mkdir(adir)
579 584 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
580 585
581 586 def pytest(test, wd, options, replacements, env):
582 587 py3kswitch = options.py3k_warnings and ' -3' or ''
583 588 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
584 589 vlog("# Running", cmd)
585 590 if os.name == 'nt':
586 591 replacements.append((r'\r\n', '\n'))
587 592 return run(cmd, wd, options, replacements, env)
588 593
589 594 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
590 595 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
591 596 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
592 597 escapemap.update({'\\': '\\\\', '\r': r'\r'})
593 598 def escapef(m):
594 599 return escapemap[m.group(0)]
595 600 def stringescape(s):
596 601 return escapesub(escapef, s)
597 602
598 603 def rematch(el, l):
599 604 try:
600 605 # use \Z to ensure that the regex matches to the end of the string
601 606 if os.name == 'nt':
602 607 return re.match(el + r'\r?\n\Z', l)
603 608 return re.match(el + r'\n\Z', l)
604 609 except re.error:
605 610 # el is an invalid regex
606 611 return False
607 612
608 613 def globmatch(el, l):
609 614 # The only supported special characters are * and ? plus / which also
610 615 # matches \ on windows. Escaping of these caracters is supported.
611 616 if el + '\n' == l:
612 617 if os.name == 'nt':
613 618 # matching on "/" is not needed for this line
614 619 log("\nInfo, unnecessary glob: %s (glob)" % el)
615 620 return True
616 621 i, n = 0, len(el)
617 622 res = ''
618 623 while i < n:
619 624 c = el[i]
620 625 i += 1
621 626 if c == '\\' and el[i] in '*?\\/':
622 627 res += el[i - 1:i + 1]
623 628 i += 1
624 629 elif c == '*':
625 630 res += '.*'
626 631 elif c == '?':
627 632 res += '.'
628 633 elif c == '/' and os.name == 'nt':
629 634 res += '[/\\\\]'
630 635 else:
631 636 res += re.escape(c)
632 637 return rematch(res, l)
633 638
634 639 def linematch(el, l):
635 640 if el == l: # perfect match (fast)
636 641 return True
637 642 if el:
638 643 if el.endswith(" (esc)\n"):
639 644 el = el[:-7].decode('string-escape') + '\n'
640 645 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
641 646 return True
642 647 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
643 648 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
644 649 return True
645 650 return False
646 651
647 652 def tsttest(test, wd, options, replacements, env):
648 653 # We generate a shell script which outputs unique markers to line
649 654 # up script results with our source. These markers include input
650 655 # line number and the last return code
651 656 salt = "SALT" + str(time.time())
652 657 def addsalt(line, inpython):
653 658 if inpython:
654 659 script.append('%s %d 0\n' % (salt, line))
655 660 else:
656 661 script.append('echo %s %s $?\n' % (salt, line))
657 662
658 663 # After we run the shell script, we re-unify the script output
659 664 # with non-active parts of the source, with synchronization by our
660 665 # SALT line number markers. The after table contains the
661 666 # non-active components, ordered by line number
662 667 after = {}
663 668 pos = prepos = -1
664 669
665 670 # Expected shellscript output
666 671 expected = {}
667 672
668 673 # We keep track of whether or not we're in a Python block so we
669 674 # can generate the surrounding doctest magic
670 675 inpython = False
671 676
672 677 # True or False when in a true or false conditional section
673 678 skipping = None
674 679
675 680 def hghave(reqs):
676 681 # TODO: do something smarter when all other uses of hghave is gone
677 682 tdir = TESTDIR.replace('\\', '/')
678 683 proc = Popen4('%s -c "%s/hghave %s"' %
679 684 (options.shell, tdir, ' '.join(reqs)), wd, 0)
680 685 stdout, stderr = proc.communicate()
681 686 ret = proc.wait()
682 687 if wifexited(ret):
683 688 ret = os.WEXITSTATUS(ret)
684 689 if ret == 2:
685 690 print stdout
686 691 sys.exit(1)
687 692 return ret == 0
688 693
689 694 f = open(test)
690 695 t = f.readlines()
691 696 f.close()
692 697
693 698 script = []
694 699 if options.debug:
695 700 script.append('set -x\n')
696 701 if os.getenv('MSYSTEM'):
697 702 script.append('alias pwd="pwd -W"\n')
698 703 n = 0
699 704 for n, l in enumerate(t):
700 705 if not l.endswith('\n'):
701 706 l += '\n'
702 707 if l.startswith('#if'):
703 708 if skipping is not None:
704 709 after.setdefault(pos, []).append(' !!! nested #if\n')
705 710 skipping = not hghave(l.split()[1:])
706 711 after.setdefault(pos, []).append(l)
707 712 elif l.startswith('#else'):
708 713 if skipping is None:
709 714 after.setdefault(pos, []).append(' !!! missing #if\n')
710 715 skipping = not skipping
711 716 after.setdefault(pos, []).append(l)
712 717 elif l.startswith('#endif'):
713 718 if skipping is None:
714 719 after.setdefault(pos, []).append(' !!! missing #if\n')
715 720 skipping = None
716 721 after.setdefault(pos, []).append(l)
717 722 elif skipping:
718 723 after.setdefault(pos, []).append(l)
719 724 elif l.startswith(' >>> '): # python inlines
720 725 after.setdefault(pos, []).append(l)
721 726 prepos = pos
722 727 pos = n
723 728 if not inpython:
724 729 # we've just entered a Python block, add the header
725 730 inpython = True
726 731 addsalt(prepos, False) # make sure we report the exit code
727 732 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
728 733 addsalt(n, True)
729 734 script.append(l[2:])
730 735 elif l.startswith(' ... '): # python inlines
731 736 after.setdefault(prepos, []).append(l)
732 737 script.append(l[2:])
733 738 elif l.startswith(' $ '): # commands
734 739 if inpython:
735 740 script.append("EOF\n")
736 741 inpython = False
737 742 after.setdefault(pos, []).append(l)
738 743 prepos = pos
739 744 pos = n
740 745 addsalt(n, False)
741 746 cmd = l[4:].split()
742 747 if len(cmd) == 2 and cmd[0] == 'cd':
743 748 l = ' $ cd %s || exit 1\n' % cmd[1]
744 749 script.append(l[4:])
745 750 elif l.startswith(' > '): # continuations
746 751 after.setdefault(prepos, []).append(l)
747 752 script.append(l[4:])
748 753 elif l.startswith(' '): # results
749 754 # queue up a list of expected results
750 755 expected.setdefault(pos, []).append(l[2:])
751 756 else:
752 757 if inpython:
753 758 script.append("EOF\n")
754 759 inpython = False
755 760 # non-command/result - queue up for merged output
756 761 after.setdefault(pos, []).append(l)
757 762
758 763 if inpython:
759 764 script.append("EOF\n")
760 765 if skipping is not None:
761 766 after.setdefault(pos, []).append(' !!! missing #endif\n')
762 767 addsalt(n + 1, False)
763 768
764 769 # Write out the script and execute it
765 770 fd, name = tempfile.mkstemp(suffix='hg-tst')
766 771 try:
767 772 for l in script:
768 773 os.write(fd, l)
769 774 os.close(fd)
770 775
771 776 cmd = '%s "%s"' % (options.shell, name)
772 777 vlog("# Running", cmd)
773 778 exitcode, output = run(cmd, wd, options, replacements, env)
774 779 # do not merge output if skipped, return hghave message instead
775 780 # similarly, with --debug, output is None
776 781 if exitcode == SKIPPED_STATUS or output is None:
777 782 return exitcode, output
778 783 finally:
779 784 os.remove(name)
780 785
781 786 # Merge the script output back into a unified test
782 787
783 788 pos = -1
784 789 postout = []
785 790 ret = 0
786 791 for l in output:
787 792 lout, lcmd = l, None
788 793 if salt in l:
789 794 lout, lcmd = l.split(salt, 1)
790 795
791 796 if lout:
792 797 if not lout.endswith('\n'):
793 798 lout += ' (no-eol)\n'
794 799
795 800 # find the expected output at the current position
796 801 el = None
797 802 if pos in expected and expected[pos]:
798 803 el = expected[pos].pop(0)
799 804
800 805 if linematch(el, lout):
801 806 postout.append(" " + el)
802 807 else:
803 808 if needescape(lout):
804 809 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
805 810 postout.append(" " + lout) # let diff deal with it
806 811
807 812 if lcmd:
808 813 # add on last return code
809 814 ret = int(lcmd.split()[1])
810 815 if ret != 0:
811 816 postout.append(" [%s]\n" % ret)
812 817 if pos in after:
813 818 # merge in non-active test bits
814 819 postout += after.pop(pos)
815 820 pos = int(lcmd.split()[0])
816 821
817 822 if pos in after:
818 823 postout += after.pop(pos)
819 824
820 825 return exitcode, postout
821 826
822 827 wifexited = getattr(os, "WIFEXITED", lambda x: False)
823 828 def run(cmd, wd, options, replacements, env):
824 829 """Run command in a sub-process, capturing the output (stdout and stderr).
825 830 Return a tuple (exitcode, output). output is None in debug mode."""
826 831 # TODO: Use subprocess.Popen if we're running on Python 2.4
827 832 if options.debug:
828 833 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
829 834 ret = proc.wait()
830 835 return (ret, None)
831 836
832 837 proc = Popen4(cmd, wd, options.timeout, env)
833 838 def cleanup():
834 839 terminate(proc)
835 840 ret = proc.wait()
836 841 if ret == 0:
837 842 ret = signal.SIGTERM << 8
838 843 killdaemons(env['DAEMON_PIDS'])
839 844 return ret
840 845
841 846 output = ''
842 847 proc.tochild.close()
843 848
844 849 try:
845 850 output = proc.fromchild.read()
846 851 except KeyboardInterrupt:
847 852 vlog('# Handling keyboard interrupt')
848 853 cleanup()
849 854 raise
850 855
851 856 ret = proc.wait()
852 857 if wifexited(ret):
853 858 ret = os.WEXITSTATUS(ret)
854 859
855 860 if proc.timeout:
856 861 ret = 'timeout'
857 862
858 863 if ret:
859 864 killdaemons(env['DAEMON_PIDS'])
860 865
861 866 for s, r in replacements:
862 867 output = re.sub(s, r, output)
863 868 return ret, output.splitlines(True)
864 869
865 870 def runone(options, test):
866 871 '''returns a result element: (code, test, msg)'''
867 872
868 873 def skip(msg):
869 874 if options.verbose:
870 875 log("\nSkipping %s: %s" % (testpath, msg))
871 876 return 's', test, msg
872 877
873 878 def fail(msg, ret):
874 879 if not options.nodiff:
875 880 log("\nERROR: %s %s" % (testpath, msg))
876 881 if (not ret and options.interactive
877 882 and os.path.exists(testpath + ".err")):
878 883 iolock.acquire()
879 884 print "Accept this change? [n] ",
880 885 answer = sys.stdin.readline().strip()
881 886 iolock.release()
882 887 if answer.lower() in "y yes".split():
883 888 if test.endswith(".t"):
884 889 rename(testpath + ".err", testpath)
885 890 else:
886 891 rename(testpath + ".err", testpath + ".out")
887 892 return '.', test, ''
888 893 return '!', test, msg
889 894
890 895 def success():
891 896 return '.', test, ''
892 897
893 898 def ignore(msg):
894 899 return 'i', test, msg
895 900
896 901 def describe(ret):
897 902 if ret < 0:
898 903 return 'killed by signal %d' % -ret
899 904 return 'returned error code %d' % ret
900 905
901 906 testpath = os.path.join(TESTDIR, test)
902 907 err = os.path.join(TESTDIR, test + ".err")
903 908 lctest = test.lower()
904 909
905 910 if not os.path.exists(testpath):
906 911 return skip("doesn't exist")
907 912
908 913 if not (options.whitelisted and test in options.whitelisted):
909 914 if options.blacklist and test in options.blacklist:
910 915 return skip("blacklisted")
911 916
912 917 if options.retest and not os.path.exists(test + ".err"):
913 918 ignore("not retesting")
914 919 return None
915 920
916 921 if options.keywords:
917 922 fp = open(test)
918 923 t = fp.read().lower() + test.lower()
919 924 fp.close()
920 925 for k in options.keywords.lower().split():
921 926 if k in t:
922 927 break
923 928 else:
924 929 ignore("doesn't match keyword")
925 930 return None
926 931
927 932 for ext, func, out in testtypes:
928 933 if lctest.startswith("test-") and lctest.endswith(ext):
929 934 runner = func
930 935 ref = os.path.join(TESTDIR, test + out)
931 936 break
932 937 else:
933 938 return skip("unknown test type")
934 939
935 940 vlog("# Test", test)
936 941
937 942 if os.path.exists(err):
938 943 os.remove(err) # Remove any previous output files
939 944
940 945 # Make a tmp subdirectory to work in
941 946 testtmp = os.path.join(HGTMP, os.path.basename(test))
942 947 os.mkdir(testtmp)
943 948
944 949 replacements = [
945 950 (r':%s\b' % options.port, ':$HGPORT'),
946 951 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
947 952 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
948 953 ]
949 954 if os.name == 'nt':
950 955 replacements.append(
951 956 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
952 957 c in '/\\' and r'[/\\]' or
953 958 c.isdigit() and c or
954 959 '\\' + c
955 960 for c in testtmp), '$TESTTMP'))
956 961 else:
957 962 replacements.append((re.escape(testtmp), '$TESTTMP'))
958 963
959 964 env = createenv(options, testtmp)
960 965 createhgrc(env['HGRCPATH'], options)
961 966
962 967 if options.time:
963 968 starttime = time.time()
964 969 ret, out = runner(testpath, testtmp, options, replacements, env)
965 970 if options.time:
966 971 endtime = time.time()
967 972 times.append((test, endtime - starttime))
968 973 vlog("# Ret was:", ret)
969 974
970 975 killdaemons(env['DAEMON_PIDS'])
971 976
972 977 skipped = (ret == SKIPPED_STATUS)
973 978
974 979 # If we're not in --debug mode and reference output file exists,
975 980 # check test output against it.
976 981 if options.debug:
977 982 refout = None # to match "out is None"
978 983 elif os.path.exists(ref):
979 984 f = open(ref, "r")
980 985 refout = f.read().splitlines(True)
981 986 f.close()
982 987 else:
983 988 refout = []
984 989
985 990 if (ret != 0 or out != refout) and not skipped and not options.debug:
986 991 # Save errors to a file for diagnosis
987 992 f = open(err, "wb")
988 993 for line in out:
989 994 f.write(line)
990 995 f.close()
991 996
992 997 if skipped:
993 998 if out is None: # debug mode: nothing to parse
994 999 missing = ['unknown']
995 1000 failed = None
996 1001 else:
997 1002 missing, failed = parsehghaveoutput(out)
998 1003 if not missing:
999 1004 missing = ['irrelevant']
1000 1005 if failed:
1001 1006 result = fail("hghave failed checking for %s" % failed[-1], ret)
1002 1007 skipped = False
1003 1008 else:
1004 1009 result = skip(missing[-1])
1005 1010 elif ret == 'timeout':
1006 1011 result = fail("timed out", ret)
1007 1012 elif out != refout:
1008 1013 if not options.nodiff:
1009 1014 iolock.acquire()
1010 1015 if options.view:
1011 1016 os.system("%s %s %s" % (options.view, ref, err))
1012 1017 else:
1013 1018 showdiff(refout, out, ref, err)
1014 1019 iolock.release()
1015 1020 if ret:
1016 1021 result = fail("output changed and " + describe(ret), ret)
1017 1022 else:
1018 1023 result = fail("output changed", ret)
1019 1024 elif ret:
1020 1025 result = fail(describe(ret), ret)
1021 1026 else:
1022 1027 result = success()
1023 1028
1024 1029 if not options.verbose:
1025 1030 iolock.acquire()
1026 1031 sys.stdout.write(result[0])
1027 1032 sys.stdout.flush()
1028 1033 iolock.release()
1029 1034
1030 1035 if not options.keep_tmpdir:
1031 1036 shutil.rmtree(testtmp, True)
1032 1037 return result
1033 1038
1034 1039 _hgpath = None
1035 1040
1036 1041 def _gethgpath():
1037 1042 """Return the path to the mercurial package that is actually found by
1038 1043 the current Python interpreter."""
1039 1044 global _hgpath
1040 1045 if _hgpath is not None:
1041 1046 return _hgpath
1042 1047
1043 1048 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1044 1049 pipe = os.popen(cmd % PYTHON)
1045 1050 try:
1046 1051 _hgpath = pipe.read().strip()
1047 1052 finally:
1048 1053 pipe.close()
1049 1054 return _hgpath
1050 1055
1051 1056 def _checkhglib(verb):
1052 1057 """Ensure that the 'mercurial' package imported by python is
1053 1058 the one we expect it to be. If not, print a warning to stderr."""
1054 1059 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1055 1060 actualhg = _gethgpath()
1056 1061 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1057 1062 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1058 1063 ' (expected %s)\n'
1059 1064 % (verb, actualhg, expecthg))
1060 1065
1061 1066 def runchildren(options, tests):
1062 1067 if INST:
1063 1068 installhg(options)
1064 1069 _checkhglib("Testing")
1065 1070 else:
1066 1071 usecorrectpython()
1067 1072
1068 1073 optcopy = dict(options.__dict__)
1069 1074 optcopy['jobs'] = 1
1070 1075
1071 1076 # Because whitelist has to override keyword matches, we have to
1072 1077 # actually load the whitelist in the children as well, so we allow
1073 1078 # the list of whitelist files to pass through and be parsed in the
1074 1079 # children, but not the dict of whitelisted tests resulting from
1075 1080 # the parse, used here to override blacklisted tests.
1076 1081 whitelist = optcopy['whitelisted'] or []
1077 1082 del optcopy['whitelisted']
1078 1083
1079 1084 blacklist = optcopy['blacklist'] or []
1080 1085 del optcopy['blacklist']
1081 1086 blacklisted = []
1082 1087
1083 1088 if optcopy['with_hg'] is None:
1084 1089 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1085 1090 optcopy.pop('anycoverage', None)
1086 1091
1087 1092 opts = []
1088 1093 for opt, value in optcopy.iteritems():
1089 1094 name = '--' + opt.replace('_', '-')
1090 1095 if value is True:
1091 1096 opts.append(name)
1092 1097 elif isinstance(value, list):
1093 1098 for v in value:
1094 1099 opts.append(name + '=' + str(v))
1095 1100 elif value is not None:
1096 1101 opts.append(name + '=' + str(value))
1097 1102
1098 1103 tests.reverse()
1099 1104 jobs = [[] for j in xrange(options.jobs)]
1100 1105 while tests:
1101 1106 for job in jobs:
1102 1107 if not tests:
1103 1108 break
1104 1109 test = tests.pop()
1105 1110 if test not in whitelist and test in blacklist:
1106 1111 blacklisted.append(test)
1107 1112 else:
1108 1113 job.append(test)
1109 1114
1110 1115 waitq = queue.Queue()
1111 1116
1112 1117 # windows lacks os.wait, so we must emulate it
1113 1118 def waitfor(proc, rfd):
1114 1119 fp = os.fdopen(rfd, 'rb')
1115 1120 return lambda: waitq.put((proc.pid, proc.wait(), fp))
1116 1121
1117 1122 for j, job in enumerate(jobs):
1118 1123 if not job:
1119 1124 continue
1120 1125 rfd, wfd = os.pipe()
1121 1126 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1122 1127 childtmp = os.path.join(HGTMP, 'child%d' % j)
1123 1128 childopts += ['--tmpdir', childtmp]
1124 1129 if options.keep_tmpdir:
1125 1130 childopts.append('--keep-tmpdir')
1126 1131 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1127 1132 vlog(' '.join(cmdline))
1128 1133 proc = subprocess.Popen(cmdline, executable=cmdline[0])
1129 1134 threading.Thread(target=waitfor(proc, rfd)).start()
1130 1135 os.close(wfd)
1131 1136 signal.signal(signal.SIGINT, signal.SIG_IGN)
1132 1137 failures = 0
1133 1138 passed, skipped, failed = 0, 0, 0
1134 1139 skips = []
1135 1140 fails = []
1136 1141 for job in jobs:
1137 1142 if not job:
1138 1143 continue
1139 1144 pid, status, fp = waitq.get()
1140 1145 try:
1141 1146 childresults = pickle.load(fp)
1142 1147 except (pickle.UnpicklingError, EOFError):
1143 1148 sys.exit(255)
1144 1149 else:
1145 1150 passed += len(childresults['.'])
1146 1151 skipped += len(childresults['s'])
1147 1152 failed += len(childresults['!'])
1148 1153 skips.extend(childresults['s'])
1149 1154 fails.extend(childresults['!'])
1150 1155 if options.time:
1151 1156 childtimes = pickle.load(fp)
1152 1157 times.extend(childtimes)
1153 1158
1154 1159 vlog('pid %d exited, status %d' % (pid, status))
1155 1160 failures |= status
1156 1161 print
1157 1162 skipped += len(blacklisted)
1158 1163 if not options.noskips:
1159 1164 for s in skips:
1160 1165 print "Skipped %s: %s" % (s[0], s[1])
1161 1166 for s in blacklisted:
1162 1167 print "Skipped %s: blacklisted" % s
1163 1168 for s in fails:
1164 1169 print "Failed %s: %s" % (s[0], s[1])
1165 1170
1166 1171 _checkhglib("Tested")
1167 1172 print "# Ran %d tests, %d skipped, %d failed." % (
1168 1173 passed + failed, skipped, failed)
1169 1174
1170 1175 if options.time:
1171 1176 outputtimes(options)
1172 1177 if options.anycoverage:
1173 1178 outputcoverage(options)
1174 1179 sys.exit(failures != 0)
1175 1180
1176 1181 results = {'.':[], '!':[], 's':[], 'i':[]}
1177 1182 resultslock = threading.Lock()
1178 1183 times = []
1179 1184 iolock = threading.Lock()
1180 1185
1181 1186 def runqueue(options, tests):
1182 1187 for test in tests:
1183 1188 code, test, msg = runone(options, test)
1184 1189 resultslock.acquire()
1185 1190 results[code].append((test, msg))
1186 1191 resultslock.release()
1187 1192
1188 1193 if options.first and code not in '.si':
1189 1194 break
1190 1195
1191 1196 def runtests(options, tests):
1192 1197 try:
1193 1198 if INST:
1194 1199 installhg(options)
1195 1200 _checkhglib("Testing")
1196 1201 else:
1197 1202 usecorrectpython()
1198 1203
1199 1204 if options.restart:
1200 1205 orig = list(tests)
1201 1206 while tests:
1202 1207 if os.path.exists(tests[0] + ".err"):
1203 1208 break
1204 1209 tests.pop(0)
1205 1210 if not tests:
1206 1211 print "running all tests"
1207 1212 tests = orig
1208 1213
1209 1214 runqueue(options, tests)
1210 1215
1211 1216 failed = len(results['!'])
1212 1217 tested = len(results['.']) + failed
1213 1218 skipped = len(results['s'])
1214 1219 ignored = len(results['i'])
1215 1220
1216 1221 if options.child:
1217 1222 fp = os.fdopen(options.child, 'wb')
1218 1223 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1219 1224 if options.time:
1220 1225 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1221 1226 fp.close()
1222 1227 else:
1223 1228 print
1224 1229 for s in results['s']:
1225 1230 print "Skipped %s: %s" % s
1226 1231 for s in results['!']:
1227 1232 print "Failed %s: %s" % s
1228 1233 _checkhglib("Tested")
1229 1234 print "# Ran %d tests, %d skipped, %d failed." % (
1230 1235 tested, skipped + ignored, failed)
1231 1236 if options.time:
1232 1237 outputtimes(options)
1233 1238
1234 1239 if options.anycoverage:
1235 1240 outputcoverage(options)
1236 1241 except KeyboardInterrupt:
1237 1242 failed = True
1238 1243 if not options.child:
1239 1244 print "\ninterrupted!"
1240 1245
1241 1246 if failed:
1242 1247 sys.exit(1)
1243 1248
1244 1249 testtypes = [('.py', pytest, '.out'),
1245 1250 ('.t', tsttest, '')]
1246 1251
1247 1252 def main():
1248 1253 (options, args) = parseargs()
1249 1254 if not options.child:
1250 1255 os.umask(022)
1251 1256
1252 1257 checktools()
1253 1258
1254 1259 if len(args) == 0:
1255 1260 args = sorted(t for t in os.listdir(".")
1256 1261 if t.startswith("test-")
1257 1262 and (t.endswith(".py") or t.endswith(".t")))
1258 1263
1259 1264 tests = args
1260 1265
1261 1266 if options.random:
1262 1267 random.shuffle(tests)
1263 1268
1264 1269 if 'PYTHONHASHSEED' not in os.environ:
1265 1270 # use a random python hash seed all the time
1266 1271 # we do the randomness ourself to know what seed is used
1267 1272 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1268 1273 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1269 1274
1270 1275 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1271 1276 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1272 1277 if options.tmpdir:
1273 1278 if not options.child:
1274 1279 options.keep_tmpdir = True
1275 1280 tmpdir = options.tmpdir
1276 1281 if os.path.exists(tmpdir):
1277 1282 # Meaning of tmpdir has changed since 1.3: we used to create
1278 1283 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1279 1284 # tmpdir already exists.
1280 1285 sys.exit("error: temp dir %r already exists" % tmpdir)
1281 1286
1282 1287 # Automatically removing tmpdir sounds convenient, but could
1283 1288 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1284 1289 # or "--tmpdir=$HOME".
1285 1290 #vlog("# Removing temp dir", tmpdir)
1286 1291 #shutil.rmtree(tmpdir)
1287 1292 os.makedirs(tmpdir)
1288 1293 else:
1289 1294 d = None
1290 1295 if os.name == 'nt':
1291 1296 # without this, we get the default temp dir location, but
1292 1297 # in all lowercase, which causes troubles with paths (issue3490)
1293 1298 d = os.getenv('TMP')
1294 1299 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1295 1300 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1296 1301
1297 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1298 os.environ["HGMERGE"] = "internal:merge"
1299 os.environ["HGUSER"] = "test"
1300 os.environ["HGENCODING"] = "ascii"
1301 os.environ["HGENCODINGMODE"] = "strict"
1302
1303 1302 if options.with_hg:
1304 1303 INST = None
1305 1304 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1306 1305
1307 1306 # This looks redundant with how Python initializes sys.path from
1308 1307 # the location of the script being executed. Needed because the
1309 1308 # "hg" specified by --with-hg is not the only Python script
1310 1309 # executed in the test suite that needs to import 'mercurial'
1311 1310 # ... which means it's not really redundant at all.
1312 1311 PYTHONDIR = BINDIR
1313 1312 else:
1314 1313 INST = os.path.join(HGTMP, "install")
1315 1314 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1316 1315 PYTHONDIR = os.path.join(INST, "lib", "python")
1317 1316
1318 1317 os.environ["BINDIR"] = BINDIR
1319 1318 os.environ["PYTHON"] = PYTHON
1320 1319
1321 1320 if not options.child:
1322 1321 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1323 1322 os.environ["PATH"] = os.pathsep.join(path)
1324 1323
1325 1324 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1326 1325 # can run .../tests/run-tests.py test-foo where test-foo
1327 1326 # adds an extension to HGRC
1328 1327 pypath = [PYTHONDIR, TESTDIR]
1329 1328 # We have to augment PYTHONPATH, rather than simply replacing
1330 1329 # it, in case external libraries are only available via current
1331 1330 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1332 1331 # are in /opt/subversion.)
1333 1332 oldpypath = os.environ.get(IMPL_PATH)
1334 1333 if oldpypath:
1335 1334 pypath.append(oldpypath)
1336 1335 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1337 1336
1338 1337 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1339 1338
1340 1339 vlog("# Using TESTDIR", TESTDIR)
1341 1340 vlog("# Using HGTMP", HGTMP)
1342 1341 vlog("# Using PATH", os.environ["PATH"])
1343 1342 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1344 1343
1345 1344 try:
1346 1345 if len(tests) > 1 and options.jobs > 1:
1347 1346 runchildren(options, tests)
1348 1347 else:
1349 1348 runtests(options, tests)
1350 1349 finally:
1351 1350 time.sleep(.1)
1352 1351 cleanup(options)
1353 1352
1354 1353 if __name__ == '__main__':
1355 1354 main()
General Comments 0
You need to be logged in to leave comments. Login now