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