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