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