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