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