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