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