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