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