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