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