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