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