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