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