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