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