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