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