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