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