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