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