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