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