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