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