##// END OF EJS Templates
run-tests.py: remove support for .bat files
Benoit Boissinot -
r12678:61642a46 default
parent child Browse files
Show More
@@ -1,1107 +1,1090 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 def battest(test, options, replacements):
458 # To reliably get the error code from batch files on WinXP,
459 # the "cmd /c call" prefix is needed. Grrr
460 cmd = 'cmd /c call "%s"' % testpath
461 vlog("# Running", cmd)
462 return run(cmd, options, replacements)
463
464 457 def tsttest(test, options, replacements):
465 458 t = open(test)
466 459 out = []
467 460 script = []
468 461 salt = "SALT" + str(time.time())
469 462
470 463 pos = prepos = -1
471 464 after = {}
472 465 expected = {}
473 466 for n, l in enumerate(t):
474 467 if l.startswith(' $ '): # commands
475 468 after.setdefault(pos, []).append(l)
476 469 prepos = pos
477 470 pos = n
478 471 script.append('echo %s %s $?\n' % (salt, n))
479 472 script.append(l[4:])
480 473 elif l.startswith(' > '): # continuations
481 474 after.setdefault(prepos, []).append(l)
482 475 script.append(l[4:])
483 476 elif l.startswith(' '): # results
484 477 # queue up a list of expected results
485 478 expected.setdefault(pos, []).append(l[2:])
486 479 else:
487 480 # non-command/result - queue up for merged output
488 481 after.setdefault(pos, []).append(l)
489 482
490 483 if script and not script[-1].endswith('\n'):
491 484 script[-1] = script[-1] + '\n'
492 485 script.append('echo %s %s $?\n' % (salt, n + 1))
493 486
494 487 fd, name = tempfile.mkstemp(suffix='hg-tst')
495 488
496 489 try:
497 490 for l in script:
498 491 os.write(fd, l)
499 492 os.close(fd)
500 493
501 494 cmd = '/bin/sh "%s"' % name
502 495 vlog("# Running", cmd)
503 496 exitcode, output = run(cmd, options, replacements)
504 497 # do not merge output if skipped, return hghave message instead
505 498 if exitcode == SKIPPED_STATUS:
506 499 return exitcode, output
507 500 finally:
508 501 os.remove(name)
509 502
510 503 def rematch(el, l):
511 504 try:
512 505 # ensure that the regex matches to the end of the string
513 506 return re.match(el + r'\Z', l)
514 507 except re.error:
515 508 # el is an invalid regex
516 509 return False
517 510
518 511 def globmatch(el, l):
519 512 # The only supported special characters are * and ?. Escaping is
520 513 # supported.
521 514 i, n = 0, len(el)
522 515 res = ''
523 516 while i < n:
524 517 c = el[i]
525 518 i += 1
526 519 if c == '\\' and el[i] in '*?\\':
527 520 res += el[i - 1:i + 1]
528 521 i += 1
529 522 elif c == '*':
530 523 res += '.*'
531 524 elif c == '?':
532 525 res += '.'
533 526 else:
534 527 res += re.escape(c)
535 528 return rematch(res, l)
536 529
537 530 pos = -1
538 531 postout = []
539 532 ret = 0
540 533 for n, l in enumerate(output):
541 534 if l.startswith(salt):
542 535 # add on last return code
543 536 ret = int(l.split()[2])
544 537 if ret != 0:
545 538 postout.append(" [%s]\n" % ret)
546 539 if pos in after:
547 540 postout += after.pop(pos)
548 541 pos = int(l.split()[1])
549 542 else:
550 543 el = None
551 544 if pos in expected and expected[pos]:
552 545 el = expected[pos].pop(0)
553 546
554 547 if el == l: # perfect match (fast)
555 548 postout.append(" " + l)
556 549 elif (el and
557 550 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
558 551 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
559 552 postout.append(" " + el) # fallback regex/glob match
560 553 else:
561 554 postout.append(" " + l) # let diff deal with it
562 555
563 556 if pos in after:
564 557 postout += after.pop(pos)
565 558
566 559 return exitcode, postout
567 560
568 561 def run(cmd, options, replacements):
569 562 """Run command in a sub-process, capturing the output (stdout and stderr).
570 563 Return a tuple (exitcode, output). output is None in debug mode."""
571 564 # TODO: Use subprocess.Popen if we're running on Python 2.4
572 565 if options.debug:
573 566 proc = subprocess.Popen(cmd, shell=True)
574 567 ret = proc.wait()
575 568 return (ret, None)
576 569
577 570 if os.name == 'nt' or sys.platform.startswith('java'):
578 571 tochild, fromchild = os.popen4(cmd)
579 572 tochild.close()
580 573 output = fromchild.read()
581 574 ret = fromchild.close()
582 575 if ret == None:
583 576 ret = 0
584 577 else:
585 578 proc = Popen4(cmd)
586 579 def cleanup():
587 580 os.kill(proc.pid, signal.SIGTERM)
588 581 ret = proc.wait()
589 582 if ret == 0:
590 583 ret = signal.SIGTERM << 8
591 584 killdaemons()
592 585 return ret
593 586
594 587 try:
595 588 output = ''
596 589 proc.tochild.close()
597 590 output = proc.fromchild.read()
598 591 ret = proc.wait()
599 592 if os.WIFEXITED(ret):
600 593 ret = os.WEXITSTATUS(ret)
601 594 except Timeout:
602 595 vlog('# Process %d timed out - killing it' % proc.pid)
603 596 ret = cleanup()
604 597 output += ("\n### Abort: timeout after %d seconds.\n"
605 598 % options.timeout)
606 599 except KeyboardInterrupt:
607 600 vlog('# Handling keyboard interrupt')
608 601 cleanup()
609 602 raise
610 603
611 604 for s, r in replacements:
612 605 output = output.replace(s, r)
613 606 return ret, splitnewlines(output)
614 607
615 608 def runone(options, test, skips, fails):
616 609 '''tristate output:
617 610 None -> skipped
618 611 True -> passed
619 612 False -> failed'''
620 613
621 614 def skip(msg):
622 615 if not options.verbose:
623 616 skips.append((test, msg))
624 617 else:
625 618 print "\nSkipping %s: %s" % (testpath, msg)
626 619 return None
627 620
628 621 def fail(msg):
629 622 fails.append((test, msg))
630 623 if not options.nodiff:
631 624 print "\nERROR: %s %s" % (testpath, msg)
632 625 return None
633 626
634 627 vlog("# Test", test)
635 628
636 629 # create a fresh hgrc
637 630 hgrc = open(HGRCPATH, 'w+')
638 631 hgrc.write('[ui]\n')
639 632 hgrc.write('slash = True\n')
640 633 hgrc.write('[defaults]\n')
641 634 hgrc.write('backout = -d "0 0"\n')
642 635 hgrc.write('commit = -d "0 0"\n')
643 636 hgrc.write('tag = -d "0 0"\n')
644 637 if options.inotify:
645 638 hgrc.write('[extensions]\n')
646 639 hgrc.write('inotify=\n')
647 640 hgrc.write('[inotify]\n')
648 641 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
649 642 hgrc.write('appendpid=True\n')
650 643 hgrc.close()
651 644
652 645 testpath = os.path.join(TESTDIR, test)
653 646 ref = os.path.join(TESTDIR, test+".out")
654 647 err = os.path.join(TESTDIR, test+".err")
655 648 if os.path.exists(err):
656 649 os.remove(err) # Remove any previous output files
657 650 try:
658 651 tf = open(testpath)
659 652 firstline = tf.readline().rstrip()
660 653 tf.close()
661 654 except:
662 655 firstline = ''
663 656 lctest = test.lower()
664 657
665 658 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
666 659 runner = pytest
667 elif lctest.endswith('.bat'):
668 # do not run batch scripts on non-windows
669 if os.name != 'nt':
670 return skip("batch script")
671 runner = battest
672 660 elif lctest.endswith('.t'):
673 661 runner = tsttest
674 662 ref = testpath
675 663 else:
676 # do not run shell scripts on windows
677 if os.name == 'nt':
678 return skip("shell script")
679 664 # do not try to run non-executable programs
680 if not os.path.exists(testpath):
681 return fail("does not exist")
682 elif not os.access(testpath, os.X_OK):
665 if not os.access(testpath, os.X_OK):
683 666 return skip("not executable")
684 667 runner = shtest
685 668
686 669 # Make a tmp subdirectory to work in
687 670 testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test)
688 671 os.mkdir(testtmp)
689 672 os.chdir(testtmp)
690 673
691 674 if options.timeout > 0:
692 675 signal.alarm(options.timeout)
693 676
694 677 ret, out = runner(testpath, options, [
695 678 (testtmp, '$TESTTMP'),
696 679 (':%s' % options.port, ':$HGPORT'),
697 680 (':%s' % (options.port + 1), ':$HGPORT1'),
698 681 (':%s' % (options.port + 2), ':$HGPORT2'),
699 682 ])
700 683 vlog("# Ret was:", ret)
701 684
702 685 if options.timeout > 0:
703 686 signal.alarm(0)
704 687
705 688 mark = '.'
706 689
707 690 skipped = (ret == SKIPPED_STATUS)
708 691
709 692 # If we're not in --debug mode and reference output file exists,
710 693 # check test output against it.
711 694 if options.debug:
712 695 refout = None # to match out == None
713 696 elif os.path.exists(ref):
714 697 f = open(ref, "r")
715 698 refout = splitnewlines(f.read())
716 699 f.close()
717 700 else:
718 701 refout = []
719 702
720 703 if (ret != 0 or out != refout) and not skipped and not options.debug:
721 704 # Save errors to a file for diagnosis
722 705 f = open(err, "wb")
723 706 for line in out:
724 707 f.write(line)
725 708 f.close()
726 709
727 710 if skipped:
728 711 mark = 's'
729 712 if out is None: # debug mode: nothing to parse
730 713 missing = ['unknown']
731 714 failed = None
732 715 else:
733 716 missing, failed = parsehghaveoutput(out)
734 717 if not missing:
735 718 missing = ['irrelevant']
736 719 if failed:
737 720 fail("hghave failed checking for %s" % failed[-1])
738 721 skipped = False
739 722 else:
740 723 skip(missing[-1])
741 724 elif out != refout:
742 725 mark = '!'
743 726 if ret:
744 727 fail("output changed and returned error code %d" % ret)
745 728 else:
746 729 fail("output changed")
747 730 if not options.nodiff:
748 731 if options.view:
749 732 os.system("%s %s %s" % (options.view, ref, err))
750 733 else:
751 734 showdiff(refout, out, ref, err)
752 735 ret = 1
753 736 elif ret:
754 737 mark = '!'
755 738 fail("returned error code %d" % ret)
756 739
757 740 if not options.verbose:
758 741 sys.stdout.write(mark)
759 742 sys.stdout.flush()
760 743
761 744 killdaemons()
762 745
763 746 os.chdir(TESTDIR)
764 747 if not options.keep_tmpdir:
765 748 shutil.rmtree(testtmp, True)
766 749 if skipped:
767 750 return None
768 751 return ret == 0
769 752
770 753 _hgpath = None
771 754
772 755 def _gethgpath():
773 756 """Return the path to the mercurial package that is actually found by
774 757 the current Python interpreter."""
775 758 global _hgpath
776 759 if _hgpath is not None:
777 760 return _hgpath
778 761
779 762 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
780 763 pipe = os.popen(cmd % PYTHON)
781 764 try:
782 765 _hgpath = pipe.read().strip()
783 766 finally:
784 767 pipe.close()
785 768 return _hgpath
786 769
787 770 def _checkhglib(verb):
788 771 """Ensure that the 'mercurial' package imported by python is
789 772 the one we expect it to be. If not, print a warning to stderr."""
790 773 expecthg = os.path.join(PYTHONDIR, 'mercurial')
791 774 actualhg = _gethgpath()
792 775 if actualhg != expecthg:
793 776 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
794 777 ' (expected %s)\n'
795 778 % (verb, actualhg, expecthg))
796 779
797 780 def runchildren(options, tests):
798 781 if INST:
799 782 installhg(options)
800 783 _checkhglib("Testing")
801 784
802 785 optcopy = dict(options.__dict__)
803 786 optcopy['jobs'] = 1
804 787 del optcopy['blacklist']
805 788 if optcopy['with_hg'] is None:
806 789 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
807 790 optcopy.pop('anycoverage', None)
808 791
809 792 opts = []
810 793 for opt, value in optcopy.iteritems():
811 794 name = '--' + opt.replace('_', '-')
812 795 if value is True:
813 796 opts.append(name)
814 797 elif value is not None:
815 798 opts.append(name + '=' + str(value))
816 799
817 800 tests.reverse()
818 801 jobs = [[] for j in xrange(options.jobs)]
819 802 while tests:
820 803 for job in jobs:
821 804 if not tests:
822 805 break
823 806 job.append(tests.pop())
824 807 fps = {}
825 808
826 809 for j, job in enumerate(jobs):
827 810 if not job:
828 811 continue
829 812 rfd, wfd = os.pipe()
830 813 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
831 814 childtmp = os.path.join(HGTMP, 'child%d' % j)
832 815 childopts += ['--tmpdir', childtmp]
833 816 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
834 817 vlog(' '.join(cmdline))
835 818 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
836 819 os.close(wfd)
837 820 signal.signal(signal.SIGINT, signal.SIG_IGN)
838 821 failures = 0
839 822 tested, skipped, failed = 0, 0, 0
840 823 skips = []
841 824 fails = []
842 825 while fps:
843 826 pid, status = os.wait()
844 827 fp = fps.pop(pid)
845 828 l = fp.read().splitlines()
846 829 try:
847 830 test, skip, fail = map(int, l[:3])
848 831 except ValueError:
849 832 test, skip, fail = 0, 0, 0
850 833 split = -fail or len(l)
851 834 for s in l[3:split]:
852 835 skips.append(s.split(" ", 1))
853 836 for s in l[split:]:
854 837 fails.append(s.split(" ", 1))
855 838 tested += test
856 839 skipped += skip
857 840 failed += fail
858 841 vlog('pid %d exited, status %d' % (pid, status))
859 842 failures |= status
860 843 print
861 844 if not options.noskips:
862 845 for s in skips:
863 846 print "Skipped %s: %s" % (s[0], s[1])
864 847 for s in fails:
865 848 print "Failed %s: %s" % (s[0], s[1])
866 849
867 850 _checkhglib("Tested")
868 851 print "# Ran %d tests, %d skipped, %d failed." % (
869 852 tested, skipped, failed)
870 853
871 854 if options.anycoverage:
872 855 outputcoverage(options)
873 856 sys.exit(failures != 0)
874 857
875 858 def runtests(options, tests):
876 859 global DAEMON_PIDS, HGRCPATH
877 860 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
878 861 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
879 862
880 863 try:
881 864 if INST:
882 865 installhg(options)
883 866 _checkhglib("Testing")
884 867
885 868 if options.timeout > 0:
886 869 try:
887 870 signal.signal(signal.SIGALRM, alarmed)
888 871 vlog('# Running each test with %d second timeout' %
889 872 options.timeout)
890 873 except AttributeError:
891 874 print 'WARNING: cannot run tests with timeouts'
892 875 options.timeout = 0
893 876
894 877 tested = 0
895 878 failed = 0
896 879 skipped = 0
897 880
898 881 if options.restart:
899 882 orig = list(tests)
900 883 while tests:
901 884 if os.path.exists(tests[0] + ".err"):
902 885 break
903 886 tests.pop(0)
904 887 if not tests:
905 888 print "running all tests"
906 889 tests = orig
907 890
908 891 skips = []
909 892 fails = []
910 893
911 894 for test in tests:
912 895 if options.blacklist:
913 896 filename = options.blacklist.get(test)
914 897 if filename is not None:
915 898 skips.append((test, "blacklisted (%s)" % filename))
916 899 skipped += 1
917 900 continue
918 901
919 902 if options.retest and not os.path.exists(test + ".err"):
920 903 skipped += 1
921 904 continue
922 905
923 906 if options.keywords:
924 907 t = open(test).read().lower() + test.lower()
925 908 for k in options.keywords.lower().split():
926 909 if k in t:
927 910 break
928 911 else:
929 912 skipped += 1
930 913 continue
931 914
932 915 ret = runone(options, test, skips, fails)
933 916 if ret is None:
934 917 skipped += 1
935 918 elif not ret:
936 919 if options.interactive:
937 920 print "Accept this change? [n] ",
938 921 answer = sys.stdin.readline().strip()
939 922 if answer.lower() in "y yes".split():
940 923 if test.endswith(".t"):
941 924 rename(test + ".err", test)
942 925 else:
943 926 rename(test + ".err", test + ".out")
944 927 tested += 1
945 928 fails.pop()
946 929 continue
947 930 failed += 1
948 931 if options.first:
949 932 break
950 933 tested += 1
951 934
952 935 if options.child:
953 936 fp = os.fdopen(options.child, 'w')
954 937 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
955 938 for s in skips:
956 939 fp.write("%s %s\n" % s)
957 940 for s in fails:
958 941 fp.write("%s %s\n" % s)
959 942 fp.close()
960 943 else:
961 944 print
962 945 for s in skips:
963 946 print "Skipped %s: %s" % s
964 947 for s in fails:
965 948 print "Failed %s: %s" % s
966 949 _checkhglib("Tested")
967 950 print "# Ran %d tests, %d skipped, %d failed." % (
968 951 tested, skipped, failed)
969 952
970 953 if options.anycoverage:
971 954 outputcoverage(options)
972 955 except KeyboardInterrupt:
973 956 failed = True
974 957 print "\ninterrupted!"
975 958
976 959 if failed:
977 960 sys.exit(1)
978 961
979 962 def main():
980 963 (options, args) = parseargs()
981 964 if not options.child:
982 965 os.umask(022)
983 966
984 967 checktools()
985 968
986 969 if len(args) == 0:
987 970 args = os.listdir(".")
988 971 args.sort()
989 972
990 973 tests = []
991 974 skipped = []
992 975 for test in args:
993 976 if (test.startswith("test-") and '~' not in test and
994 977 ('.' not in test or test.endswith('.py') or
995 978 test.endswith('.bat') or test.endswith('.t'))):
996 979 if not os.path.exists(test):
997 980 skipped.append(test)
998 981 else:
999 982 tests.append(test)
1000 983 if not tests:
1001 984 for test in skipped:
1002 985 print 'Skipped %s: does not exist' % test
1003 986 print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped)
1004 987 return
1005 988 tests = tests + skipped
1006 989
1007 990 # Reset some environment variables to well-known values so that
1008 991 # the tests produce repeatable output.
1009 992 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1010 993 os.environ['TZ'] = 'GMT'
1011 994 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1012 995 os.environ['CDPATH'] = ''
1013 996 os.environ['COLUMNS'] = '80'
1014 997 os.environ['GREP_OPTIONS'] = ''
1015 998 os.environ['http_proxy'] = ''
1016 999
1017 1000 # unset env related to hooks
1018 1001 for k in os.environ.keys():
1019 1002 if k.startswith('HG_'):
1020 1003 # can't remove on solaris
1021 1004 os.environ[k] = ''
1022 1005 del os.environ[k]
1023 1006
1024 1007 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1025 1008 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1026 1009 if options.tmpdir:
1027 1010 options.keep_tmpdir = True
1028 1011 tmpdir = options.tmpdir
1029 1012 if os.path.exists(tmpdir):
1030 1013 # Meaning of tmpdir has changed since 1.3: we used to create
1031 1014 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1032 1015 # tmpdir already exists.
1033 1016 sys.exit("error: temp dir %r already exists" % tmpdir)
1034 1017
1035 1018 # Automatically removing tmpdir sounds convenient, but could
1036 1019 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1037 1020 # or "--tmpdir=$HOME".
1038 1021 #vlog("# Removing temp dir", tmpdir)
1039 1022 #shutil.rmtree(tmpdir)
1040 1023 os.makedirs(tmpdir)
1041 1024 else:
1042 1025 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1043 1026 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1044 1027 DAEMON_PIDS = None
1045 1028 HGRCPATH = None
1046 1029
1047 1030 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1048 1031 os.environ["HGMERGE"] = "internal:merge"
1049 1032 os.environ["HGUSER"] = "test"
1050 1033 os.environ["HGENCODING"] = "ascii"
1051 1034 os.environ["HGENCODINGMODE"] = "strict"
1052 1035 os.environ["HGPORT"] = str(options.port)
1053 1036 os.environ["HGPORT1"] = str(options.port + 1)
1054 1037 os.environ["HGPORT2"] = str(options.port + 2)
1055 1038
1056 1039 if options.with_hg:
1057 1040 INST = None
1058 1041 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1059 1042
1060 1043 # This looks redundant with how Python initializes sys.path from
1061 1044 # the location of the script being executed. Needed because the
1062 1045 # "hg" specified by --with-hg is not the only Python script
1063 1046 # executed in the test suite that needs to import 'mercurial'
1064 1047 # ... which means it's not really redundant at all.
1065 1048 PYTHONDIR = BINDIR
1066 1049 else:
1067 1050 INST = os.path.join(HGTMP, "install")
1068 1051 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1069 1052 PYTHONDIR = os.path.join(INST, "lib", "python")
1070 1053
1071 1054 os.environ["BINDIR"] = BINDIR
1072 1055 os.environ["PYTHON"] = PYTHON
1073 1056
1074 1057 if not options.child:
1075 1058 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1076 1059 os.environ["PATH"] = os.pathsep.join(path)
1077 1060
1078 1061 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1079 1062 # can run .../tests/run-tests.py test-foo where test-foo
1080 1063 # adds an extension to HGRC
1081 1064 pypath = [PYTHONDIR, TESTDIR]
1082 1065 # We have to augment PYTHONPATH, rather than simply replacing
1083 1066 # it, in case external libraries are only available via current
1084 1067 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1085 1068 # are in /opt/subversion.)
1086 1069 oldpypath = os.environ.get(IMPL_PATH)
1087 1070 if oldpypath:
1088 1071 pypath.append(oldpypath)
1089 1072 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1090 1073
1091 1074 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1092 1075
1093 1076 vlog("# Using TESTDIR", TESTDIR)
1094 1077 vlog("# Using HGTMP", HGTMP)
1095 1078 vlog("# Using PATH", os.environ["PATH"])
1096 1079 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1097 1080
1098 1081 try:
1099 1082 if len(tests) > 1 and options.jobs > 1:
1100 1083 runchildren(options, tests)
1101 1084 else:
1102 1085 runtests(options, tests)
1103 1086 finally:
1104 1087 time.sleep(1)
1105 1088 cleanup(options)
1106 1089
1107 1090 main()
General Comments 0
You need to be logged in to leave comments. Login now