##// END OF EJS Templates
run-tests: move HGTMP out of a global
Gregory Szorc -
r21342:1ad7aabb default
parent child Browse files
Show More
@@ -1,1418 +1,1419 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 def cleanup(options):
391 def cleanup(runner, options):
392 392 if not options.keep_tmpdir:
393 vlog("# Cleaning up HGTMP", HGTMP)
394 shutil.rmtree(HGTMP, True)
393 vlog("# Cleaning up HGTMP", runner.hgtmp)
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 401 def usecorrectpython():
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 408 mypython = os.path.join(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 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
462 'compiler': compiler, 'base': os.path.join(runner.hgtmp, "build"),
463 463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
464 464 'nohome': nohome, 'logfile': installerrs})
465 465 vlog("# Running", cmd)
466 466 if os.system(cmd) == 0:
467 467 if not options.verbose:
468 468 os.remove(installerrs)
469 469 else:
470 470 f = open(installerrs)
471 471 for line in f:
472 472 print line,
473 473 f.close()
474 474 sys.exit(1)
475 475 os.chdir(runner.testdir)
476 476
477 477 usecorrectpython()
478 478
479 479 if options.py3k_warnings and not options.anycoverage:
480 480 vlog("# Updating hg command to enable Py3k Warnings switch")
481 481 f = open(os.path.join(BINDIR, 'hg'), 'r')
482 482 lines = [line.rstrip() for line in f]
483 483 lines[0] += ' -3'
484 484 f.close()
485 485 f = open(os.path.join(BINDIR, 'hg'), 'w')
486 486 for line in lines:
487 487 f.write(line + '\n')
488 488 f.close()
489 489
490 490 hgbat = os.path.join(BINDIR, 'hg.bat')
491 491 if os.path.isfile(hgbat):
492 492 # hg.bat expects to be put in bin/scripts while run-tests.py
493 493 # installation layout put it in bin/ directly. Fix it
494 494 f = open(hgbat, 'rb')
495 495 data = f.read()
496 496 f.close()
497 497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
498 498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
499 499 '"%~dp0python" "%~dp0hg" %*')
500 500 f = open(hgbat, 'wb')
501 501 f.write(data)
502 502 f.close()
503 503 else:
504 504 print 'WARNING: cannot fix hg.bat reference to python.exe'
505 505
506 506 if options.anycoverage:
507 507 custom = os.path.join(runner.testdir, 'sitecustomize.py')
508 508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
509 509 vlog('# Installing coverage trigger to %s' % target)
510 510 shutil.copyfile(custom, target)
511 511 rc = os.path.join(runner.testdir, '.coveragerc')
512 512 vlog('# Installing coverage rc to %s' % rc)
513 513 os.environ['COVERAGE_PROCESS_START'] = rc
514 514 fn = os.path.join(INST, '..', '.coverage')
515 515 os.environ['COVERAGE_FILE'] = fn
516 516
517 517 def outputtimes(options):
518 518 vlog('# Producing time report')
519 519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
520 520 cols = '%7.3f %s'
521 521 print '\n%-7s %s' % ('Time', 'Test')
522 522 for test, timetaken in times:
523 523 print cols % (timetaken, test)
524 524
525 525 def outputcoverage(runner, options):
526 526
527 527 vlog('# Producing coverage report')
528 528 os.chdir(PYTHONDIR)
529 529
530 530 def covrun(*args):
531 531 cmd = 'coverage %s' % ' '.join(args)
532 532 vlog('# Running: %s' % cmd)
533 533 os.system(cmd)
534 534
535 535 covrun('-c')
536 536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, runner.testdir])
537 537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
538 538 if options.htmlcov:
539 539 htmldir = os.path.join(runner.testdir, 'htmlcov')
540 540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
541 541 if options.annotate:
542 542 adir = os.path.join(runner.testdir, 'annotated')
543 543 if not os.path.isdir(adir):
544 544 os.mkdir(adir)
545 545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
546 546
547 547 class Test(object):
548 548 """Encapsulates a single, runnable test.
549 549
550 550 Test instances can be run multiple times via run(). However, multiple
551 551 runs cannot be run concurrently.
552 552 """
553 553
554 def __init__(self, testdir, test, options, count, refpath):
555 path = os.path.join(testdir, test)
556 errpath = os.path.join(testdir, '%s.err' % test)
554 def __init__(self, runner, test, options, count, refpath):
555 path = os.path.join(runner.testdir, test)
556 errpath = os.path.join(runner.testdir, '%s.err' % test)
557 557
558 self._testdir = testdir
558 self._testdir = runner.testdir
559 559 self._test = test
560 560 self._path = path
561 561 self._options = options
562 562 self._count = count
563 563 self._daemonpids = []
564 564 self._refpath = refpath
565 565 self._errpath = errpath
566 566
567 567 # If we're not in --debug mode and reference output file exists,
568 568 # check test output against it.
569 569 if options.debug:
570 570 self._refout = None # to match "out is None"
571 571 elif os.path.exists(refpath):
572 572 f = open(refpath, 'r')
573 573 self._refout = f.read().splitlines(True)
574 574 f.close()
575 575 else:
576 576 self._refout = []
577 577
578 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
578 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
579 579 os.mkdir(self._threadtmp)
580 580
581 581 def cleanup(self):
582 582 for entry in self._daemonpids:
583 583 killdaemons(entry)
584 584
585 585 if self._threadtmp and not self._options.keep_tmpdir:
586 586 shutil.rmtree(self._threadtmp, True)
587 587
588 588 def run(self):
589 589 if not os.path.exists(self._path):
590 590 return self.skip("Doesn't exist")
591 591
592 592 options = self._options
593 593 if not (options.whitelisted and self._test in options.whitelisted):
594 594 if options.blacklist and self._test in options.blacklist:
595 595 return self.skip('blacklisted')
596 596
597 597 if options.retest and not os.path.exists('%s.err' % self._test):
598 598 return self.ignore('not retesting')
599 599
600 600 if options.keywords:
601 601 f = open(self._test)
602 602 t = f.read().lower() + self._test.lower()
603 603 f.close()
604 604 for k in options.keywords.lower().split():
605 605 if k in t:
606 606 break
607 607 else:
608 608 return self.ignore("doesn't match keyword")
609 609
610 610 if not os.path.basename(self._test.lower()).startswith('test-'):
611 611 return self.skip('not a test file')
612 612
613 613 # Remove any previous output files.
614 614 if os.path.exists(self._errpath):
615 615 os.remove(self._errpath)
616 616
617 617 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
618 618 os.mkdir(testtmp)
619 619 replacements, port = self._getreplacements(testtmp)
620 620 env = self._getenv(testtmp, port)
621 621 self._daemonpids.append(env['DAEMON_PIDS'])
622 622 createhgrc(env['HGRCPATH'], options)
623 623
624 624 vlog('# Test', self._test)
625 625
626 626 starttime = time.time()
627 627 try:
628 628 ret, out = self._run(testtmp, replacements, env)
629 629 duration = time.time() - starttime
630 630 except KeyboardInterrupt:
631 631 duration = time.time() - starttime
632 632 log('INTERRUPTED: %s (after %d seconds)' % (self._test, duration))
633 633 raise
634 634 except Exception, e:
635 635 return self.fail('Exception during execution: %s' % e, 255)
636 636
637 637 killdaemons(env['DAEMON_PIDS'])
638 638
639 639 if not options.keep_tmpdir:
640 640 shutil.rmtree(testtmp)
641 641
642 642 def describe(ret):
643 643 if ret < 0:
644 644 return 'killed by signal: %d' % -ret
645 645 return 'returned error code %d' % ret
646 646
647 647 skipped = False
648 648
649 649 if ret == SKIPPED_STATUS:
650 650 if out is None: # Debug mode, nothing to parse.
651 651 missing = ['unknown']
652 652 failed = None
653 653 else:
654 654 missing, failed = parsehghaveoutput(out)
655 655
656 656 if not missing:
657 657 missing = ['irrelevant']
658 658
659 659 if failed:
660 660 res = self.fail('hg have failed checking for %s' % failed[-1],
661 661 ret)
662 662 else:
663 663 skipped = True
664 664 res = self.skip(missing[-1])
665 665 elif ret == 'timeout':
666 666 res = self.fail('timed out', ret)
667 667 elif out != self._refout:
668 668 info = {}
669 669 if not options.nodiff:
670 670 iolock.acquire()
671 671 if options.view:
672 672 os.system("%s %s %s" % (options.view, self._refpath,
673 673 self._errpath))
674 674 else:
675 675 info = showdiff(self._refout, out, self._refpath,
676 676 self._errpath)
677 677 iolock.release()
678 678 msg = ''
679 679 if info.get('servefail'):
680 680 msg += 'serve failed and '
681 681 if ret:
682 682 msg += 'output changed and ' + describe(ret)
683 683 else:
684 684 msg += 'output changed'
685 685
686 686 res = self.fail(msg, ret)
687 687 elif ret:
688 688 res = self.fail(describe(ret), ret)
689 689 else:
690 690 res = self.success()
691 691
692 692 if (ret != 0 or out != self._refout) and not skipped \
693 693 and not options.debug:
694 694 f = open(self._errpath, 'wb')
695 695 for line in out:
696 696 f.write(line)
697 697 f.close()
698 698
699 699 vlog("# Ret was:", ret)
700 700
701 701 if not options.verbose:
702 702 iolock.acquire()
703 703 sys.stdout.write(res[0])
704 704 sys.stdout.flush()
705 705 iolock.release()
706 706
707 707 times.append((self._test, duration))
708 708
709 709 return res
710 710
711 711 def _run(self, testtmp, replacements, env):
712 712 # This should be implemented in child classes to run tests.
713 713 return self._skip('unknown test type')
714 714
715 715 def _getreplacements(self, testtmp):
716 716 port = self._options.port + self._count * 3
717 717 r = [
718 718 (r':%s\b' % port, ':$HGPORT'),
719 719 (r':%s\b' % (port + 1), ':$HGPORT1'),
720 720 (r':%s\b' % (port + 2), ':$HGPORT2'),
721 721 ]
722 722
723 723 if os.name == 'nt':
724 724 r.append(
725 725 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
726 726 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
727 727 for c in testtmp), '$TESTTMP'))
728 728 else:
729 729 r.append((re.escape(testtmp), '$TESTTMP'))
730 730
731 731 return r, port
732 732
733 733 def _getenv(self, testtmp, port):
734 734 env = os.environ.copy()
735 735 env['TESTTMP'] = testtmp
736 736 env['HOME'] = testtmp
737 737 env["HGPORT"] = str(port)
738 738 env["HGPORT1"] = str(port + 1)
739 739 env["HGPORT2"] = str(port + 2)
740 740 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
741 741 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
742 742 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
743 743 env["HGMERGE"] = "internal:merge"
744 744 env["HGUSER"] = "test"
745 745 env["HGENCODING"] = "ascii"
746 746 env["HGENCODINGMODE"] = "strict"
747 747
748 748 # Reset some environment variables to well-known values so that
749 749 # the tests produce repeatable output.
750 750 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
751 751 env['TZ'] = 'GMT'
752 752 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
753 753 env['COLUMNS'] = '80'
754 754 env['TERM'] = 'xterm'
755 755
756 756 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
757 757 'NO_PROXY').split():
758 758 if k in env:
759 759 del env[k]
760 760
761 761 # unset env related to hooks
762 762 for k in env.keys():
763 763 if k.startswith('HG_'):
764 764 del env[k]
765 765
766 766 return env
767 767
768 768 def success(self):
769 769 return '.', self._test, ''
770 770
771 771 def fail(self, msg, ret):
772 772 warned = ret is False
773 773 if not self._options.nodiff:
774 774 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
775 775 msg))
776 776 if (not ret and self._options.interactive and
777 777 os.path.exists(self._errpath)):
778 778 iolock.acquire()
779 779 print 'Accept this change? [n] ',
780 780 answer = sys.stdin.readline().strip()
781 781 iolock.release()
782 782 if answer.lower() in ('y', 'yes').split():
783 783 if self._test.endswith('.t'):
784 784 rename(self._errpath, self._testpath)
785 785 else:
786 786 rename(self._errpath, '%s.out' % self._testpath)
787 787
788 788 return '.', self._test, ''
789 789
790 790 return warned and '~' or '!', self._test, msg
791 791
792 792 def skip(self, msg):
793 793 if self._options.verbose:
794 794 log("\nSkipping %s: %s" % (self._path, msg))
795 795
796 796 return 's', self._test, msg
797 797
798 798 def ignore(self, msg):
799 799 return 'i', self._test, msg
800 800
801 801 class PythonTest(Test):
802 802 """A Python-based test."""
803 803 def _run(self, testtmp, replacements, env):
804 804 py3kswitch = self._options.py3k_warnings and ' -3' or ''
805 805 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
806 806 vlog("# Running", cmd)
807 807 if os.name == 'nt':
808 808 replacements.append((r'\r\n', '\n'))
809 809 return run(cmd, testtmp, self._options, replacements, env)
810 810
811 811
812 812 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
813 813 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
814 814 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
815 815 escapemap.update({'\\': '\\\\', '\r': r'\r'})
816 816 def escapef(m):
817 817 return escapemap[m.group(0)]
818 818 def stringescape(s):
819 819 return escapesub(escapef, s)
820 820
821 821 class TTest(Test):
822 822 """A "t test" is a test backed by a .t file."""
823 823
824 824 def _run(self, testtmp, replacements, env):
825 825 f = open(self._path)
826 826 lines = f.readlines()
827 827 f.close()
828 828
829 829 salt, script, after, expected = self._parsetest(lines, testtmp)
830 830
831 831 # Write out the generated script.
832 832 fname = '%s.sh' % testtmp
833 833 f = open(fname, 'w')
834 834 for l in script:
835 835 f.write(l)
836 836 f.close()
837 837
838 838 cmd = '%s "%s"' % (self._options.shell, fname)
839 839 vlog("# Running", cmd)
840 840
841 841 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
842 842 # Do not merge output if skipped. Return hghave message instead.
843 843 # Similarly, with --debug, output is None.
844 844 if exitcode == SKIPPED_STATUS or output is None:
845 845 return exitcode, output
846 846
847 847 return self._processoutput(exitcode, output, salt, after, expected)
848 848
849 849 def _hghave(self, reqs, testtmp):
850 850 # TODO do something smarter when all other uses of hghave are gone.
851 851 tdir = self._testdir.replace('\\', '/')
852 852 proc = Popen4('%s -c "%s/hghave %s"' %
853 853 (self._options.shell, tdir, ' '.join(reqs)),
854 854 testtmp, 0)
855 855 stdout, stderr = proc.communicate()
856 856 ret = proc.wait()
857 857 if wifexited(ret):
858 858 ret = os.WEXITSTATUS(ret)
859 859 if ret == 2:
860 860 print stdout
861 861 sys.exit(1)
862 862
863 863 return ret == 0
864 864
865 865 def _parsetest(self, lines, testtmp):
866 866 # We generate a shell script which outputs unique markers to line
867 867 # up script results with our source. These markers include input
868 868 # line number and the last return code.
869 869 salt = "SALT" + str(time.time())
870 870 def addsalt(line, inpython):
871 871 if inpython:
872 872 script.append('%s %d 0\n' % (salt, line))
873 873 else:
874 874 script.append('echo %s %s $?\n' % (salt, line))
875 875
876 876 script = []
877 877
878 878 # After we run the shell script, we re-unify the script output
879 879 # with non-active parts of the source, with synchronization by our
880 880 # SALT line number markers. The after table contains the non-active
881 881 # components, ordered by line number.
882 882 after = {}
883 883
884 884 # Expected shell script output.
885 885 expected = {}
886 886
887 887 pos = prepos = -1
888 888
889 889 # True or False when in a true or false conditional section
890 890 skipping = None
891 891
892 892 # We keep track of whether or not we're in a Python block so we
893 893 # can generate the surrounding doctest magic.
894 894 inpython = False
895 895
896 896 if self._options.debug:
897 897 script.append('set -x\n')
898 898 if os.getenv('MSYSTEM'):
899 899 script.append('alias pwd="pwd -W"\n')
900 900
901 901 for n, l in enumerate(lines):
902 902 if not l.endswith('\n'):
903 903 l += '\n'
904 904 if l.startswith('#if'):
905 905 lsplit = l.split()
906 906 if len(lsplit) < 2 or lsplit[0] != '#if':
907 907 after.setdefault(pos, []).append(' !!! invalid #if\n')
908 908 if skipping is not None:
909 909 after.setdefault(pos, []).append(' !!! nested #if\n')
910 910 skipping = not self._hghave(lsplit[1:], testtmp)
911 911 after.setdefault(pos, []).append(l)
912 912 elif l.startswith('#else'):
913 913 if skipping is None:
914 914 after.setdefault(pos, []).append(' !!! missing #if\n')
915 915 skipping = not skipping
916 916 after.setdefault(pos, []).append(l)
917 917 elif l.startswith('#endif'):
918 918 if skipping is None:
919 919 after.setdefault(pos, []).append(' !!! missing #if\n')
920 920 skipping = None
921 921 after.setdefault(pos, []).append(l)
922 922 elif skipping:
923 923 after.setdefault(pos, []).append(l)
924 924 elif l.startswith(' >>> '): # python inlines
925 925 after.setdefault(pos, []).append(l)
926 926 prepos = pos
927 927 pos = n
928 928 if not inpython:
929 929 # We've just entered a Python block. Add the header.
930 930 inpython = True
931 931 addsalt(prepos, False) # Make sure we report the exit code.
932 932 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
933 933 addsalt(n, True)
934 934 script.append(l[2:])
935 935 elif l.startswith(' ... '): # python inlines
936 936 after.setdefault(prepos, []).append(l)
937 937 script.append(l[2:])
938 938 elif l.startswith(' $ '): # commands
939 939 if inpython:
940 940 script.append('EOF\n')
941 941 inpython = False
942 942 after.setdefault(pos, []).append(l)
943 943 prepos = pos
944 944 pos = n
945 945 addsalt(n, False)
946 946 cmd = l[4:].split()
947 947 if len(cmd) == 2 and cmd[0] == 'cd':
948 948 l = ' $ cd %s || exit 1\n' % cmd[1]
949 949 script.append(l[4:])
950 950 elif l.startswith(' > '): # continuations
951 951 after.setdefault(prepos, []).append(l)
952 952 script.append(l[4:])
953 953 elif l.startswith(' '): # results
954 954 # Queue up a list of expected results.
955 955 expected.setdefault(pos, []).append(l[2:])
956 956 else:
957 957 if inpython:
958 958 script.append('EOF\n')
959 959 inpython = False
960 960 # Non-command/result. Queue up for merged output.
961 961 after.setdefault(pos, []).append(l)
962 962
963 963 if inpython:
964 964 script.append('EOF\n')
965 965 if skipping is not None:
966 966 after.setdefault(pos, []).append(' !!! missing #endif\n')
967 967 addsalt(n + 1, False)
968 968
969 969 return salt, script, after, expected
970 970
971 971 def _processoutput(self, exitcode, output, salt, after, expected):
972 972 # Merge the script output back into a unified test.
973 973 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
974 974 if exitcode != 0:
975 975 warnonly = 3
976 976
977 977 pos = -1
978 978 postout = []
979 979 for l in output:
980 980 lout, lcmd = l, None
981 981 if salt in l:
982 982 lout, lcmd = l.split(salt, 1)
983 983
984 984 if lout:
985 985 if not lout.endswith('\n'):
986 986 lout += ' (no-eol)\n'
987 987
988 988 # Find the expected output at the current position.
989 989 el = None
990 990 if expected.get(pos, None):
991 991 el = expected[pos].pop(0)
992 992
993 993 r = TTest.linematch(el, lout)
994 994 if isinstance(r, str):
995 995 if r == '+glob':
996 996 lout = el[:-1] + ' (glob)\n'
997 997 r = '' # Warn only this line.
998 998 elif r == '-glob':
999 999 lout = ''.join(el.rsplit(' (glob)', 1))
1000 1000 r = '' # Warn only this line.
1001 1001 else:
1002 1002 log('\ninfo, unknown linematch result: %r\n' % r)
1003 1003 r = False
1004 1004 if r:
1005 1005 postout.append(' ' + el)
1006 1006 else:
1007 1007 if needescape(lout):
1008 1008 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
1009 1009 postout.append(' ' + lout) # Let diff deal with it.
1010 1010 if r != '': # If line failed.
1011 1011 warnonly = 3 # for sure not
1012 1012 elif warnonly == 1: # Is "not yet" and line is warn only.
1013 1013 warnonly = 2 # Yes do warn.
1014 1014
1015 1015 if lcmd:
1016 1016 # Add on last return code.
1017 1017 ret = int(lcmd.split()[1])
1018 1018 if ret != 0:
1019 1019 postout.append(' [%s]\n' % ret)
1020 1020 if pos in after:
1021 1021 # Merge in non-active test bits.
1022 1022 postout += after.pop(pos)
1023 1023 pos = int(lcmd.split()[0])
1024 1024
1025 1025 if pos in after:
1026 1026 postout += after.pop(pos)
1027 1027
1028 1028 if warnonly == 2:
1029 1029 exitcode = False # Set exitcode to warned.
1030 1030
1031 1031 return exitcode, postout
1032 1032
1033 1033 @staticmethod
1034 1034 def rematch(el, l):
1035 1035 try:
1036 1036 # use \Z to ensure that the regex matches to the end of the string
1037 1037 if os.name == 'nt':
1038 1038 return re.match(el + r'\r?\n\Z', l)
1039 1039 return re.match(el + r'\n\Z', l)
1040 1040 except re.error:
1041 1041 # el is an invalid regex
1042 1042 return False
1043 1043
1044 1044 @staticmethod
1045 1045 def globmatch(el, l):
1046 1046 # The only supported special characters are * and ? plus / which also
1047 1047 # matches \ on windows. Escaping of these characters is supported.
1048 1048 if el + '\n' == l:
1049 1049 if os.altsep:
1050 1050 # matching on "/" is not needed for this line
1051 1051 return '-glob'
1052 1052 return True
1053 1053 i, n = 0, len(el)
1054 1054 res = ''
1055 1055 while i < n:
1056 1056 c = el[i]
1057 1057 i += 1
1058 1058 if c == '\\' and el[i] in '*?\\/':
1059 1059 res += el[i - 1:i + 1]
1060 1060 i += 1
1061 1061 elif c == '*':
1062 1062 res += '.*'
1063 1063 elif c == '?':
1064 1064 res += '.'
1065 1065 elif c == '/' and os.altsep:
1066 1066 res += '[/\\\\]'
1067 1067 else:
1068 1068 res += re.escape(c)
1069 1069 return TTest.rematch(res, l)
1070 1070
1071 1071 @staticmethod
1072 1072 def linematch(el, l):
1073 1073 if el == l: # perfect match (fast)
1074 1074 return True
1075 1075 if el:
1076 1076 if el.endswith(" (esc)\n"):
1077 1077 el = el[:-7].decode('string-escape') + '\n'
1078 1078 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1079 1079 return True
1080 1080 if el.endswith(" (re)\n"):
1081 1081 return TTest.rematch(el[:-6], l)
1082 1082 if el.endswith(" (glob)\n"):
1083 1083 return TTest.globmatch(el[:-8], l)
1084 1084 if os.altsep and l.replace('\\', '/') == el:
1085 1085 return '+glob'
1086 1086 return False
1087 1087
1088 1088 def gettest(runner, test, options, count):
1089 1089 """Obtain a Test by looking at its filename.
1090 1090
1091 1091 Returns a Test instance. The Test may not be runnable if it doesn't map
1092 1092 to a known type.
1093 1093 """
1094 1094
1095 1095 lctest = test.lower()
1096 1096 refpath = os.path.join(runner.testdir, test)
1097 1097
1098 1098 testcls = Test
1099 1099
1100 1100 for ext, cls, out in testtypes:
1101 1101 if lctest.endswith(ext):
1102 1102 testcls = cls
1103 1103 refpath = os.path.join(runner.testdir, test + out)
1104 1104 break
1105 1105
1106 return testcls(runner.testdir, test, options, count, refpath)
1106 return testcls(runner, test, options, count, refpath)
1107 1107
1108 1108 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1109 1109 def run(cmd, wd, options, replacements, env):
1110 1110 """Run command in a sub-process, capturing the output (stdout and stderr).
1111 1111 Return a tuple (exitcode, output). output is None in debug mode."""
1112 1112 # TODO: Use subprocess.Popen if we're running on Python 2.4
1113 1113 if options.debug:
1114 1114 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1115 1115 ret = proc.wait()
1116 1116 return (ret, None)
1117 1117
1118 1118 proc = Popen4(cmd, wd, options.timeout, env)
1119 1119 def cleanup():
1120 1120 terminate(proc)
1121 1121 ret = proc.wait()
1122 1122 if ret == 0:
1123 1123 ret = signal.SIGTERM << 8
1124 1124 killdaemons(env['DAEMON_PIDS'])
1125 1125 return ret
1126 1126
1127 1127 output = ''
1128 1128 proc.tochild.close()
1129 1129
1130 1130 try:
1131 1131 output = proc.fromchild.read()
1132 1132 except KeyboardInterrupt:
1133 1133 vlog('# Handling keyboard interrupt')
1134 1134 cleanup()
1135 1135 raise
1136 1136
1137 1137 ret = proc.wait()
1138 1138 if wifexited(ret):
1139 1139 ret = os.WEXITSTATUS(ret)
1140 1140
1141 1141 if proc.timeout:
1142 1142 ret = 'timeout'
1143 1143
1144 1144 if ret:
1145 1145 killdaemons(env['DAEMON_PIDS'])
1146 1146
1147 1147 if abort:
1148 1148 raise KeyboardInterrupt()
1149 1149
1150 1150 for s, r in replacements:
1151 1151 output = re.sub(s, r, output)
1152 1152 return ret, output.splitlines(True)
1153 1153
1154 1154 _hgpath = None
1155 1155
1156 1156 def _gethgpath():
1157 1157 """Return the path to the mercurial package that is actually found by
1158 1158 the current Python interpreter."""
1159 1159 global _hgpath
1160 1160 if _hgpath is not None:
1161 1161 return _hgpath
1162 1162
1163 1163 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1164 1164 pipe = os.popen(cmd % PYTHON)
1165 1165 try:
1166 1166 _hgpath = pipe.read().strip()
1167 1167 finally:
1168 1168 pipe.close()
1169 1169 return _hgpath
1170 1170
1171 1171 def _checkhglib(verb):
1172 1172 """Ensure that the 'mercurial' package imported by python is
1173 1173 the one we expect it to be. If not, print a warning to stderr."""
1174 1174 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1175 1175 actualhg = _gethgpath()
1176 1176 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1177 1177 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1178 1178 ' (expected %s)\n'
1179 1179 % (verb, actualhg, expecthg))
1180 1180
1181 1181 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1182 1182 times = []
1183 1183 iolock = threading.Lock()
1184 1184 abort = False
1185 1185
1186 1186 def scheduletests(runner, options, tests):
1187 1187 jobs = options.jobs
1188 1188 done = queue.Queue()
1189 1189 running = 0
1190 1190 count = 0
1191 1191 global abort
1192 1192
1193 1193 def job(test, count):
1194 1194 try:
1195 1195 t = gettest(runner, test, options, count)
1196 1196 done.put(t.run())
1197 1197 t.cleanup()
1198 1198 except KeyboardInterrupt:
1199 1199 pass
1200 1200 except: # re-raises
1201 1201 done.put(('!', test, 'run-test raised an error, see traceback'))
1202 1202 raise
1203 1203
1204 1204 try:
1205 1205 while tests or running:
1206 1206 if not done.empty() or running == jobs or not tests:
1207 1207 try:
1208 1208 code, test, msg = done.get(True, 1)
1209 1209 results[code].append((test, msg))
1210 1210 if options.first and code not in '.si':
1211 1211 break
1212 1212 except queue.Empty:
1213 1213 continue
1214 1214 running -= 1
1215 1215 if tests and not running == jobs:
1216 1216 test = tests.pop(0)
1217 1217 if options.loop:
1218 1218 tests.append(test)
1219 1219 t = threading.Thread(target=job, name=test, args=(test, count))
1220 1220 t.start()
1221 1221 running += 1
1222 1222 count += 1
1223 1223 except KeyboardInterrupt:
1224 1224 abort = True
1225 1225
1226 1226 def runtests(runner, options, tests):
1227 1227 try:
1228 1228 if INST:
1229 1229 installhg(runner, options)
1230 1230 _checkhglib("Testing")
1231 1231 else:
1232 1232 usecorrectpython()
1233 1233
1234 1234 if options.restart:
1235 1235 orig = list(tests)
1236 1236 while tests:
1237 1237 if os.path.exists(tests[0] + ".err"):
1238 1238 break
1239 1239 tests.pop(0)
1240 1240 if not tests:
1241 1241 print "running all tests"
1242 1242 tests = orig
1243 1243
1244 1244 scheduletests(runner, options, tests)
1245 1245
1246 1246 failed = len(results['!'])
1247 1247 warned = len(results['~'])
1248 1248 tested = len(results['.']) + failed + warned
1249 1249 skipped = len(results['s'])
1250 1250 ignored = len(results['i'])
1251 1251
1252 1252 print
1253 1253 if not options.noskips:
1254 1254 for s in results['s']:
1255 1255 print "Skipped %s: %s" % s
1256 1256 for s in results['~']:
1257 1257 print "Warned %s: %s" % s
1258 1258 for s in results['!']:
1259 1259 print "Failed %s: %s" % s
1260 1260 _checkhglib("Tested")
1261 1261 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1262 1262 tested, skipped + ignored, warned, failed)
1263 1263 if results['!']:
1264 1264 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1265 1265 if options.time:
1266 1266 outputtimes(options)
1267 1267
1268 1268 if options.anycoverage:
1269 1269 outputcoverage(runner, options)
1270 1270 except KeyboardInterrupt:
1271 1271 failed = True
1272 1272 print "\ninterrupted!"
1273 1273
1274 1274 if failed:
1275 1275 return 1
1276 1276 if warned:
1277 1277 return 80
1278 1278
1279 1279 testtypes = [('.py', PythonTest, '.out'),
1280 1280 ('.t', TTest, '')]
1281 1281
1282 1282 class TestRunner(object):
1283 1283 """Holds context for executing tests.
1284 1284
1285 1285 Tests rely on a lot of state. This object holds it for them.
1286 1286 """
1287 1287 def __init__(self):
1288 1288 self.testdir = None
1289 self.hgtmp = None
1289 1290
1290 1291 def main(args, parser=None):
1291 1292 runner = TestRunner()
1292 1293
1293 1294 parser = parser or getparser()
1294 1295 (options, args) = parseargs(args, parser)
1295 1296 os.umask(022)
1296 1297
1297 1298 checktools()
1298 1299
1299 1300 if not args:
1300 1301 if options.changed:
1301 1302 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1302 1303 None, 0)
1303 1304 stdout, stderr = proc.communicate()
1304 1305 args = stdout.strip('\0').split('\0')
1305 1306 else:
1306 1307 args = os.listdir(".")
1307 1308
1308 1309 tests = [t for t in args
1309 1310 if os.path.basename(t).startswith("test-")
1310 1311 and (t.endswith(".py") or t.endswith(".t"))]
1311 1312
1312 1313 if options.random:
1313 1314 random.shuffle(tests)
1314 1315 else:
1315 1316 # keywords for slow tests
1316 1317 slow = 'svn gendoc check-code-hg'.split()
1317 1318 def sortkey(f):
1318 1319 # run largest tests first, as they tend to take the longest
1319 1320 try:
1320 1321 val = -os.stat(f).st_size
1321 1322 except OSError, e:
1322 1323 if e.errno != errno.ENOENT:
1323 1324 raise
1324 1325 return -1e9 # file does not exist, tell early
1325 1326 for kw in slow:
1326 1327 if kw in f:
1327 1328 val *= 10
1328 1329 return val
1329 1330 tests.sort(key=sortkey)
1330 1331
1331 1332 if 'PYTHONHASHSEED' not in os.environ:
1332 1333 # use a random python hash seed all the time
1333 1334 # we do the randomness ourself to know what seed is used
1334 1335 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1335 1336
1336 global HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1337 global INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1337 1338 runner.testdir = os.environ['TESTDIR'] = os.getcwd()
1338 1339 if options.tmpdir:
1339 1340 options.keep_tmpdir = True
1340 1341 tmpdir = options.tmpdir
1341 1342 if os.path.exists(tmpdir):
1342 1343 # Meaning of tmpdir has changed since 1.3: we used to create
1343 1344 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1344 1345 # tmpdir already exists.
1345 1346 print "error: temp dir %r already exists" % tmpdir
1346 1347 return 1
1347 1348
1348 1349 # Automatically removing tmpdir sounds convenient, but could
1349 1350 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1350 1351 # or "--tmpdir=$HOME".
1351 1352 #vlog("# Removing temp dir", tmpdir)
1352 1353 #shutil.rmtree(tmpdir)
1353 1354 os.makedirs(tmpdir)
1354 1355 else:
1355 1356 d = None
1356 1357 if os.name == 'nt':
1357 1358 # without this, we get the default temp dir location, but
1358 1359 # in all lowercase, which causes troubles with paths (issue3490)
1359 1360 d = os.getenv('TMP')
1360 1361 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1361 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1362 runner.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1362 1363
1363 1364 if options.with_hg:
1364 1365 INST = None
1365 1366 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1366 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1367 TMPBINDIR = os.path.join(runner.hgtmp, 'install', 'bin')
1367 1368 os.makedirs(TMPBINDIR)
1368 1369
1369 1370 # This looks redundant with how Python initializes sys.path from
1370 1371 # the location of the script being executed. Needed because the
1371 1372 # "hg" specified by --with-hg is not the only Python script
1372 1373 # executed in the test suite that needs to import 'mercurial'
1373 1374 # ... which means it's not really redundant at all.
1374 1375 PYTHONDIR = BINDIR
1375 1376 else:
1376 INST = os.path.join(HGTMP, "install")
1377 INST = os.path.join(runner.hgtmp, "install")
1377 1378 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1378 1379 TMPBINDIR = BINDIR
1379 1380 PYTHONDIR = os.path.join(INST, "lib", "python")
1380 1381
1381 1382 os.environ["BINDIR"] = BINDIR
1382 1383 os.environ["PYTHON"] = PYTHON
1383 1384
1384 1385 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1385 1386 if TMPBINDIR != BINDIR:
1386 1387 path = [TMPBINDIR] + path
1387 1388 os.environ["PATH"] = os.pathsep.join(path)
1388 1389
1389 1390 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1390 1391 # can run .../tests/run-tests.py test-foo where test-foo
1391 1392 # adds an extension to HGRC. Also include run-test.py directory to import
1392 1393 # modules like heredoctest.
1393 1394 pypath = [PYTHONDIR, runner.testdir,
1394 1395 os.path.abspath(os.path.dirname(__file__))]
1395 1396 # We have to augment PYTHONPATH, rather than simply replacing
1396 1397 # it, in case external libraries are only available via current
1397 1398 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1398 1399 # are in /opt/subversion.)
1399 1400 oldpypath = os.environ.get(IMPL_PATH)
1400 1401 if oldpypath:
1401 1402 pypath.append(oldpypath)
1402 1403 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1403 1404
1404 1405 COVERAGE_FILE = os.path.join(runner.testdir, ".coverage")
1405 1406
1406 1407 vlog("# Using TESTDIR", runner.testdir)
1407 vlog("# Using HGTMP", HGTMP)
1408 vlog("# Using HGTMP", runner.hgtmp)
1408 1409 vlog("# Using PATH", os.environ["PATH"])
1409 1410 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1410 1411
1411 1412 try:
1412 1413 return runtests(runner, options, tests) or 0
1413 1414 finally:
1414 1415 time.sleep(.1)
1415 cleanup(options)
1416 cleanup(runner, options)
1416 1417
1417 1418 if __name__ == '__main__':
1418 1419 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now