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