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