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