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