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