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