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