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