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