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