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