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