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