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