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