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