##// END OF EJS Templates
run-tests: remove threadtmp in Test.cleanup()...
Gregory Szorc -
r21310:af9a0495 default
parent child Browse files
Show More
@@ -1,1397 +1,1400 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install:
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 # 9) parallel, custom tmp dir:
37 37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 38 #
39 39 # (You could use any subset of the tests: test-s* happens to match
40 40 # enough that it's worth doing parallel runs, few enough that it
41 41 # completes fairly quickly, includes both shell and Python scripts, and
42 42 # includes some scripts that run daemon processes.)
43 43
44 44 from distutils import version
45 45 import difflib
46 46 import errno
47 47 import optparse
48 48 import os
49 49 import shutil
50 50 import subprocess
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55 import random
56 56 import re
57 57 import threading
58 58 import killdaemons as killmod
59 59 import Queue as queue
60 60
61 61 processlock = threading.Lock()
62 62
63 63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 65 # zombies but it's pretty harmless even if we do.
66 66 if sys.version_info < (2, 5):
67 67 subprocess._cleanup = lambda: None
68 68
69 69 closefds = os.name == 'posix'
70 70 def Popen4(cmd, wd, timeout, env=None):
71 71 processlock.acquire()
72 72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 73 close_fds=closefds,
74 74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 75 stderr=subprocess.STDOUT)
76 76 processlock.release()
77 77
78 78 p.fromchild = p.stdout
79 79 p.tochild = p.stdin
80 80 p.childerr = p.stderr
81 81
82 82 p.timeout = False
83 83 if timeout:
84 84 def t():
85 85 start = time.time()
86 86 while time.time() - start < timeout and p.returncode is None:
87 87 time.sleep(.1)
88 88 p.timeout = True
89 89 if p.returncode is None:
90 90 terminate(p)
91 91 threading.Thread(target=t).start()
92 92
93 93 return p
94 94
95 95 # reserved exit code to skip test (used by hghave)
96 96 SKIPPED_STATUS = 80
97 97 SKIPPED_PREFIX = 'skipped: '
98 98 FAILED_PREFIX = 'hghave check failed: '
99 99 PYTHON = sys.executable.replace('\\', '/')
100 100 IMPL_PATH = 'PYTHONPATH'
101 101 if 'java' in sys.platform:
102 102 IMPL_PATH = 'JYTHONPATH'
103 103
104 104 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
105 105
106 106 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
107 107 "gunzip", "bunzip2", "sed"]
108 108 createdfiles = []
109 109
110 110 defaults = {
111 111 'jobs': ('HGTEST_JOBS', 1),
112 112 'timeout': ('HGTEST_TIMEOUT', 180),
113 113 'port': ('HGTEST_PORT', 20059),
114 114 'shell': ('HGTEST_SHELL', 'sh'),
115 115 }
116 116
117 117 def parselistfiles(files, listtype, warn=True):
118 118 entries = dict()
119 119 for filename in files:
120 120 try:
121 121 path = os.path.expanduser(os.path.expandvars(filename))
122 122 f = open(path, "r")
123 123 except IOError, err:
124 124 if err.errno != errno.ENOENT:
125 125 raise
126 126 if warn:
127 127 print "warning: no such %s file: %s" % (listtype, filename)
128 128 continue
129 129
130 130 for line in f.readlines():
131 131 line = line.split('#', 1)[0].strip()
132 132 if line:
133 133 entries[line] = filename
134 134
135 135 f.close()
136 136 return entries
137 137
138 138 def getparser():
139 139 parser = optparse.OptionParser("%prog [options] [tests]")
140 140
141 141 # keep these sorted
142 142 parser.add_option("--blacklist", action="append",
143 143 help="skip tests listed in the specified blacklist file")
144 144 parser.add_option("--whitelist", action="append",
145 145 help="always run tests listed in the specified whitelist file")
146 146 parser.add_option("--changed", type="string",
147 147 help="run tests that are changed in parent rev or working directory")
148 148 parser.add_option("-C", "--annotate", action="store_true",
149 149 help="output files annotated with coverage")
150 150 parser.add_option("-c", "--cover", action="store_true",
151 151 help="print a test coverage report")
152 152 parser.add_option("-d", "--debug", action="store_true",
153 153 help="debug mode: write output of test scripts to console"
154 154 " rather than capturing and diffing it (disables timeout)")
155 155 parser.add_option("-f", "--first", action="store_true",
156 156 help="exit on the first test failure")
157 157 parser.add_option("-H", "--htmlcov", action="store_true",
158 158 help="create an HTML report of the coverage of the files")
159 159 parser.add_option("-i", "--interactive", action="store_true",
160 160 help="prompt to accept changed output")
161 161 parser.add_option("-j", "--jobs", type="int",
162 162 help="number of jobs to run in parallel"
163 163 " (default: $%s or %d)" % defaults['jobs'])
164 164 parser.add_option("--keep-tmpdir", action="store_true",
165 165 help="keep temporary directory after running tests")
166 166 parser.add_option("-k", "--keywords",
167 167 help="run tests matching keywords")
168 168 parser.add_option("-l", "--local", action="store_true",
169 169 help="shortcut for --with-hg=<testdir>/../hg")
170 170 parser.add_option("--loop", action="store_true",
171 171 help="loop tests repeatedly")
172 172 parser.add_option("-n", "--nodiff", action="store_true",
173 173 help="skip showing test changes")
174 174 parser.add_option("-p", "--port", type="int",
175 175 help="port on which servers should listen"
176 176 " (default: $%s or %d)" % defaults['port'])
177 177 parser.add_option("--compiler", type="string",
178 178 help="compiler to build with")
179 179 parser.add_option("--pure", action="store_true",
180 180 help="use pure Python code instead of C extensions")
181 181 parser.add_option("-R", "--restart", action="store_true",
182 182 help="restart at last error")
183 183 parser.add_option("-r", "--retest", action="store_true",
184 184 help="retest failed tests")
185 185 parser.add_option("-S", "--noskips", action="store_true",
186 186 help="don't report skip tests verbosely")
187 187 parser.add_option("--shell", type="string",
188 188 help="shell to use (default: $%s or %s)" % defaults['shell'])
189 189 parser.add_option("-t", "--timeout", type="int",
190 190 help="kill errant tests after TIMEOUT seconds"
191 191 " (default: $%s or %d)" % defaults['timeout'])
192 192 parser.add_option("--time", action="store_true",
193 193 help="time how long each test takes")
194 194 parser.add_option("--tmpdir", type="string",
195 195 help="run tests in the given temporary directory"
196 196 " (implies --keep-tmpdir)")
197 197 parser.add_option("-v", "--verbose", action="store_true",
198 198 help="output verbose messages")
199 199 parser.add_option("--view", type="string",
200 200 help="external diff viewer")
201 201 parser.add_option("--with-hg", type="string",
202 202 metavar="HG",
203 203 help="test using specified hg script rather than a "
204 204 "temporary installation")
205 205 parser.add_option("-3", "--py3k-warnings", action="store_true",
206 206 help="enable Py3k warnings on Python 2.6+")
207 207 parser.add_option('--extra-config-opt', action="append",
208 208 help='set the given config opt in the test hgrc')
209 209 parser.add_option('--random', action="store_true",
210 210 help='run tests in random order')
211 211
212 212 for option, (envvar, default) in defaults.items():
213 213 defaults[option] = type(default)(os.environ.get(envvar, default))
214 214 parser.set_defaults(**defaults)
215 215
216 216 return parser
217 217
218 218 def parseargs(args, parser):
219 219 (options, args) = parser.parse_args(args)
220 220
221 221 # jython is always pure
222 222 if 'java' in sys.platform or '__pypy__' in sys.modules:
223 223 options.pure = True
224 224
225 225 if options.with_hg:
226 226 options.with_hg = os.path.expanduser(options.with_hg)
227 227 if not (os.path.isfile(options.with_hg) and
228 228 os.access(options.with_hg, os.X_OK)):
229 229 parser.error('--with-hg must specify an executable hg script')
230 230 if not os.path.basename(options.with_hg) == 'hg':
231 231 sys.stderr.write('warning: --with-hg should specify an hg script\n')
232 232 if options.local:
233 233 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
234 234 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
235 235 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
236 236 parser.error('--local specified, but %r not found or not executable'
237 237 % hgbin)
238 238 options.with_hg = hgbin
239 239
240 240 options.anycoverage = options.cover or options.annotate or options.htmlcov
241 241 if options.anycoverage:
242 242 try:
243 243 import coverage
244 244 covver = version.StrictVersion(coverage.__version__).version
245 245 if covver < (3, 3):
246 246 parser.error('coverage options require coverage 3.3 or later')
247 247 except ImportError:
248 248 parser.error('coverage options now require the coverage package')
249 249
250 250 if options.anycoverage and options.local:
251 251 # this needs some path mangling somewhere, I guess
252 252 parser.error("sorry, coverage options do not work when --local "
253 253 "is specified")
254 254
255 255 global verbose
256 256 if options.verbose:
257 257 verbose = ''
258 258
259 259 if options.tmpdir:
260 260 options.tmpdir = os.path.expanduser(options.tmpdir)
261 261
262 262 if options.jobs < 1:
263 263 parser.error('--jobs must be positive')
264 264 if options.interactive and options.debug:
265 265 parser.error("-i/--interactive and -d/--debug are incompatible")
266 266 if options.debug:
267 267 if options.timeout != defaults['timeout']:
268 268 sys.stderr.write(
269 269 'warning: --timeout option ignored with --debug\n')
270 270 options.timeout = 0
271 271 if options.py3k_warnings:
272 272 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
273 273 parser.error('--py3k-warnings can only be used on Python 2.6+')
274 274 if options.blacklist:
275 275 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
276 276 if options.whitelist:
277 277 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
278 278 else:
279 279 options.whitelisted = {}
280 280
281 281 return (options, args)
282 282
283 283 def rename(src, dst):
284 284 """Like os.rename(), trade atomicity and opened files friendliness
285 285 for existing destination support.
286 286 """
287 287 shutil.copy(src, dst)
288 288 os.remove(src)
289 289
290 290 def parsehghaveoutput(lines):
291 291 '''Parse hghave log lines.
292 292 Return tuple of lists (missing, failed):
293 293 * the missing/unknown features
294 294 * the features for which existence check failed'''
295 295 missing = []
296 296 failed = []
297 297 for line in lines:
298 298 if line.startswith(SKIPPED_PREFIX):
299 299 line = line.splitlines()[0]
300 300 missing.append(line[len(SKIPPED_PREFIX):])
301 301 elif line.startswith(FAILED_PREFIX):
302 302 line = line.splitlines()[0]
303 303 failed.append(line[len(FAILED_PREFIX):])
304 304
305 305 return missing, failed
306 306
307 307 def showdiff(expected, output, ref, err):
308 308 print
309 309 servefail = False
310 310 for line in difflib.unified_diff(expected, output, ref, err):
311 311 sys.stdout.write(line)
312 312 if not servefail and line.startswith(
313 313 '+ abort: child process failed to start'):
314 314 servefail = True
315 315 return {'servefail': servefail}
316 316
317 317
318 318 verbose = False
319 319 def vlog(*msg):
320 320 if verbose is not False:
321 321 iolock.acquire()
322 322 if verbose:
323 323 print verbose,
324 324 for m in msg:
325 325 print m,
326 326 print
327 327 sys.stdout.flush()
328 328 iolock.release()
329 329
330 330 def log(*msg):
331 331 iolock.acquire()
332 332 if verbose:
333 333 print verbose,
334 334 for m in msg:
335 335 print m,
336 336 print
337 337 sys.stdout.flush()
338 338 iolock.release()
339 339
340 340 def findprogram(program):
341 341 """Search PATH for a executable program"""
342 342 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
343 343 name = os.path.join(p, program)
344 344 if os.name == 'nt' or os.access(name, os.X_OK):
345 345 return name
346 346 return None
347 347
348 348 def createhgrc(path, options):
349 349 # create a fresh hgrc
350 350 hgrc = open(path, 'w')
351 351 hgrc.write('[ui]\n')
352 352 hgrc.write('slash = True\n')
353 353 hgrc.write('interactive = False\n')
354 354 hgrc.write('[defaults]\n')
355 355 hgrc.write('backout = -d "0 0"\n')
356 356 hgrc.write('commit = -d "0 0"\n')
357 357 hgrc.write('shelve = --date "0 0"\n')
358 358 hgrc.write('tag = -d "0 0"\n')
359 359 if options.extra_config_opt:
360 360 for opt in options.extra_config_opt:
361 361 section, key = opt.split('.', 1)
362 362 assert '=' in key, ('extra config opt %s must '
363 363 'have an = for assignment' % opt)
364 364 hgrc.write('[%s]\n%s\n' % (section, key))
365 365 hgrc.close()
366 366
367 367 def checktools():
368 368 # Before we go any further, check for pre-requisite tools
369 369 # stuff from coreutils (cat, rm, etc) are not tested
370 370 for p in requiredtools:
371 371 if os.name == 'nt' and not p.endswith('.exe'):
372 372 p += '.exe'
373 373 found = findprogram(p)
374 374 if found:
375 375 vlog("# Found prerequisite", p, "at", found)
376 376 else:
377 377 print "WARNING: Did not find prerequisite tool: "+p
378 378
379 379 def terminate(proc):
380 380 """Terminate subprocess (with fallback for Python versions < 2.6)"""
381 381 vlog('# Terminating process %d' % proc.pid)
382 382 try:
383 383 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
384 384 except OSError:
385 385 pass
386 386
387 387 def killdaemons(pidfile):
388 388 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
389 389 logfn=vlog)
390 390
391 391 def cleanup(options):
392 392 if not options.keep_tmpdir:
393 393 vlog("# Cleaning up HGTMP", HGTMP)
394 394 shutil.rmtree(HGTMP, True)
395 395 for f in createdfiles:
396 396 try:
397 397 os.remove(f)
398 398 except OSError:
399 399 pass
400 400
401 401 def usecorrectpython():
402 402 # some tests run python interpreter. they must use same
403 403 # interpreter we use or bad things will happen.
404 404 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
405 405 if getattr(os, 'symlink', None):
406 406 vlog("# Making python executable in test path a symlink to '%s'" %
407 407 sys.executable)
408 408 mypython = os.path.join(TMPBINDIR, pyexename)
409 409 try:
410 410 if os.readlink(mypython) == sys.executable:
411 411 return
412 412 os.unlink(mypython)
413 413 except OSError, err:
414 414 if err.errno != errno.ENOENT:
415 415 raise
416 416 if findprogram(pyexename) != sys.executable:
417 417 try:
418 418 os.symlink(sys.executable, mypython)
419 419 createdfiles.append(mypython)
420 420 except OSError, err:
421 421 # child processes may race, which is harmless
422 422 if err.errno != errno.EEXIST:
423 423 raise
424 424 else:
425 425 exedir, exename = os.path.split(sys.executable)
426 426 vlog("# Modifying search path to find %s as %s in '%s'" %
427 427 (exename, pyexename, exedir))
428 428 path = os.environ['PATH'].split(os.pathsep)
429 429 while exedir in path:
430 430 path.remove(exedir)
431 431 os.environ['PATH'] = os.pathsep.join([exedir] + path)
432 432 if not findprogram(pyexename):
433 433 print "WARNING: Cannot find %s in search path" % pyexename
434 434
435 435 def installhg(options):
436 436 vlog("# Performing temporary installation of HG")
437 437 installerrs = os.path.join("tests", "install.err")
438 438 compiler = ''
439 439 if options.compiler:
440 440 compiler = '--compiler ' + options.compiler
441 441 pure = options.pure and "--pure" or ""
442 442 py3 = ''
443 443 if sys.version_info[0] == 3:
444 444 py3 = '--c2to3'
445 445
446 446 # Run installer in hg root
447 447 script = os.path.realpath(sys.argv[0])
448 448 hgroot = os.path.dirname(os.path.dirname(script))
449 449 os.chdir(hgroot)
450 450 nohome = '--home=""'
451 451 if os.name == 'nt':
452 452 # The --home="" trick works only on OS where os.sep == '/'
453 453 # because of a distutils convert_path() fast-path. Avoid it at
454 454 # least on Windows for now, deal with .pydistutils.cfg bugs
455 455 # when they happen.
456 456 nohome = ''
457 457 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
458 458 ' build %(compiler)s --build-base="%(base)s"'
459 459 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
460 460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
461 461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
462 462 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
463 463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
464 464 'nohome': nohome, 'logfile': installerrs})
465 465 vlog("# Running", cmd)
466 466 if os.system(cmd) == 0:
467 467 if not options.verbose:
468 468 os.remove(installerrs)
469 469 else:
470 470 f = open(installerrs)
471 471 for line in f:
472 472 print line,
473 473 f.close()
474 474 sys.exit(1)
475 475 os.chdir(TESTDIR)
476 476
477 477 usecorrectpython()
478 478
479 479 if options.py3k_warnings and not options.anycoverage:
480 480 vlog("# Updating hg command to enable Py3k Warnings switch")
481 481 f = open(os.path.join(BINDIR, 'hg'), 'r')
482 482 lines = [line.rstrip() for line in f]
483 483 lines[0] += ' -3'
484 484 f.close()
485 485 f = open(os.path.join(BINDIR, 'hg'), 'w')
486 486 for line in lines:
487 487 f.write(line + '\n')
488 488 f.close()
489 489
490 490 hgbat = os.path.join(BINDIR, 'hg.bat')
491 491 if os.path.isfile(hgbat):
492 492 # hg.bat expects to be put in bin/scripts while run-tests.py
493 493 # installation layout put it in bin/ directly. Fix it
494 494 f = open(hgbat, 'rb')
495 495 data = f.read()
496 496 f.close()
497 497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
498 498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
499 499 '"%~dp0python" "%~dp0hg" %*')
500 500 f = open(hgbat, 'wb')
501 501 f.write(data)
502 502 f.close()
503 503 else:
504 504 print 'WARNING: cannot fix hg.bat reference to python.exe'
505 505
506 506 if options.anycoverage:
507 507 custom = os.path.join(TESTDIR, 'sitecustomize.py')
508 508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
509 509 vlog('# Installing coverage trigger to %s' % target)
510 510 shutil.copyfile(custom, target)
511 511 rc = os.path.join(TESTDIR, '.coveragerc')
512 512 vlog('# Installing coverage rc to %s' % rc)
513 513 os.environ['COVERAGE_PROCESS_START'] = rc
514 514 fn = os.path.join(INST, '..', '.coverage')
515 515 os.environ['COVERAGE_FILE'] = fn
516 516
517 517 def outputtimes(options):
518 518 vlog('# Producing time report')
519 519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
520 520 cols = '%7.3f %s'
521 521 print '\n%-7s %s' % ('Time', 'Test')
522 522 for test, timetaken in times:
523 523 print cols % (timetaken, test)
524 524
525 525 def outputcoverage(options):
526 526
527 527 vlog('# Producing coverage report')
528 528 os.chdir(PYTHONDIR)
529 529
530 530 def covrun(*args):
531 531 cmd = 'coverage %s' % ' '.join(args)
532 532 vlog('# Running: %s' % cmd)
533 533 os.system(cmd)
534 534
535 535 covrun('-c')
536 536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
537 537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
538 538 if options.htmlcov:
539 539 htmldir = os.path.join(TESTDIR, 'htmlcov')
540 540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
541 541 if options.annotate:
542 542 adir = os.path.join(TESTDIR, 'annotated')
543 543 if not os.path.isdir(adir):
544 544 os.mkdir(adir)
545 545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
546 546
547 547 class Test(object):
548 548 """Encapsulates a single, runnable test.
549 549
550 550 Test instances can be run multiple times via run(). However, multiple
551 551 runs cannot be run concurrently.
552 552 """
553 553
554 554 def __init__(self, path, options, count):
555 555 self._path = path
556 556 self._options = options
557 557 self._count = count
558 558
559 self.threadtmp = os.path.join(HGTMP, 'child%d' % count)
560 os.mkdir(self.threadtmp)
559 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
560 os.mkdir(self._threadtmp)
561
562 def cleanup(self):
563 if self._threadtmp and not self._options.keep_tmpdir:
564 shutil.rmtree(self._threadtmp, True)
561 565
562 566 def run(self, result, refpath):
563 testtmp = os.path.join(self.threadtmp, os.path.basename(self._path))
567 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
564 568 os.mkdir(testtmp)
565 569 replacements, port = self._getreplacements(testtmp)
566 570 env = self._getenv(testtmp, port)
567 571 createhgrc(env['HGRCPATH'], self._options)
568 572
569 573 starttime = time.time()
570 574
571 575 def updateduration():
572 576 result.duration = time.time() - starttime
573 577
574 578 try:
575 579 ret, out = self._run(testtmp, replacements, env)
576 580 updateduration()
577 581 result.ret = ret
578 582 result.out = out
579 583 except KeyboardInterrupt:
580 584 updateduration()
581 585 result.interrupted = True
582 586 except Exception, e:
583 587 updateduration()
584 588 result.exception = e
585 589
586 590 killdaemons(env['DAEMON_PIDS'])
587 591
588 592 # If we're not in --debug mode and reference output file exists,
589 593 # check test output against it.
590 594 if self._options.debug:
591 595 result.refout = None # to match "out is None"
592 596 elif os.path.exists(refpath):
593 597 f = open(refpath, 'r')
594 598 result.refout = f.read().splitlines(True)
595 599 f.close()
596 600 else:
597 601 result.refout = []
598 602
599 603 if not self._options.keep_tmpdir:
600 604 shutil.rmtree(testtmp)
601 605
602 606 def _run(self, testtmp, replacements, env):
603 607 raise NotImplemented('Subclasses must implement Test.run()')
604 608
605 609 def _getreplacements(self, testtmp):
606 610 port = self._options.port + self._count * 3
607 611 r = [
608 612 (r':%s\b' % port, ':$HGPORT'),
609 613 (r':%s\b' % (port + 1), ':$HGPORT1'),
610 614 (r':%s\b' % (port + 2), ':$HGPORT2'),
611 615 ]
612 616
613 617 if os.name == 'nt':
614 618 r.append(
615 619 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
616 620 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
617 621 for c in testtmp), '$TESTTMP'))
618 622 else:
619 623 r.append((re.escape(testtmp), '$TESTTMP'))
620 624
621 625 return r, port
622 626
623 627 def _getenv(self, testtmp, port):
624 628 env = os.environ.copy()
625 629 env['TESTTMP'] = testtmp
626 630 env['HOME'] = testtmp
627 631 env["HGPORT"] = str(port)
628 632 env["HGPORT1"] = str(port + 1)
629 633 env["HGPORT2"] = str(port + 2)
630 env["HGRCPATH"] = os.path.join(self.threadtmp, '.hgrc')
631 env["DAEMON_PIDS"] = os.path.join(self.threadtmp, 'daemon.pids')
634 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
635 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
632 636 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
633 637 env["HGMERGE"] = "internal:merge"
634 638 env["HGUSER"] = "test"
635 639 env["HGENCODING"] = "ascii"
636 640 env["HGENCODINGMODE"] = "strict"
637 641
638 642 # Reset some environment variables to well-known values so that
639 643 # the tests produce repeatable output.
640 644 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
641 645 env['TZ'] = 'GMT'
642 646 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
643 647 env['COLUMNS'] = '80'
644 648 env['TERM'] = 'xterm'
645 649
646 650 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
647 651 'NO_PROXY').split():
648 652 if k in env:
649 653 del env[k]
650 654
651 655 # unset env related to hooks
652 656 for k in env.keys():
653 657 if k.startswith('HG_'):
654 658 del env[k]
655 659
656 660 return env
657 661
658 662 class TestResult(object):
659 663 """Holds the result of a test execution."""
660 664
661 665 def __init__(self):
662 666 self.ret = None
663 667 self.out = None
664 668 self.duration = None
665 669 self.interrupted = False
666 670 self.exception = None
667 671 self.refout = None
668 672
669 673 @property
670 674 def skipped(self):
671 675 """Whether the test was skipped."""
672 676 return self.ret == SKIPPED_STATUS
673 677
674 678 def pytest(test, wd, options, replacements, env):
675 679 py3kswitch = options.py3k_warnings and ' -3' or ''
676 680 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
677 681 vlog("# Running", cmd)
678 682 if os.name == 'nt':
679 683 replacements.append((r'\r\n', '\n'))
680 684 return run(cmd, wd, options, replacements, env)
681 685
682 686 class PythonTest(Test):
683 687 """A Python-based test."""
684 688 def _run(self, testtmp, replacements, env):
685 689 return pytest(self._path, testtmp, self._options, replacements,
686 690 env)
687 691
688 692 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
689 693 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
690 694 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
691 695 escapemap.update({'\\': '\\\\', '\r': r'\r'})
692 696 def escapef(m):
693 697 return escapemap[m.group(0)]
694 698 def stringescape(s):
695 699 return escapesub(escapef, s)
696 700
697 701 def rematch(el, l):
698 702 try:
699 703 # use \Z to ensure that the regex matches to the end of the string
700 704 if os.name == 'nt':
701 705 return re.match(el + r'\r?\n\Z', l)
702 706 return re.match(el + r'\n\Z', l)
703 707 except re.error:
704 708 # el is an invalid regex
705 709 return False
706 710
707 711 def globmatch(el, l):
708 712 # The only supported special characters are * and ? plus / which also
709 713 # matches \ on windows. Escaping of these characters is supported.
710 714 if el + '\n' == l:
711 715 if os.altsep:
712 716 # matching on "/" is not needed for this line
713 717 return '-glob'
714 718 return True
715 719 i, n = 0, len(el)
716 720 res = ''
717 721 while i < n:
718 722 c = el[i]
719 723 i += 1
720 724 if c == '\\' and el[i] in '*?\\/':
721 725 res += el[i - 1:i + 1]
722 726 i += 1
723 727 elif c == '*':
724 728 res += '.*'
725 729 elif c == '?':
726 730 res += '.'
727 731 elif c == '/' and os.altsep:
728 732 res += '[/\\\\]'
729 733 else:
730 734 res += re.escape(c)
731 735 return rematch(res, l)
732 736
733 737 def linematch(el, l):
734 738 if el == l: # perfect match (fast)
735 739 return True
736 740 if el:
737 741 if el.endswith(" (esc)\n"):
738 742 el = el[:-7].decode('string-escape') + '\n'
739 743 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
740 744 return True
741 745 if el.endswith(" (re)\n"):
742 746 return rematch(el[:-6], l)
743 747 if el.endswith(" (glob)\n"):
744 748 return globmatch(el[:-8], l)
745 749 if os.altsep and l.replace('\\', '/') == el:
746 750 return '+glob'
747 751 return False
748 752
749 753 def tsttest(test, wd, options, replacements, env):
750 754 # We generate a shell script which outputs unique markers to line
751 755 # up script results with our source. These markers include input
752 756 # line number and the last return code
753 757 salt = "SALT" + str(time.time())
754 758 def addsalt(line, inpython):
755 759 if inpython:
756 760 script.append('%s %d 0\n' % (salt, line))
757 761 else:
758 762 script.append('echo %s %s $?\n' % (salt, line))
759 763
760 764 # After we run the shell script, we re-unify the script output
761 765 # with non-active parts of the source, with synchronization by our
762 766 # SALT line number markers. The after table contains the
763 767 # non-active components, ordered by line number
764 768 after = {}
765 769 pos = prepos = -1
766 770
767 771 # Expected shell script output
768 772 expected = {}
769 773
770 774 # We keep track of whether or not we're in a Python block so we
771 775 # can generate the surrounding doctest magic
772 776 inpython = False
773 777
774 778 # True or False when in a true or false conditional section
775 779 skipping = None
776 780
777 781 def hghave(reqs):
778 782 # TODO: do something smarter when all other uses of hghave is gone
779 783 tdir = TESTDIR.replace('\\', '/')
780 784 proc = Popen4('%s -c "%s/hghave %s"' %
781 785 (options.shell, tdir, ' '.join(reqs)), wd, 0)
782 786 stdout, stderr = proc.communicate()
783 787 ret = proc.wait()
784 788 if wifexited(ret):
785 789 ret = os.WEXITSTATUS(ret)
786 790 if ret == 2:
787 791 print stdout
788 792 sys.exit(1)
789 793 return ret == 0
790 794
791 795 f = open(test)
792 796 t = f.readlines()
793 797 f.close()
794 798
795 799 script = []
796 800 if options.debug:
797 801 script.append('set -x\n')
798 802 if os.getenv('MSYSTEM'):
799 803 script.append('alias pwd="pwd -W"\n')
800 804 n = 0
801 805 for n, l in enumerate(t):
802 806 if not l.endswith('\n'):
803 807 l += '\n'
804 808 if l.startswith('#if'):
805 809 lsplit = l.split()
806 810 if len(lsplit) < 2 or lsplit[0] != '#if':
807 811 after.setdefault(pos, []).append(' !!! invalid #if\n')
808 812 if skipping is not None:
809 813 after.setdefault(pos, []).append(' !!! nested #if\n')
810 814 skipping = not hghave(lsplit[1:])
811 815 after.setdefault(pos, []).append(l)
812 816 elif l.startswith('#else'):
813 817 if skipping is None:
814 818 after.setdefault(pos, []).append(' !!! missing #if\n')
815 819 skipping = not skipping
816 820 after.setdefault(pos, []).append(l)
817 821 elif l.startswith('#endif'):
818 822 if skipping is None:
819 823 after.setdefault(pos, []).append(' !!! missing #if\n')
820 824 skipping = None
821 825 after.setdefault(pos, []).append(l)
822 826 elif skipping:
823 827 after.setdefault(pos, []).append(l)
824 828 elif l.startswith(' >>> '): # python inlines
825 829 after.setdefault(pos, []).append(l)
826 830 prepos = pos
827 831 pos = n
828 832 if not inpython:
829 833 # we've just entered a Python block, add the header
830 834 inpython = True
831 835 addsalt(prepos, False) # make sure we report the exit code
832 836 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
833 837 addsalt(n, True)
834 838 script.append(l[2:])
835 839 elif l.startswith(' ... '): # python inlines
836 840 after.setdefault(prepos, []).append(l)
837 841 script.append(l[2:])
838 842 elif l.startswith(' $ '): # commands
839 843 if inpython:
840 844 script.append("EOF\n")
841 845 inpython = False
842 846 after.setdefault(pos, []).append(l)
843 847 prepos = pos
844 848 pos = n
845 849 addsalt(n, False)
846 850 cmd = l[4:].split()
847 851 if len(cmd) == 2 and cmd[0] == 'cd':
848 852 l = ' $ cd %s || exit 1\n' % cmd[1]
849 853 script.append(l[4:])
850 854 elif l.startswith(' > '): # continuations
851 855 after.setdefault(prepos, []).append(l)
852 856 script.append(l[4:])
853 857 elif l.startswith(' '): # results
854 858 # queue up a list of expected results
855 859 expected.setdefault(pos, []).append(l[2:])
856 860 else:
857 861 if inpython:
858 862 script.append("EOF\n")
859 863 inpython = False
860 864 # non-command/result - queue up for merged output
861 865 after.setdefault(pos, []).append(l)
862 866
863 867 if inpython:
864 868 script.append("EOF\n")
865 869 if skipping is not None:
866 870 after.setdefault(pos, []).append(' !!! missing #endif\n')
867 871 addsalt(n + 1, False)
868 872
869 873 # Write out the script and execute it
870 874 name = wd + '.sh'
871 875 f = open(name, 'w')
872 876 for l in script:
873 877 f.write(l)
874 878 f.close()
875 879
876 880 cmd = '%s "%s"' % (options.shell, name)
877 881 vlog("# Running", cmd)
878 882 exitcode, output = run(cmd, wd, options, replacements, env)
879 883 # do not merge output if skipped, return hghave message instead
880 884 # similarly, with --debug, output is None
881 885 if exitcode == SKIPPED_STATUS or output is None:
882 886 return exitcode, output
883 887
884 888 # Merge the script output back into a unified test
885 889
886 890 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
887 891 if exitcode != 0: # failure has been reported
888 892 warnonly = 3 # set to "for sure not"
889 893 pos = -1
890 894 postout = []
891 895 for l in output:
892 896 lout, lcmd = l, None
893 897 if salt in l:
894 898 lout, lcmd = l.split(salt, 1)
895 899
896 900 if lout:
897 901 if not lout.endswith('\n'):
898 902 lout += ' (no-eol)\n'
899 903
900 904 # find the expected output at the current position
901 905 el = None
902 906 if pos in expected and expected[pos]:
903 907 el = expected[pos].pop(0)
904 908
905 909 r = linematch(el, lout)
906 910 if isinstance(r, str):
907 911 if r == '+glob':
908 912 lout = el[:-1] + ' (glob)\n'
909 913 r = '' # warn only this line
910 914 elif r == '-glob':
911 915 lout = ''.join(el.rsplit(' (glob)', 1))
912 916 r = '' # warn only this line
913 917 else:
914 918 log('\ninfo, unknown linematch result: %r\n' % r)
915 919 r = False
916 920 if r:
917 921 postout.append(" " + el)
918 922 else:
919 923 if needescape(lout):
920 924 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
921 925 postout.append(" " + lout) # let diff deal with it
922 926 if r != '': # if line failed
923 927 warnonly = 3 # set to "for sure not"
924 928 elif warnonly == 1: # is "not yet" (and line is warn only)
925 929 warnonly = 2 # set to "yes" do warn
926 930
927 931 if lcmd:
928 932 # add on last return code
929 933 ret = int(lcmd.split()[1])
930 934 if ret != 0:
931 935 postout.append(" [%s]\n" % ret)
932 936 if pos in after:
933 937 # merge in non-active test bits
934 938 postout += after.pop(pos)
935 939 pos = int(lcmd.split()[0])
936 940
937 941 if pos in after:
938 942 postout += after.pop(pos)
939 943
940 944 if warnonly == 2:
941 945 exitcode = False # set exitcode to warned
942 946 return exitcode, postout
943 947
944 948 class TTest(Test):
945 949 """A "t test" is a test backed by a .t file."""
946 950
947 951 def _run(self, testtmp, replacements, env):
948 952 return tsttest(self._path, testtmp, self._options, replacements,
949 953 env)
950 954
951 955 wifexited = getattr(os, "WIFEXITED", lambda x: False)
952 956 def run(cmd, wd, options, replacements, env):
953 957 """Run command in a sub-process, capturing the output (stdout and stderr).
954 958 Return a tuple (exitcode, output). output is None in debug mode."""
955 959 # TODO: Use subprocess.Popen if we're running on Python 2.4
956 960 if options.debug:
957 961 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
958 962 ret = proc.wait()
959 963 return (ret, None)
960 964
961 965 proc = Popen4(cmd, wd, options.timeout, env)
962 966 def cleanup():
963 967 terminate(proc)
964 968 ret = proc.wait()
965 969 if ret == 0:
966 970 ret = signal.SIGTERM << 8
967 971 killdaemons(env['DAEMON_PIDS'])
968 972 return ret
969 973
970 974 output = ''
971 975 proc.tochild.close()
972 976
973 977 try:
974 978 output = proc.fromchild.read()
975 979 except KeyboardInterrupt:
976 980 vlog('# Handling keyboard interrupt')
977 981 cleanup()
978 982 raise
979 983
980 984 ret = proc.wait()
981 985 if wifexited(ret):
982 986 ret = os.WEXITSTATUS(ret)
983 987
984 988 if proc.timeout:
985 989 ret = 'timeout'
986 990
987 991 if ret:
988 992 killdaemons(env['DAEMON_PIDS'])
989 993
990 994 if abort:
991 995 raise KeyboardInterrupt()
992 996
993 997 for s, r in replacements:
994 998 output = re.sub(s, r, output)
995 999 return ret, output.splitlines(True)
996 1000
997 1001 def runone(options, test, count):
998 1002 '''returns a result element: (code, test, msg)'''
999 1003
1000 1004 def skip(msg):
1001 1005 if options.verbose:
1002 1006 log("\nSkipping %s: %s" % (testpath, msg))
1003 1007 return 's', test, msg
1004 1008
1005 1009 def fail(msg, ret):
1006 1010 warned = ret is False
1007 1011 if not options.nodiff:
1008 1012 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
1009 1013 if (not ret and options.interactive
1010 1014 and os.path.exists(testpath + ".err")):
1011 1015 iolock.acquire()
1012 1016 print "Accept this change? [n] ",
1013 1017 answer = sys.stdin.readline().strip()
1014 1018 iolock.release()
1015 1019 if answer.lower() in "y yes".split():
1016 1020 if test.endswith(".t"):
1017 1021 rename(testpath + ".err", testpath)
1018 1022 else:
1019 1023 rename(testpath + ".err", testpath + ".out")
1020 1024 return '.', test, ''
1021 1025 return warned and '~' or '!', test, msg
1022 1026
1023 1027 def success():
1024 1028 return '.', test, ''
1025 1029
1026 1030 def ignore(msg):
1027 1031 return 'i', test, msg
1028 1032
1029 1033 def describe(ret):
1030 1034 if ret < 0:
1031 1035 return 'killed by signal %d' % -ret
1032 1036 return 'returned error code %d' % ret
1033 1037
1034 1038 testpath = os.path.join(TESTDIR, test)
1035 1039 err = os.path.join(TESTDIR, test + ".err")
1036 1040 lctest = test.lower()
1037 1041
1038 1042 if not os.path.exists(testpath):
1039 1043 return skip("doesn't exist")
1040 1044
1041 1045 if not (options.whitelisted and test in options.whitelisted):
1042 1046 if options.blacklist and test in options.blacklist:
1043 1047 return skip("blacklisted")
1044 1048
1045 1049 if options.retest and not os.path.exists(test + ".err"):
1046 1050 return ignore("not retesting")
1047 1051
1048 1052 if options.keywords:
1049 1053 fp = open(test)
1050 1054 t = fp.read().lower() + test.lower()
1051 1055 fp.close()
1052 1056 for k in options.keywords.lower().split():
1053 1057 if k in t:
1054 1058 break
1055 1059 else:
1056 1060 return ignore("doesn't match keyword")
1057 1061
1058 1062 if not os.path.basename(lctest).startswith("test-"):
1059 1063 return skip("not a test file")
1060 1064 for ext, cls, out in testtypes:
1061 1065 if lctest.endswith(ext):
1062 1066 runner = cls
1063 1067 ref = os.path.join(TESTDIR, test + out)
1064 1068 break
1065 1069 else:
1066 1070 return skip("unknown test type")
1067 1071
1068 1072 vlog("# Test", test)
1069 1073
1070 1074 if os.path.exists(err):
1071 1075 os.remove(err) # Remove any previous output files
1072 1076
1073 1077 t = runner(testpath, options, count)
1074 1078 res = TestResult()
1075 1079 t.run(res, ref)
1080 t.cleanup()
1076 1081
1077 1082 if res.interrupted:
1078 1083 log('INTERRUPTED: %s (after %d seconds)' % (test, res.duration))
1079 1084 raise KeyboardInterrupt()
1080 1085
1081 1086 if res.exception:
1082 1087 return fail('Exception during execution: %s' % res.exception, 255)
1083 1088
1084 1089 ret = res.ret
1085 1090 out = res.out
1086 1091
1087 1092 times.append((test, res.duration))
1088 1093 vlog("# Ret was:", ret)
1089 1094
1090 1095 skipped = res.skipped
1091 1096 refout = res.refout
1092 1097
1093 1098 if (ret != 0 or out != refout) and not skipped and not options.debug:
1094 1099 # Save errors to a file for diagnosis
1095 1100 f = open(err, "wb")
1096 1101 for line in out:
1097 1102 f.write(line)
1098 1103 f.close()
1099 1104
1100 1105 if skipped:
1101 1106 if out is None: # debug mode: nothing to parse
1102 1107 missing = ['unknown']
1103 1108 failed = None
1104 1109 else:
1105 1110 missing, failed = parsehghaveoutput(out)
1106 1111 if not missing:
1107 1112 missing = ['irrelevant']
1108 1113 if failed:
1109 1114 result = fail("hghave failed checking for %s" % failed[-1], ret)
1110 1115 skipped = False
1111 1116 else:
1112 1117 result = skip(missing[-1])
1113 1118 elif ret == 'timeout':
1114 1119 result = fail("timed out", ret)
1115 1120 elif out != refout:
1116 1121 info = {}
1117 1122 if not options.nodiff:
1118 1123 iolock.acquire()
1119 1124 if options.view:
1120 1125 os.system("%s %s %s" % (options.view, ref, err))
1121 1126 else:
1122 1127 info = showdiff(refout, out, ref, err)
1123 1128 iolock.release()
1124 1129 msg = ""
1125 1130 if info.get('servefail'): msg += "serve failed and "
1126 1131 if ret:
1127 1132 msg += "output changed and " + describe(ret)
1128 1133 else:
1129 1134 msg += "output changed"
1130 1135 result = fail(msg, ret)
1131 1136 elif ret:
1132 1137 result = fail(describe(ret), ret)
1133 1138 else:
1134 1139 result = success()
1135 1140
1136 1141 if not options.verbose:
1137 1142 iolock.acquire()
1138 1143 sys.stdout.write(result[0])
1139 1144 sys.stdout.flush()
1140 1145 iolock.release()
1141 1146
1142 if not options.keep_tmpdir:
1143 shutil.rmtree(t.threadtmp, True)
1144 1147 return result
1145 1148
1146 1149 _hgpath = None
1147 1150
1148 1151 def _gethgpath():
1149 1152 """Return the path to the mercurial package that is actually found by
1150 1153 the current Python interpreter."""
1151 1154 global _hgpath
1152 1155 if _hgpath is not None:
1153 1156 return _hgpath
1154 1157
1155 1158 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1156 1159 pipe = os.popen(cmd % PYTHON)
1157 1160 try:
1158 1161 _hgpath = pipe.read().strip()
1159 1162 finally:
1160 1163 pipe.close()
1161 1164 return _hgpath
1162 1165
1163 1166 def _checkhglib(verb):
1164 1167 """Ensure that the 'mercurial' package imported by python is
1165 1168 the one we expect it to be. If not, print a warning to stderr."""
1166 1169 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1167 1170 actualhg = _gethgpath()
1168 1171 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1169 1172 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1170 1173 ' (expected %s)\n'
1171 1174 % (verb, actualhg, expecthg))
1172 1175
1173 1176 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1174 1177 times = []
1175 1178 iolock = threading.Lock()
1176 1179 abort = False
1177 1180
1178 1181 def scheduletests(options, tests):
1179 1182 jobs = options.jobs
1180 1183 done = queue.Queue()
1181 1184 running = 0
1182 1185 count = 0
1183 1186 global abort
1184 1187
1185 1188 def job(test, count):
1186 1189 try:
1187 1190 done.put(runone(options, test, count))
1188 1191 except KeyboardInterrupt:
1189 1192 pass
1190 1193 except: # re-raises
1191 1194 done.put(('!', test, 'run-test raised an error, see traceback'))
1192 1195 raise
1193 1196
1194 1197 try:
1195 1198 while tests or running:
1196 1199 if not done.empty() or running == jobs or not tests:
1197 1200 try:
1198 1201 code, test, msg = done.get(True, 1)
1199 1202 results[code].append((test, msg))
1200 1203 if options.first and code not in '.si':
1201 1204 break
1202 1205 except queue.Empty:
1203 1206 continue
1204 1207 running -= 1
1205 1208 if tests and not running == jobs:
1206 1209 test = tests.pop(0)
1207 1210 if options.loop:
1208 1211 tests.append(test)
1209 1212 t = threading.Thread(target=job, name=test, args=(test, count))
1210 1213 t.start()
1211 1214 running += 1
1212 1215 count += 1
1213 1216 except KeyboardInterrupt:
1214 1217 abort = True
1215 1218
1216 1219 def runtests(options, tests):
1217 1220 try:
1218 1221 if INST:
1219 1222 installhg(options)
1220 1223 _checkhglib("Testing")
1221 1224 else:
1222 1225 usecorrectpython()
1223 1226
1224 1227 if options.restart:
1225 1228 orig = list(tests)
1226 1229 while tests:
1227 1230 if os.path.exists(tests[0] + ".err"):
1228 1231 break
1229 1232 tests.pop(0)
1230 1233 if not tests:
1231 1234 print "running all tests"
1232 1235 tests = orig
1233 1236
1234 1237 scheduletests(options, tests)
1235 1238
1236 1239 failed = len(results['!'])
1237 1240 warned = len(results['~'])
1238 1241 tested = len(results['.']) + failed + warned
1239 1242 skipped = len(results['s'])
1240 1243 ignored = len(results['i'])
1241 1244
1242 1245 print
1243 1246 if not options.noskips:
1244 1247 for s in results['s']:
1245 1248 print "Skipped %s: %s" % s
1246 1249 for s in results['~']:
1247 1250 print "Warned %s: %s" % s
1248 1251 for s in results['!']:
1249 1252 print "Failed %s: %s" % s
1250 1253 _checkhglib("Tested")
1251 1254 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1252 1255 tested, skipped + ignored, warned, failed)
1253 1256 if results['!']:
1254 1257 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1255 1258 if options.time:
1256 1259 outputtimes(options)
1257 1260
1258 1261 if options.anycoverage:
1259 1262 outputcoverage(options)
1260 1263 except KeyboardInterrupt:
1261 1264 failed = True
1262 1265 print "\ninterrupted!"
1263 1266
1264 1267 if failed:
1265 1268 return 1
1266 1269 if warned:
1267 1270 return 80
1268 1271
1269 1272 testtypes = [('.py', PythonTest, '.out'),
1270 1273 ('.t', TTest, '')]
1271 1274
1272 1275 def main(args, parser=None):
1273 1276 parser = parser or getparser()
1274 1277 (options, args) = parseargs(args, parser)
1275 1278 os.umask(022)
1276 1279
1277 1280 checktools()
1278 1281
1279 1282 if not args:
1280 1283 if options.changed:
1281 1284 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1282 1285 None, 0)
1283 1286 stdout, stderr = proc.communicate()
1284 1287 args = stdout.strip('\0').split('\0')
1285 1288 else:
1286 1289 args = os.listdir(".")
1287 1290
1288 1291 tests = [t for t in args
1289 1292 if os.path.basename(t).startswith("test-")
1290 1293 and (t.endswith(".py") or t.endswith(".t"))]
1291 1294
1292 1295 if options.random:
1293 1296 random.shuffle(tests)
1294 1297 else:
1295 1298 # keywords for slow tests
1296 1299 slow = 'svn gendoc check-code-hg'.split()
1297 1300 def sortkey(f):
1298 1301 # run largest tests first, as they tend to take the longest
1299 1302 try:
1300 1303 val = -os.stat(f).st_size
1301 1304 except OSError, e:
1302 1305 if e.errno != errno.ENOENT:
1303 1306 raise
1304 1307 return -1e9 # file does not exist, tell early
1305 1308 for kw in slow:
1306 1309 if kw in f:
1307 1310 val *= 10
1308 1311 return val
1309 1312 tests.sort(key=sortkey)
1310 1313
1311 1314 if 'PYTHONHASHSEED' not in os.environ:
1312 1315 # use a random python hash seed all the time
1313 1316 # we do the randomness ourself to know what seed is used
1314 1317 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1315 1318
1316 1319 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1317 1320 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1318 1321 if options.tmpdir:
1319 1322 options.keep_tmpdir = True
1320 1323 tmpdir = options.tmpdir
1321 1324 if os.path.exists(tmpdir):
1322 1325 # Meaning of tmpdir has changed since 1.3: we used to create
1323 1326 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1324 1327 # tmpdir already exists.
1325 1328 print "error: temp dir %r already exists" % tmpdir
1326 1329 return 1
1327 1330
1328 1331 # Automatically removing tmpdir sounds convenient, but could
1329 1332 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1330 1333 # or "--tmpdir=$HOME".
1331 1334 #vlog("# Removing temp dir", tmpdir)
1332 1335 #shutil.rmtree(tmpdir)
1333 1336 os.makedirs(tmpdir)
1334 1337 else:
1335 1338 d = None
1336 1339 if os.name == 'nt':
1337 1340 # without this, we get the default temp dir location, but
1338 1341 # in all lowercase, which causes troubles with paths (issue3490)
1339 1342 d = os.getenv('TMP')
1340 1343 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1341 1344 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1342 1345
1343 1346 if options.with_hg:
1344 1347 INST = None
1345 1348 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1346 1349 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1347 1350 os.makedirs(TMPBINDIR)
1348 1351
1349 1352 # This looks redundant with how Python initializes sys.path from
1350 1353 # the location of the script being executed. Needed because the
1351 1354 # "hg" specified by --with-hg is not the only Python script
1352 1355 # executed in the test suite that needs to import 'mercurial'
1353 1356 # ... which means it's not really redundant at all.
1354 1357 PYTHONDIR = BINDIR
1355 1358 else:
1356 1359 INST = os.path.join(HGTMP, "install")
1357 1360 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1358 1361 TMPBINDIR = BINDIR
1359 1362 PYTHONDIR = os.path.join(INST, "lib", "python")
1360 1363
1361 1364 os.environ["BINDIR"] = BINDIR
1362 1365 os.environ["PYTHON"] = PYTHON
1363 1366
1364 1367 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1365 1368 if TMPBINDIR != BINDIR:
1366 1369 path = [TMPBINDIR] + path
1367 1370 os.environ["PATH"] = os.pathsep.join(path)
1368 1371
1369 1372 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1370 1373 # can run .../tests/run-tests.py test-foo where test-foo
1371 1374 # adds an extension to HGRC. Also include run-test.py directory to import
1372 1375 # modules like heredoctest.
1373 1376 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1374 1377 # We have to augment PYTHONPATH, rather than simply replacing
1375 1378 # it, in case external libraries are only available via current
1376 1379 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1377 1380 # are in /opt/subversion.)
1378 1381 oldpypath = os.environ.get(IMPL_PATH)
1379 1382 if oldpypath:
1380 1383 pypath.append(oldpypath)
1381 1384 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1382 1385
1383 1386 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1384 1387
1385 1388 vlog("# Using TESTDIR", TESTDIR)
1386 1389 vlog("# Using HGTMP", HGTMP)
1387 1390 vlog("# Using PATH", os.environ["PATH"])
1388 1391 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1389 1392
1390 1393 try:
1391 1394 return runtests(options, tests) or 0
1392 1395 finally:
1393 1396 time.sleep(.1)
1394 1397 cleanup(options)
1395 1398
1396 1399 if __name__ == '__main__':
1397 1400 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now