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