##// END OF EJS Templates
run-tests: end doctest block when seeing a non-command
Idan Kamara -
r15248:3efbb5e8 default
parent child Browse files
Show More
@@ -1,1224 +1,1224
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 524 def transformtst(lines):
525 525 inblock = False
526 526 for l in lines:
527 527 if inblock:
528 if l.startswith(' $ '):
528 if l.startswith(' $ ') or not l.startswith(' '):
529 529 inblock = False
530 530 yield ' > EOF\n'
531 531 yield l
532 532 else:
533 533 yield ' > ' + l[2:]
534 534 else:
535 535 if l.startswith(' >>> '):
536 536 inblock = True
537 537 yield ' $ %s -m heredoctest <<EOF\n' % PYTHON
538 538 yield ' > ' + l[2:]
539 539 else:
540 540 yield l
541 541 if inblock:
542 542 yield ' > EOF\n'
543 543
544 544 def tsttest(test, wd, options, replacements):
545 545 t = open(test)
546 546 out = []
547 547 script = []
548 548 salt = "SALT" + str(time.time())
549 549
550 550 pos = prepos = -1
551 551 after = {}
552 552 expected = {}
553 553 for n, l in enumerate(transformtst(t)):
554 554 if not l.endswith('\n'):
555 555 l += '\n'
556 556 if l.startswith(' $ '): # commands
557 557 after.setdefault(pos, []).append(l)
558 558 prepos = pos
559 559 pos = n
560 560 script.append('echo %s %s $?\n' % (salt, n))
561 561 script.append(l[4:])
562 562 elif l.startswith(' > '): # continuations
563 563 after.setdefault(prepos, []).append(l)
564 564 script.append(l[4:])
565 565 elif l.startswith(' '): # results
566 566 # queue up a list of expected results
567 567 expected.setdefault(pos, []).append(l[2:])
568 568 else:
569 569 # non-command/result - queue up for merged output
570 570 after.setdefault(pos, []).append(l)
571 571
572 572 t.close()
573 573
574 574 script.append('echo %s %s $?\n' % (salt, n + 1))
575 575
576 576 fd, name = tempfile.mkstemp(suffix='hg-tst')
577 577
578 578 try:
579 579 for l in script:
580 580 os.write(fd, l)
581 581 os.close(fd)
582 582
583 583 cmd = '"%s" "%s"' % (options.shell, name)
584 584 vlog("# Running", cmd)
585 585 exitcode, output = run(cmd, wd, options, replacements)
586 586 # do not merge output if skipped, return hghave message instead
587 587 # similarly, with --debug, output is None
588 588 if exitcode == SKIPPED_STATUS or output is None:
589 589 return exitcode, output
590 590 finally:
591 591 os.remove(name)
592 592
593 593 def rematch(el, l):
594 594 try:
595 595 # ensure that the regex matches to the end of the string
596 596 return re.match(el + r'\Z', l)
597 597 except re.error:
598 598 # el is an invalid regex
599 599 return False
600 600
601 601 def globmatch(el, l):
602 602 # The only supported special characters are * and ?. Escaping is
603 603 # supported.
604 604 i, n = 0, len(el)
605 605 res = ''
606 606 while i < n:
607 607 c = el[i]
608 608 i += 1
609 609 if c == '\\' and el[i] in '*?\\':
610 610 res += el[i - 1:i + 1]
611 611 i += 1
612 612 elif c == '*':
613 613 res += '.*'
614 614 elif c == '?':
615 615 res += '.'
616 616 else:
617 617 res += re.escape(c)
618 618 return rematch(res, l)
619 619
620 620 pos = -1
621 621 postout = []
622 622 ret = 0
623 623 for n, l in enumerate(output):
624 624 lout, lcmd = l, None
625 625 if salt in l:
626 626 lout, lcmd = l.split(salt, 1)
627 627
628 628 if lout:
629 629 if lcmd:
630 630 lout += ' (no-eol)\n'
631 631
632 632 el = None
633 633 if pos in expected and expected[pos]:
634 634 el = expected[pos].pop(0)
635 635
636 636 if el == lout: # perfect match (fast)
637 637 postout.append(" " + lout)
638 638 elif (el and
639 639 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
640 640 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
641 641 or el.endswith(" (esc)\n") and
642 642 el.decode('string-escape') == l)):
643 643 postout.append(" " + el) # fallback regex/glob/esc match
644 644 else:
645 645 if needescape(lout):
646 646 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
647 647 postout.append(" " + lout) # let diff deal with it
648 648
649 649 if lcmd:
650 650 # add on last return code
651 651 ret = int(lcmd.split()[1])
652 652 if ret != 0:
653 653 postout.append(" [%s]\n" % ret)
654 654 if pos in after:
655 655 postout += after.pop(pos)
656 656 pos = int(lcmd.split()[0])
657 657
658 658 if pos in after:
659 659 postout += after.pop(pos)
660 660
661 661 return exitcode, postout
662 662
663 663 wifexited = getattr(os, "WIFEXITED", lambda x: False)
664 664 def run(cmd, wd, options, replacements):
665 665 """Run command in a sub-process, capturing the output (stdout and stderr).
666 666 Return a tuple (exitcode, output). output is None in debug mode."""
667 667 # TODO: Use subprocess.Popen if we're running on Python 2.4
668 668 if options.debug:
669 669 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
670 670 ret = proc.wait()
671 671 return (ret, None)
672 672
673 673 proc = Popen4(cmd, wd, options.timeout)
674 674 def cleanup():
675 675 terminate(proc)
676 676 ret = proc.wait()
677 677 if ret == 0:
678 678 ret = signal.SIGTERM << 8
679 679 killdaemons()
680 680 return ret
681 681
682 682 output = ''
683 683 proc.tochild.close()
684 684
685 685 try:
686 686 output = proc.fromchild.read()
687 687 except KeyboardInterrupt:
688 688 vlog('# Handling keyboard interrupt')
689 689 cleanup()
690 690 raise
691 691
692 692 ret = proc.wait()
693 693 if wifexited(ret):
694 694 ret = os.WEXITSTATUS(ret)
695 695
696 696 if proc.timeout:
697 697 ret = 'timeout'
698 698
699 699 if ret:
700 700 killdaemons()
701 701
702 702 for s, r in replacements:
703 703 output = re.sub(s, r, output)
704 704 return ret, splitnewlines(output)
705 705
706 706 def runone(options, test):
707 707 '''tristate output:
708 708 None -> skipped
709 709 True -> passed
710 710 False -> failed'''
711 711
712 712 global results, resultslock, iolock
713 713
714 714 testpath = os.path.join(TESTDIR, test)
715 715
716 716 def result(l, e):
717 717 resultslock.acquire()
718 718 results[l].append(e)
719 719 resultslock.release()
720 720
721 721 def skip(msg):
722 722 if not options.verbose:
723 723 result('s', (test, msg))
724 724 else:
725 725 iolock.acquire()
726 726 print "\nSkipping %s: %s" % (testpath, msg)
727 727 iolock.release()
728 728 return None
729 729
730 730 def fail(msg, ret):
731 731 if not options.nodiff:
732 732 iolock.acquire()
733 733 print "\nERROR: %s %s" % (testpath, msg)
734 734 iolock.release()
735 735 if (not ret and options.interactive
736 736 and os.path.exists(testpath + ".err")):
737 737 iolock.acquire()
738 738 print "Accept this change? [n] ",
739 739 answer = sys.stdin.readline().strip()
740 740 iolock.release()
741 741 if answer.lower() in "y yes".split():
742 742 if test.endswith(".t"):
743 743 rename(testpath + ".err", testpath)
744 744 else:
745 745 rename(testpath + ".err", testpath + ".out")
746 746 result('p', test)
747 747 return
748 748 result('f', (test, msg))
749 749
750 750 def success():
751 751 result('p', test)
752 752
753 753 def ignore(msg):
754 754 result('i', (test, msg))
755 755
756 756 if (os.path.basename(test).startswith("test-") and '~' not in test and
757 757 ('.' not in test or test.endswith('.py') or
758 758 test.endswith('.bat') or test.endswith('.t'))):
759 759 if not os.path.exists(test):
760 760 skip("doesn't exist")
761 761 return None
762 762 else:
763 763 vlog('# Test file', test, 'not supported, ignoring')
764 764 return None # not a supported test, don't record
765 765
766 766 if not (options.whitelisted and test in options.whitelisted):
767 767 if options.blacklist and test in options.blacklist:
768 768 skip("blacklisted")
769 769 return None
770 770
771 771 if options.retest and not os.path.exists(test + ".err"):
772 772 ignore("not retesting")
773 773 return None
774 774
775 775 if options.keywords:
776 776 fp = open(test)
777 777 t = fp.read().lower() + test.lower()
778 778 fp.close()
779 779 for k in options.keywords.lower().split():
780 780 if k in t:
781 781 break
782 782 else:
783 783 ignore("doesn't match keyword")
784 784 return None
785 785
786 786 vlog("# Test", test)
787 787
788 788 # create a fresh hgrc
789 789 hgrc = open(HGRCPATH, 'w+')
790 790 hgrc.write('[ui]\n')
791 791 hgrc.write('slash = True\n')
792 792 hgrc.write('[defaults]\n')
793 793 hgrc.write('backout = -d "0 0"\n')
794 794 hgrc.write('commit = -d "0 0"\n')
795 795 hgrc.write('tag = -d "0 0"\n')
796 796 if options.inotify:
797 797 hgrc.write('[extensions]\n')
798 798 hgrc.write('inotify=\n')
799 799 hgrc.write('[inotify]\n')
800 800 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
801 801 hgrc.write('appendpid=True\n')
802 802 if options.extra_config_opt:
803 803 for opt in options.extra_config_opt:
804 804 section, key = opt.split('.', 1)
805 805 assert '=' in key, ('extra config opt %s must '
806 806 'have an = for assignment' % opt)
807 807 hgrc.write('[%s]\n%s\n' % (section, key))
808 808 hgrc.close()
809 809
810 810 ref = os.path.join(TESTDIR, test+".out")
811 811 err = os.path.join(TESTDIR, test+".err")
812 812 if os.path.exists(err):
813 813 os.remove(err) # Remove any previous output files
814 814 try:
815 815 tf = open(testpath)
816 816 firstline = tf.readline().rstrip()
817 817 tf.close()
818 818 except:
819 819 firstline = ''
820 820 lctest = test.lower()
821 821
822 822 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
823 823 runner = pytest
824 824 elif lctest.endswith('.t'):
825 825 runner = tsttest
826 826 ref = testpath
827 827 else:
828 828 # do not try to run non-executable programs
829 829 if not os.access(testpath, os.X_OK):
830 830 return skip("not executable")
831 831 runner = shtest
832 832
833 833 # Make a tmp subdirectory to work in
834 834 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
835 835 os.path.join(HGTMP, os.path.basename(test))
836 836
837 837 os.mkdir(testtmp)
838 838 ret, out = runner(testpath, testtmp, options, [
839 839 (re.escape(testtmp), '$TESTTMP'),
840 840 (r':%s\b' % options.port, ':$HGPORT'),
841 841 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
842 842 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
843 843 ])
844 844 vlog("# Ret was:", ret)
845 845
846 846 mark = '.'
847 847
848 848 skipped = (ret == SKIPPED_STATUS)
849 849
850 850 # If we're not in --debug mode and reference output file exists,
851 851 # check test output against it.
852 852 if options.debug:
853 853 refout = None # to match "out is None"
854 854 elif os.path.exists(ref):
855 855 f = open(ref, "r")
856 856 refout = list(transformtst(splitnewlines(f.read())))
857 857 f.close()
858 858 else:
859 859 refout = []
860 860
861 861 if (ret != 0 or out != refout) and not skipped and not options.debug:
862 862 # Save errors to a file for diagnosis
863 863 f = open(err, "wb")
864 864 for line in out:
865 865 f.write(line)
866 866 f.close()
867 867
868 868 if skipped:
869 869 mark = 's'
870 870 if out is None: # debug mode: nothing to parse
871 871 missing = ['unknown']
872 872 failed = None
873 873 else:
874 874 missing, failed = parsehghaveoutput(out)
875 875 if not missing:
876 876 missing = ['irrelevant']
877 877 if failed:
878 878 fail("hghave failed checking for %s" % failed[-1], ret)
879 879 skipped = False
880 880 else:
881 881 skip(missing[-1])
882 882 elif ret == 'timeout':
883 883 mark = 't'
884 884 fail("timed out", ret)
885 885 elif out != refout:
886 886 mark = '!'
887 887 if not options.nodiff:
888 888 iolock.acquire()
889 889 if options.view:
890 890 os.system("%s %s %s" % (options.view, ref, err))
891 891 else:
892 892 showdiff(refout, out, ref, err)
893 893 iolock.release()
894 894 if ret:
895 895 fail("output changed and returned error code %d" % ret, ret)
896 896 else:
897 897 fail("output changed", ret)
898 898 ret = 1
899 899 elif ret:
900 900 mark = '!'
901 901 fail("returned error code %d" % ret, ret)
902 902 else:
903 903 success()
904 904
905 905 if not options.verbose:
906 906 iolock.acquire()
907 907 sys.stdout.write(mark)
908 908 sys.stdout.flush()
909 909 iolock.release()
910 910
911 911 killdaemons()
912 912
913 913 if not options.keep_tmpdir:
914 914 shutil.rmtree(testtmp, True)
915 915 if skipped:
916 916 return None
917 917 return ret == 0
918 918
919 919 _hgpath = None
920 920
921 921 def _gethgpath():
922 922 """Return the path to the mercurial package that is actually found by
923 923 the current Python interpreter."""
924 924 global _hgpath
925 925 if _hgpath is not None:
926 926 return _hgpath
927 927
928 928 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
929 929 pipe = os.popen(cmd % PYTHON)
930 930 try:
931 931 _hgpath = pipe.read().strip()
932 932 finally:
933 933 pipe.close()
934 934 return _hgpath
935 935
936 936 def _checkhglib(verb):
937 937 """Ensure that the 'mercurial' package imported by python is
938 938 the one we expect it to be. If not, print a warning to stderr."""
939 939 expecthg = os.path.join(PYTHONDIR, 'mercurial')
940 940 actualhg = _gethgpath()
941 941 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
942 942 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
943 943 ' (expected %s)\n'
944 944 % (verb, actualhg, expecthg))
945 945
946 946 def runchildren(options, tests):
947 947 if INST:
948 948 installhg(options)
949 949 _checkhglib("Testing")
950 950
951 951 optcopy = dict(options.__dict__)
952 952 optcopy['jobs'] = 1
953 953
954 954 # Because whitelist has to override keyword matches, we have to
955 955 # actually load the whitelist in the children as well, so we allow
956 956 # the list of whitelist files to pass through and be parsed in the
957 957 # children, but not the dict of whitelisted tests resulting from
958 958 # the parse, used here to override blacklisted tests.
959 959 whitelist = optcopy['whitelisted'] or []
960 960 del optcopy['whitelisted']
961 961
962 962 blacklist = optcopy['blacklist'] or []
963 963 del optcopy['blacklist']
964 964 blacklisted = []
965 965
966 966 if optcopy['with_hg'] is None:
967 967 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
968 968 optcopy.pop('anycoverage', None)
969 969
970 970 opts = []
971 971 for opt, value in optcopy.iteritems():
972 972 name = '--' + opt.replace('_', '-')
973 973 if value is True:
974 974 opts.append(name)
975 975 elif isinstance(value, list):
976 976 for v in value:
977 977 opts.append(name + '=' + str(v))
978 978 elif value is not None:
979 979 opts.append(name + '=' + str(value))
980 980
981 981 tests.reverse()
982 982 jobs = [[] for j in xrange(options.jobs)]
983 983 while tests:
984 984 for job in jobs:
985 985 if not tests:
986 986 break
987 987 test = tests.pop()
988 988 if test not in whitelist and test in blacklist:
989 989 blacklisted.append(test)
990 990 else:
991 991 job.append(test)
992 992 fps = {}
993 993
994 994 for j, job in enumerate(jobs):
995 995 if not job:
996 996 continue
997 997 rfd, wfd = os.pipe()
998 998 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
999 999 childtmp = os.path.join(HGTMP, 'child%d' % j)
1000 1000 childopts += ['--tmpdir', childtmp]
1001 1001 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1002 1002 vlog(' '.join(cmdline))
1003 1003 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1004 1004 os.close(wfd)
1005 1005 signal.signal(signal.SIGINT, signal.SIG_IGN)
1006 1006 failures = 0
1007 1007 tested, skipped, failed = 0, 0, 0
1008 1008 skips = []
1009 1009 fails = []
1010 1010 while fps:
1011 1011 pid, status = os.wait()
1012 1012 fp = fps.pop(pid)
1013 1013 l = fp.read().splitlines()
1014 1014 try:
1015 1015 test, skip, fail = map(int, l[:3])
1016 1016 except ValueError:
1017 1017 test, skip, fail = 0, 0, 0
1018 1018 split = -fail or len(l)
1019 1019 for s in l[3:split]:
1020 1020 skips.append(s.split(" ", 1))
1021 1021 for s in l[split:]:
1022 1022 fails.append(s.split(" ", 1))
1023 1023 tested += test
1024 1024 skipped += skip
1025 1025 failed += fail
1026 1026 vlog('pid %d exited, status %d' % (pid, status))
1027 1027 failures |= status
1028 1028 print
1029 1029 skipped += len(blacklisted)
1030 1030 if not options.noskips:
1031 1031 for s in skips:
1032 1032 print "Skipped %s: %s" % (s[0], s[1])
1033 1033 for s in blacklisted:
1034 1034 print "Skipped %s: blacklisted" % s
1035 1035 for s in fails:
1036 1036 print "Failed %s: %s" % (s[0], s[1])
1037 1037
1038 1038 _checkhglib("Tested")
1039 1039 print "# Ran %d tests, %d skipped, %d failed." % (
1040 1040 tested, skipped, failed)
1041 1041
1042 1042 if options.anycoverage:
1043 1043 outputcoverage(options)
1044 1044 sys.exit(failures != 0)
1045 1045
1046 1046 results = dict(p=[], f=[], s=[], i=[])
1047 1047 resultslock = threading.Lock()
1048 1048 iolock = threading.Lock()
1049 1049
1050 1050 def runqueue(options, tests, results):
1051 1051 for test in tests:
1052 1052 ret = runone(options, test)
1053 1053 if options.first and ret is not None and not ret:
1054 1054 break
1055 1055
1056 1056 def runtests(options, tests):
1057 1057 global DAEMON_PIDS, HGRCPATH
1058 1058 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1059 1059 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1060 1060
1061 1061 try:
1062 1062 if INST:
1063 1063 installhg(options)
1064 1064 _checkhglib("Testing")
1065 1065
1066 1066 if options.restart:
1067 1067 orig = list(tests)
1068 1068 while tests:
1069 1069 if os.path.exists(tests[0] + ".err"):
1070 1070 break
1071 1071 tests.pop(0)
1072 1072 if not tests:
1073 1073 print "running all tests"
1074 1074 tests = orig
1075 1075
1076 1076 runqueue(options, tests, results)
1077 1077
1078 1078 failed = len(results['f'])
1079 1079 tested = len(results['p']) + failed
1080 1080 skipped = len(results['s'])
1081 1081 ignored = len(results['i'])
1082 1082
1083 1083 if options.child:
1084 1084 fp = os.fdopen(options.child, 'w')
1085 1085 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1086 1086 for s in results['s']:
1087 1087 fp.write("%s %s\n" % s)
1088 1088 for s in results['f']:
1089 1089 fp.write("%s %s\n" % s)
1090 1090 fp.close()
1091 1091 else:
1092 1092 print
1093 1093 for s in results['s']:
1094 1094 print "Skipped %s: %s" % s
1095 1095 for s in results['f']:
1096 1096 print "Failed %s: %s" % s
1097 1097 _checkhglib("Tested")
1098 1098 print "# Ran %d tests, %d skipped, %d failed." % (
1099 1099 tested, skipped + ignored, failed)
1100 1100
1101 1101 if options.anycoverage:
1102 1102 outputcoverage(options)
1103 1103 except KeyboardInterrupt:
1104 1104 failed = True
1105 1105 print "\ninterrupted!"
1106 1106
1107 1107 if failed:
1108 1108 sys.exit(1)
1109 1109
1110 1110 def main():
1111 1111 (options, args) = parseargs()
1112 1112 if not options.child:
1113 1113 os.umask(022)
1114 1114
1115 1115 checktools()
1116 1116
1117 1117 if len(args) == 0:
1118 1118 args = os.listdir(".")
1119 1119 args.sort()
1120 1120
1121 1121 tests = args
1122 1122
1123 1123 # Reset some environment variables to well-known values so that
1124 1124 # the tests produce repeatable output.
1125 1125 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1126 1126 os.environ['TZ'] = 'GMT'
1127 1127 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1128 1128 os.environ['CDPATH'] = ''
1129 1129 os.environ['COLUMNS'] = '80'
1130 1130 os.environ['GREP_OPTIONS'] = ''
1131 1131 os.environ['http_proxy'] = ''
1132 1132
1133 1133 # unset env related to hooks
1134 1134 for k in os.environ.keys():
1135 1135 if k.startswith('HG_'):
1136 1136 # can't remove on solaris
1137 1137 os.environ[k] = ''
1138 1138 del os.environ[k]
1139 1139
1140 1140 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1141 1141 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1142 1142 if options.tmpdir:
1143 1143 options.keep_tmpdir = True
1144 1144 tmpdir = options.tmpdir
1145 1145 if os.path.exists(tmpdir):
1146 1146 # Meaning of tmpdir has changed since 1.3: we used to create
1147 1147 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1148 1148 # tmpdir already exists.
1149 1149 sys.exit("error: temp dir %r already exists" % tmpdir)
1150 1150
1151 1151 # Automatically removing tmpdir sounds convenient, but could
1152 1152 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1153 1153 # or "--tmpdir=$HOME".
1154 1154 #vlog("# Removing temp dir", tmpdir)
1155 1155 #shutil.rmtree(tmpdir)
1156 1156 os.makedirs(tmpdir)
1157 1157 else:
1158 1158 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1159 1159 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1160 1160 DAEMON_PIDS = None
1161 1161 HGRCPATH = None
1162 1162
1163 1163 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1164 1164 os.environ["HGMERGE"] = "internal:merge"
1165 1165 os.environ["HGUSER"] = "test"
1166 1166 os.environ["HGENCODING"] = "ascii"
1167 1167 os.environ["HGENCODINGMODE"] = "strict"
1168 1168 os.environ["HGPORT"] = str(options.port)
1169 1169 os.environ["HGPORT1"] = str(options.port + 1)
1170 1170 os.environ["HGPORT2"] = str(options.port + 2)
1171 1171
1172 1172 if options.with_hg:
1173 1173 INST = None
1174 1174 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1175 1175
1176 1176 # This looks redundant with how Python initializes sys.path from
1177 1177 # the location of the script being executed. Needed because the
1178 1178 # "hg" specified by --with-hg is not the only Python script
1179 1179 # executed in the test suite that needs to import 'mercurial'
1180 1180 # ... which means it's not really redundant at all.
1181 1181 PYTHONDIR = BINDIR
1182 1182 else:
1183 1183 INST = os.path.join(HGTMP, "install")
1184 1184 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1185 1185 PYTHONDIR = os.path.join(INST, "lib", "python")
1186 1186
1187 1187 os.environ["BINDIR"] = BINDIR
1188 1188 os.environ["PYTHON"] = PYTHON
1189 1189
1190 1190 if not options.child:
1191 1191 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1192 1192 os.environ["PATH"] = os.pathsep.join(path)
1193 1193
1194 1194 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1195 1195 # can run .../tests/run-tests.py test-foo where test-foo
1196 1196 # adds an extension to HGRC
1197 1197 pypath = [PYTHONDIR, TESTDIR]
1198 1198 # We have to augment PYTHONPATH, rather than simply replacing
1199 1199 # it, in case external libraries are only available via current
1200 1200 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1201 1201 # are in /opt/subversion.)
1202 1202 oldpypath = os.environ.get(IMPL_PATH)
1203 1203 if oldpypath:
1204 1204 pypath.append(oldpypath)
1205 1205 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1206 1206
1207 1207 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1208 1208
1209 1209 vlog("# Using TESTDIR", TESTDIR)
1210 1210 vlog("# Using HGTMP", HGTMP)
1211 1211 vlog("# Using PATH", os.environ["PATH"])
1212 1212 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1213 1213
1214 1214 try:
1215 1215 if len(tests) > 1 and options.jobs > 1:
1216 1216 runchildren(options, tests)
1217 1217 else:
1218 1218 runtests(options, tests)
1219 1219 finally:
1220 1220 time.sleep(1)
1221 1221 cleanup(options)
1222 1222
1223 1223 if __name__ == '__main__':
1224 1224 main()
General Comments 0
You need to be logged in to leave comments. Login now