##// END OF EJS Templates
run-tests: allow whitelisting tests that should always run...
Augie Fackler -
r14493:5cc7905b default
parent child Browse files
Show More
@@ -1,1185 +1,1204 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install:
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 # 9) parallel, custom tmp dir:
37 37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 38 #
39 39 # (You could use any subset of the tests: test-s* happens to match
40 40 # enough that it's worth doing parallel runs, few enough that it
41 41 # completes fairly quickly, includes both shell and Python scripts, and
42 42 # includes some scripts that run daemon processes.)
43 43
44 44 from distutils import version
45 45 import difflib
46 46 import errno
47 47 import optparse
48 48 import os
49 49 import shutil
50 50 import subprocess
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55 import re
56 56 import threading
57 57
58 58 processlock = threading.Lock()
59 59
60 60 closefds = os.name == 'posix'
61 61 def Popen4(cmd, wd, timeout):
62 62 processlock.acquire()
63 63 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd,
64 64 close_fds=closefds,
65 65 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
66 66 stderr=subprocess.STDOUT)
67 67 processlock.release()
68 68
69 69 p.fromchild = p.stdout
70 70 p.tochild = p.stdin
71 71 p.childerr = p.stderr
72 72
73 73 p.timeout = False
74 74 if timeout:
75 75 def t():
76 76 start = time.time()
77 77 while time.time() - start < timeout and p.returncode is None:
78 78 time.sleep(1)
79 79 p.timeout = True
80 80 if p.returncode is None:
81 81 try:
82 82 p.terminate()
83 83 except OSError:
84 84 pass
85 85 threading.Thread(target=t).start()
86 86
87 87 return p
88 88
89 89 # reserved exit code to skip test (used by hghave)
90 90 SKIPPED_STATUS = 80
91 91 SKIPPED_PREFIX = 'skipped: '
92 92 FAILED_PREFIX = 'hghave check failed: '
93 93 PYTHON = sys.executable
94 94 IMPL_PATH = 'PYTHONPATH'
95 95 if 'java' in sys.platform:
96 96 IMPL_PATH = 'JYTHONPATH'
97 97
98 98 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
99 99
100 100 defaults = {
101 101 'jobs': ('HGTEST_JOBS', 1),
102 102 'timeout': ('HGTEST_TIMEOUT', 180),
103 103 'port': ('HGTEST_PORT', 20059),
104 104 'shell': ('HGTEST_SHELL', '/bin/sh'),
105 105 }
106 106
107 def parselistfiles(files, listtype, warn=True):
108 entries = dict()
109 for filename in files:
110 try:
111 path = os.path.expanduser(os.path.expandvars(filename))
112 f = open(path, "r")
113 except IOError, err:
114 if err.errno != errno.ENOENT:
115 raise
116 if warn:
117 print "warning: no such %s file: %s" % (listtype, filename)
118 continue
119
120 for line in f.readlines():
121 line = line.split('#', 1)[0].strip()
122 if line:
123 entries[line] = filename
124
125 f.close()
126 return entries
127
107 128 def parseargs():
108 129 parser = optparse.OptionParser("%prog [options] [tests]")
109 130
110 131 # keep these sorted
111 132 parser.add_option("--blacklist", action="append",
112 133 help="skip tests listed in the specified blacklist file")
134 parser.add_option("--whitelist", action="append",
135 help="always run tests listed in the specified whitelist file")
113 136 parser.add_option("-C", "--annotate", action="store_true",
114 137 help="output files annotated with coverage")
115 138 parser.add_option("--child", type="int",
116 139 help="run as child process, summary to given fd")
117 140 parser.add_option("-c", "--cover", action="store_true",
118 141 help="print a test coverage report")
119 142 parser.add_option("-d", "--debug", action="store_true",
120 143 help="debug mode: write output of test scripts to console"
121 144 " rather than capturing and diff'ing it (disables timeout)")
122 145 parser.add_option("-f", "--first", action="store_true",
123 146 help="exit on the first test failure")
124 147 parser.add_option("--inotify", action="store_true",
125 148 help="enable inotify extension when running tests")
126 149 parser.add_option("-i", "--interactive", action="store_true",
127 150 help="prompt to accept changed output")
128 151 parser.add_option("-j", "--jobs", type="int",
129 152 help="number of jobs to run in parallel"
130 153 " (default: $%s or %d)" % defaults['jobs'])
131 154 parser.add_option("--keep-tmpdir", action="store_true",
132 155 help="keep temporary directory after running tests")
133 156 parser.add_option("-k", "--keywords",
134 157 help="run tests matching keywords")
135 158 parser.add_option("-l", "--local", action="store_true",
136 159 help="shortcut for --with-hg=<testdir>/../hg")
137 160 parser.add_option("-n", "--nodiff", action="store_true",
138 161 help="skip showing test changes")
139 162 parser.add_option("-p", "--port", type="int",
140 163 help="port on which servers should listen"
141 164 " (default: $%s or %d)" % defaults['port'])
142 165 parser.add_option("--pure", action="store_true",
143 166 help="use pure Python code instead of C extensions")
144 167 parser.add_option("-R", "--restart", action="store_true",
145 168 help="restart at last error")
146 169 parser.add_option("-r", "--retest", action="store_true",
147 170 help="retest failed tests")
148 171 parser.add_option("-S", "--noskips", action="store_true",
149 172 help="don't report skip tests verbosely")
150 173 parser.add_option("--shell", type="string",
151 174 help="shell to use (default: $%s or %s)" % defaults['shell'])
152 175 parser.add_option("-t", "--timeout", type="int",
153 176 help="kill errant tests after TIMEOUT seconds"
154 177 " (default: $%s or %d)" % defaults['timeout'])
155 178 parser.add_option("--tmpdir", type="string",
156 179 help="run tests in the given temporary directory"
157 180 " (implies --keep-tmpdir)")
158 181 parser.add_option("-v", "--verbose", action="store_true",
159 182 help="output verbose messages")
160 183 parser.add_option("--view", type="string",
161 184 help="external diff viewer")
162 185 parser.add_option("--with-hg", type="string",
163 186 metavar="HG",
164 187 help="test using specified hg script rather than a "
165 188 "temporary installation")
166 189 parser.add_option("-3", "--py3k-warnings", action="store_true",
167 190 help="enable Py3k warnings on Python 2.6+")
168 191 parser.add_option('--extra-config-opt', action="append",
169 192 help='set the given config opt in the test hgrc')
170 193
171 194 for option, (envvar, default) in defaults.items():
172 195 defaults[option] = type(default)(os.environ.get(envvar, default))
173 196 parser.set_defaults(**defaults)
174 197 (options, args) = parser.parse_args()
175 198
176 199 # jython is always pure
177 200 if 'java' in sys.platform or '__pypy__' in sys.modules:
178 201 options.pure = True
179 202
180 203 if options.with_hg:
181 204 if not (os.path.isfile(options.with_hg) and
182 205 os.access(options.with_hg, os.X_OK)):
183 206 parser.error('--with-hg must specify an executable hg script')
184 207 if not os.path.basename(options.with_hg) == 'hg':
185 208 sys.stderr.write('warning: --with-hg should specify an hg script\n')
186 209 if options.local:
187 210 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
188 211 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
189 212 if not os.access(hgbin, os.X_OK):
190 213 parser.error('--local specified, but %r not found or not executable'
191 214 % hgbin)
192 215 options.with_hg = hgbin
193 216
194 217 options.anycoverage = options.cover or options.annotate
195 218 if options.anycoverage:
196 219 try:
197 220 import coverage
198 221 covver = version.StrictVersion(coverage.__version__).version
199 222 if covver < (3, 3):
200 223 parser.error('coverage options require coverage 3.3 or later')
201 224 except ImportError:
202 225 parser.error('coverage options now require the coverage package')
203 226
204 227 if options.anycoverage and options.local:
205 228 # this needs some path mangling somewhere, I guess
206 229 parser.error("sorry, coverage options do not work when --local "
207 230 "is specified")
208 231
209 232 global vlog
210 233 if options.verbose:
211 234 if options.jobs > 1 or options.child is not None:
212 235 pid = "[%d]" % os.getpid()
213 236 else:
214 237 pid = None
215 238 def vlog(*msg):
216 239 iolock.acquire()
217 240 if pid:
218 241 print pid,
219 242 for m in msg:
220 243 print m,
221 244 print
222 245 sys.stdout.flush()
223 246 iolock.release()
224 247 else:
225 248 vlog = lambda *msg: None
226 249
227 250 if options.tmpdir:
228 251 options.tmpdir = os.path.expanduser(options.tmpdir)
229 252
230 253 if options.jobs < 1:
231 254 parser.error('--jobs must be positive')
232 255 if options.interactive and options.jobs > 1:
233 256 print '(--interactive overrides --jobs)'
234 257 options.jobs = 1
235 258 if options.interactive and options.debug:
236 259 parser.error("-i/--interactive and -d/--debug are incompatible")
237 260 if options.debug:
238 261 if options.timeout != defaults['timeout']:
239 262 sys.stderr.write(
240 263 'warning: --timeout option ignored with --debug\n')
241 264 options.timeout = 0
242 265 if options.timeout and not hasattr(subprocess.Popen, 'terminate'):
243 266 sys.stderr.write('warning: timeout is not supported on this '
244 267 'platform and will be ignored\n')
245 268 options.timeout = 0
246 269 if options.py3k_warnings:
247 270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
248 271 parser.error('--py3k-warnings can only be used on Python 2.6+')
249 272 if options.blacklist:
250 blacklist = dict()
251 for filename in options.blacklist:
252 try:
253 path = os.path.expanduser(os.path.expandvars(filename))
254 f = open(path, "r")
255 except IOError, err:
256 if err.errno != errno.ENOENT:
257 raise
258 print "warning: no such blacklist file: %s" % filename
259 continue
260
261 for line in f.readlines():
262 line = line.split('#', 1)[0].strip()
263 if line:
264 blacklist[line] = filename
265
266 f.close()
267
268 options.blacklist = blacklist
273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
274 if options.whitelist:
275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
276 warn=options.child is None)
277 else:
278 options.whitelisted = {}
269 279
270 280 return (options, args)
271 281
272 282 def rename(src, dst):
273 283 """Like os.rename(), trade atomicity and opened files friendliness
274 284 for existing destination support.
275 285 """
276 286 shutil.copy(src, dst)
277 287 os.remove(src)
278 288
279 289 def splitnewlines(text):
280 290 '''like str.splitlines, but only split on newlines.
281 291 keep line endings.'''
282 292 i = 0
283 293 lines = []
284 294 while True:
285 295 n = text.find('\n', i)
286 296 if n == -1:
287 297 last = text[i:]
288 298 if last:
289 299 lines.append(last)
290 300 return lines
291 301 lines.append(text[i:n + 1])
292 302 i = n + 1
293 303
294 304 def parsehghaveoutput(lines):
295 305 '''Parse hghave log lines.
296 306 Return tuple of lists (missing, failed):
297 307 * the missing/unknown features
298 308 * the features for which existence check failed'''
299 309 missing = []
300 310 failed = []
301 311 for line in lines:
302 312 if line.startswith(SKIPPED_PREFIX):
303 313 line = line.splitlines()[0]
304 314 missing.append(line[len(SKIPPED_PREFIX):])
305 315 elif line.startswith(FAILED_PREFIX):
306 316 line = line.splitlines()[0]
307 317 failed.append(line[len(FAILED_PREFIX):])
308 318
309 319 return missing, failed
310 320
311 321 def showdiff(expected, output, ref, err):
312 322 print
313 323 for line in difflib.unified_diff(expected, output, ref, err):
314 324 sys.stdout.write(line)
315 325
316 326 def findprogram(program):
317 327 """Search PATH for a executable program"""
318 328 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
319 329 name = os.path.join(p, program)
320 330 if os.name == 'nt' or os.access(name, os.X_OK):
321 331 return name
322 332 return None
323 333
324 334 def checktools():
325 335 # Before we go any further, check for pre-requisite tools
326 336 # stuff from coreutils (cat, rm, etc) are not tested
327 337 for p in requiredtools:
328 338 if os.name == 'nt':
329 339 p += '.exe'
330 340 found = findprogram(p)
331 341 if found:
332 342 vlog("# Found prerequisite", p, "at", found)
333 343 else:
334 344 print "WARNING: Did not find prerequisite tool: "+p
335 345
336 346 def killdaemons():
337 347 # Kill off any leftover daemon processes
338 348 try:
339 349 fp = open(DAEMON_PIDS)
340 350 for line in fp:
341 351 try:
342 352 pid = int(line)
343 353 except ValueError:
344 354 continue
345 355 try:
346 356 os.kill(pid, 0)
347 357 vlog('# Killing daemon process %d' % pid)
348 358 os.kill(pid, signal.SIGTERM)
349 359 time.sleep(0.25)
350 360 os.kill(pid, 0)
351 361 vlog('# Daemon process %d is stuck - really killing it' % pid)
352 362 os.kill(pid, signal.SIGKILL)
353 363 except OSError, err:
354 364 if err.errno != errno.ESRCH:
355 365 raise
356 366 fp.close()
357 367 os.unlink(DAEMON_PIDS)
358 368 except IOError:
359 369 pass
360 370
361 371 def cleanup(options):
362 372 if not options.keep_tmpdir:
363 373 vlog("# Cleaning up HGTMP", HGTMP)
364 374 shutil.rmtree(HGTMP, True)
365 375
366 376 def usecorrectpython():
367 377 # some tests run python interpreter. they must use same
368 378 # interpreter we use or bad things will happen.
369 379 exedir, exename = os.path.split(sys.executable)
370 380 if exename in ('python', 'python.exe'):
371 381 path = findprogram(exename)
372 382 if os.path.dirname(path) == exedir:
373 383 return
374 384 else:
375 385 exename = 'python'
376 386 vlog('# Making python executable in test path use correct Python')
377 387 mypython = os.path.join(BINDIR, exename)
378 388 try:
379 389 os.symlink(sys.executable, mypython)
380 390 except AttributeError:
381 391 # windows fallback
382 392 shutil.copyfile(sys.executable, mypython)
383 393 shutil.copymode(sys.executable, mypython)
384 394
385 395 def installhg(options):
386 396 vlog("# Performing temporary installation of HG")
387 397 installerrs = os.path.join("tests", "install.err")
388 398 pure = options.pure and "--pure" or ""
389 399
390 400 # Run installer in hg root
391 401 script = os.path.realpath(sys.argv[0])
392 402 hgroot = os.path.dirname(os.path.dirname(script))
393 403 os.chdir(hgroot)
394 404 nohome = '--home=""'
395 405 if os.name == 'nt':
396 406 # The --home="" trick works only on OS where os.sep == '/'
397 407 # because of a distutils convert_path() fast-path. Avoid it at
398 408 # least on Windows for now, deal with .pydistutils.cfg bugs
399 409 # when they happen.
400 410 nohome = ''
401 411 cmd = ('%s setup.py %s clean --all'
402 412 ' build --build-base="%s"'
403 413 ' install --force --prefix="%s" --install-lib="%s"'
404 414 ' --install-scripts="%s" %s >%s 2>&1'
405 415 % (sys.executable, pure, os.path.join(HGTMP, "build"),
406 416 INST, PYTHONDIR, BINDIR, nohome, installerrs))
407 417 vlog("# Running", cmd)
408 418 if os.system(cmd) == 0:
409 419 if not options.verbose:
410 420 os.remove(installerrs)
411 421 else:
412 422 f = open(installerrs)
413 423 for line in f:
414 424 print line,
415 425 f.close()
416 426 sys.exit(1)
417 427 os.chdir(TESTDIR)
418 428
419 429 usecorrectpython()
420 430
421 431 vlog("# Installing dummy diffstat")
422 432 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
423 433 f.write('#!' + sys.executable + '\n'
424 434 'import sys\n'
425 435 'files = 0\n'
426 436 'for line in sys.stdin:\n'
427 437 ' if line.startswith("diff "):\n'
428 438 ' files += 1\n'
429 439 'sys.stdout.write("files patched: %d\\n" % files)\n')
430 440 f.close()
431 441 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
432 442
433 443 if options.py3k_warnings and not options.anycoverage:
434 444 vlog("# Updating hg command to enable Py3k Warnings switch")
435 445 f = open(os.path.join(BINDIR, 'hg'), 'r')
436 446 lines = [line.rstrip() for line in f]
437 447 lines[0] += ' -3'
438 448 f.close()
439 449 f = open(os.path.join(BINDIR, 'hg'), 'w')
440 450 for line in lines:
441 451 f.write(line + '\n')
442 452 f.close()
443 453
444 454 hgbat = os.path.join(BINDIR, 'hg.bat')
445 455 if os.path.isfile(hgbat):
446 456 # hg.bat expects to be put in bin/scripts while run-tests.py
447 457 # installation layout put it in bin/ directly. Fix it
448 458 f = open(hgbat, 'rb')
449 459 data = f.read()
450 460 f.close()
451 461 if '"%~dp0..\python" "%~dp0hg" %*' in data:
452 462 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
453 463 '"%~dp0python" "%~dp0hg" %*')
454 464 f = open(hgbat, 'wb')
455 465 f.write(data)
456 466 f.close()
457 467 else:
458 468 print 'WARNING: cannot fix hg.bat reference to python.exe'
459 469
460 470 if options.anycoverage:
461 471 custom = os.path.join(TESTDIR, 'sitecustomize.py')
462 472 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
463 473 vlog('# Installing coverage trigger to %s' % target)
464 474 shutil.copyfile(custom, target)
465 475 rc = os.path.join(TESTDIR, '.coveragerc')
466 476 vlog('# Installing coverage rc to %s' % rc)
467 477 os.environ['COVERAGE_PROCESS_START'] = rc
468 478 fn = os.path.join(INST, '..', '.coverage')
469 479 os.environ['COVERAGE_FILE'] = fn
470 480
471 481 def outputcoverage(options):
472 482
473 483 vlog('# Producing coverage report')
474 484 os.chdir(PYTHONDIR)
475 485
476 486 def covrun(*args):
477 487 cmd = 'coverage %s' % ' '.join(args)
478 488 vlog('# Running: %s' % cmd)
479 489 os.system(cmd)
480 490
481 491 if options.child:
482 492 return
483 493
484 494 covrun('-c')
485 495 omit = ','.join([BINDIR, TESTDIR])
486 496 covrun('-i', '-r', '"--omit=%s"' % omit) # report
487 497 if options.annotate:
488 498 adir = os.path.join(TESTDIR, 'annotated')
489 499 if not os.path.isdir(adir):
490 500 os.mkdir(adir)
491 501 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
492 502
493 503 def pytest(test, wd, options, replacements):
494 504 py3kswitch = options.py3k_warnings and ' -3' or ''
495 505 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
496 506 vlog("# Running", cmd)
497 507 return run(cmd, wd, options, replacements)
498 508
499 509 def shtest(test, wd, options, replacements):
500 510 cmd = '"%s"' % test
501 511 vlog("# Running", cmd)
502 512 return run(cmd, wd, options, replacements)
503 513
504 514 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
505 515 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
506 516 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
507 517 escapemap.update({'\\': '\\\\', '\r': r'\r'})
508 518 def escapef(m):
509 519 return escapemap[m.group(0)]
510 520 def stringescape(s):
511 521 return escapesub(escapef, s)
512 522
513 523 def tsttest(test, wd, options, replacements):
514 524 t = open(test)
515 525 out = []
516 526 script = []
517 527 salt = "SALT" + str(time.time())
518 528
519 529 pos = prepos = -1
520 530 after = {}
521 531 expected = {}
522 532 for n, l in enumerate(t):
523 533 if not l.endswith('\n'):
524 534 l += '\n'
525 535 if l.startswith(' $ '): # commands
526 536 after.setdefault(pos, []).append(l)
527 537 prepos = pos
528 538 pos = n
529 539 script.append('echo %s %s $?\n' % (salt, n))
530 540 script.append(l[4:])
531 541 elif l.startswith(' > '): # continuations
532 542 after.setdefault(prepos, []).append(l)
533 543 script.append(l[4:])
534 544 elif l.startswith(' '): # results
535 545 # queue up a list of expected results
536 546 expected.setdefault(pos, []).append(l[2:])
537 547 else:
538 548 # non-command/result - queue up for merged output
539 549 after.setdefault(pos, []).append(l)
540 550
541 551 t.close()
542 552
543 553 script.append('echo %s %s $?\n' % (salt, n + 1))
544 554
545 555 fd, name = tempfile.mkstemp(suffix='hg-tst')
546 556
547 557 try:
548 558 for l in script:
549 559 os.write(fd, l)
550 560 os.close(fd)
551 561
552 562 cmd = '"%s" "%s"' % (options.shell, name)
553 563 vlog("# Running", cmd)
554 564 exitcode, output = run(cmd, wd, options, replacements)
555 565 # do not merge output if skipped, return hghave message instead
556 566 # similarly, with --debug, output is None
557 567 if exitcode == SKIPPED_STATUS or output is None:
558 568 return exitcode, output
559 569 finally:
560 570 os.remove(name)
561 571
562 572 def rematch(el, l):
563 573 try:
564 574 # ensure that the regex matches to the end of the string
565 575 return re.match(el + r'\Z', l)
566 576 except re.error:
567 577 # el is an invalid regex
568 578 return False
569 579
570 580 def globmatch(el, l):
571 581 # The only supported special characters are * and ?. Escaping is
572 582 # supported.
573 583 i, n = 0, len(el)
574 584 res = ''
575 585 while i < n:
576 586 c = el[i]
577 587 i += 1
578 588 if c == '\\' and el[i] in '*?\\':
579 589 res += el[i - 1:i + 1]
580 590 i += 1
581 591 elif c == '*':
582 592 res += '.*'
583 593 elif c == '?':
584 594 res += '.'
585 595 else:
586 596 res += re.escape(c)
587 597 return rematch(res, l)
588 598
589 599 pos = -1
590 600 postout = []
591 601 ret = 0
592 602 for n, l in enumerate(output):
593 603 lout, lcmd = l, None
594 604 if salt in l:
595 605 lout, lcmd = l.split(salt, 1)
596 606
597 607 if lout:
598 608 if lcmd:
599 609 lout += ' (no-eol)\n'
600 610
601 611 el = None
602 612 if pos in expected and expected[pos]:
603 613 el = expected[pos].pop(0)
604 614
605 615 if el == lout: # perfect match (fast)
606 616 postout.append(" " + lout)
607 617 elif (el and
608 618 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or
609 619 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout)
610 620 or el.endswith(" (esc)\n") and
611 621 el.decode('string-escape') == l)):
612 622 postout.append(" " + el) # fallback regex/glob/esc match
613 623 else:
614 624 if needescape(lout):
615 625 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
616 626 postout.append(" " + lout) # let diff deal with it
617 627
618 628 if lcmd:
619 629 # add on last return code
620 630 ret = int(lcmd.split()[1])
621 631 if ret != 0:
622 632 postout.append(" [%s]\n" % ret)
623 633 if pos in after:
624 634 postout += after.pop(pos)
625 635 pos = int(lcmd.split()[0])
626 636
627 637 if pos in after:
628 638 postout += after.pop(pos)
629 639
630 640 return exitcode, postout
631 641
632 642 wifexited = getattr(os, "WIFEXITED", lambda x: False)
633 643 def run(cmd, wd, options, replacements):
634 644 """Run command in a sub-process, capturing the output (stdout and stderr).
635 645 Return a tuple (exitcode, output). output is None in debug mode."""
636 646 # TODO: Use subprocess.Popen if we're running on Python 2.4
637 647 if options.debug:
638 648 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
639 649 ret = proc.wait()
640 650 return (ret, None)
641 651
642 652 proc = Popen4(cmd, wd, options.timeout)
643 653 def cleanup():
644 654 try:
645 655 proc.terminate()
646 656 except OSError:
647 657 pass
648 658 ret = proc.wait()
649 659 if ret == 0:
650 660 ret = signal.SIGTERM << 8
651 661 killdaemons()
652 662 return ret
653 663
654 664 output = ''
655 665 proc.tochild.close()
656 666
657 667 try:
658 668 output = proc.fromchild.read()
659 669 except KeyboardInterrupt:
660 670 vlog('# Handling keyboard interrupt')
661 671 cleanup()
662 672 raise
663 673
664 674 ret = proc.wait()
665 675 if wifexited(ret):
666 676 ret = os.WEXITSTATUS(ret)
667 677
668 678 if proc.timeout:
669 679 ret = 'timeout'
670 680
671 681 if ret:
672 682 killdaemons()
673 683
674 684 for s, r in replacements:
675 685 output = re.sub(s, r, output)
676 686 return ret, splitnewlines(output)
677 687
678 688 def runone(options, test):
679 689 '''tristate output:
680 690 None -> skipped
681 691 True -> passed
682 692 False -> failed'''
683 693
684 694 global results, resultslock, iolock
685 695
686 696 testpath = os.path.join(TESTDIR, test)
687 697
688 698 def result(l, e):
689 699 resultslock.acquire()
690 700 results[l].append(e)
691 701 resultslock.release()
692 702
693 703 def skip(msg):
694 704 if not options.verbose:
695 705 result('s', (test, msg))
696 706 else:
697 707 iolock.acquire()
698 708 print "\nSkipping %s: %s" % (testpath, msg)
699 709 iolock.release()
700 710 return None
701 711
702 712 def fail(msg, ret):
703 713 if not options.nodiff:
704 714 iolock.acquire()
705 715 print "\nERROR: %s %s" % (testpath, msg)
706 716 iolock.release()
707 717 if (not ret and options.interactive
708 718 and os.path.exists(testpath + ".err")):
709 719 iolock.acquire()
710 720 print "Accept this change? [n] ",
711 721 answer = sys.stdin.readline().strip()
712 722 iolock.release()
713 723 if answer.lower() in "y yes".split():
714 724 if test.endswith(".t"):
715 725 rename(testpath + ".err", testpath)
716 726 else:
717 727 rename(testpath + ".err", testpath + ".out")
718 728 return
719 729 result('f', (test, msg))
720 730
721 731 def success():
722 732 result('p', test)
723 733
724 734 def ignore(msg):
725 735 result('i', (test, msg))
726 736
727 737 if (os.path.basename(test).startswith("test-") and '~' not in test and
728 738 ('.' not in test or test.endswith('.py') or
729 739 test.endswith('.bat') or test.endswith('.t'))):
730 740 if not os.path.exists(test):
731 741 skip("doesn't exist")
732 742 return None
733 743 else:
734 744 return None # not a supported test, don't record
735 745
736 if options.blacklist and test in options.blacklist:
737 skip("blacklisted")
738 return None
746 if not (options.whitelisted and test in options.whitelisted):
747 if options.blacklist and test in options.blacklist:
748 skip("blacklisted")
749 return None
739 750
740 if options.retest and not os.path.exists(test + ".err"):
741 ignore("not retesting")
742 return None
751 if options.retest and not os.path.exists(test + ".err"):
752 ignore("not retesting")
753 return None
743 754
744 if options.keywords:
745 fp = open(test)
746 t = fp.read().lower() + test.lower()
747 fp.close()
748 for k in options.keywords.lower().split():
749 if k in t:
750 break
751 else:
752 ignore("doesn't match keyword")
753 return None
755 if options.keywords:
756 fp = open(test)
757 t = fp.read().lower() + test.lower()
758 fp.close()
759 for k in options.keywords.lower().split():
760 if k in t:
761 break
762 else:
763 ignore("doesn't match keyword")
764 return None
754 765
755 766 vlog("# Test", test)
756 767
757 768 # create a fresh hgrc
758 769 hgrc = open(HGRCPATH, 'w+')
759 770 hgrc.write('[ui]\n')
760 771 hgrc.write('slash = True\n')
761 772 hgrc.write('[defaults]\n')
762 773 hgrc.write('backout = -d "0 0"\n')
763 774 hgrc.write('commit = -d "0 0"\n')
764 775 hgrc.write('tag = -d "0 0"\n')
765 776 if options.inotify:
766 777 hgrc.write('[extensions]\n')
767 778 hgrc.write('inotify=\n')
768 779 hgrc.write('[inotify]\n')
769 780 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
770 781 hgrc.write('appendpid=True\n')
771 782 if options.extra_config_opt:
772 783 for opt in options.extra_config_opt:
773 784 section, key = opt.split('.', 1)
774 785 assert '=' in key, ('extra config opt %s must '
775 786 'have an = for assignment' % opt)
776 787 hgrc.write('[%s]\n%s\n' % (section, key))
777 788 hgrc.close()
778 789
779 790 ref = os.path.join(TESTDIR, test+".out")
780 791 err = os.path.join(TESTDIR, test+".err")
781 792 if os.path.exists(err):
782 793 os.remove(err) # Remove any previous output files
783 794 try:
784 795 tf = open(testpath)
785 796 firstline = tf.readline().rstrip()
786 797 tf.close()
787 798 except:
788 799 firstline = ''
789 800 lctest = test.lower()
790 801
791 802 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
792 803 runner = pytest
793 804 elif lctest.endswith('.t'):
794 805 runner = tsttest
795 806 ref = testpath
796 807 else:
797 808 # do not try to run non-executable programs
798 809 if not os.access(testpath, os.X_OK):
799 810 return skip("not executable")
800 811 runner = shtest
801 812
802 813 # Make a tmp subdirectory to work in
803 814 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
804 815 os.path.join(HGTMP, os.path.basename(test))
805 816
806 817 os.mkdir(testtmp)
807 818 ret, out = runner(testpath, testtmp, options, [
808 819 (re.escape(testtmp), '$TESTTMP'),
809 820 (r':%s\b' % options.port, ':$HGPORT'),
810 821 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
811 822 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
812 823 ])
813 824 vlog("# Ret was:", ret)
814 825
815 826 mark = '.'
816 827
817 828 skipped = (ret == SKIPPED_STATUS)
818 829
819 830 # If we're not in --debug mode and reference output file exists,
820 831 # check test output against it.
821 832 if options.debug:
822 833 refout = None # to match "out is None"
823 834 elif os.path.exists(ref):
824 835 f = open(ref, "r")
825 836 refout = splitnewlines(f.read())
826 837 f.close()
827 838 else:
828 839 refout = []
829 840
830 841 if (ret != 0 or out != refout) and not skipped and not options.debug:
831 842 # Save errors to a file for diagnosis
832 843 f = open(err, "wb")
833 844 for line in out:
834 845 f.write(line)
835 846 f.close()
836 847
837 848 if skipped:
838 849 mark = 's'
839 850 if out is None: # debug mode: nothing to parse
840 851 missing = ['unknown']
841 852 failed = None
842 853 else:
843 854 missing, failed = parsehghaveoutput(out)
844 855 if not missing:
845 856 missing = ['irrelevant']
846 857 if failed:
847 858 fail("hghave failed checking for %s" % failed[-1], ret)
848 859 skipped = False
849 860 else:
850 861 skip(missing[-1])
851 862 elif ret == 'timeout':
852 863 mark = 't'
853 864 fail("timed out", ret)
854 865 elif out != refout:
855 866 mark = '!'
856 867 if not options.nodiff:
857 868 iolock.acquire()
858 869 if options.view:
859 870 os.system("%s %s %s" % (options.view, ref, err))
860 871 else:
861 872 showdiff(refout, out, ref, err)
862 873 iolock.release()
863 874 if ret:
864 875 fail("output changed and returned error code %d" % ret, ret)
865 876 else:
866 877 fail("output changed", ret)
867 878 ret = 1
868 879 elif ret:
869 880 mark = '!'
870 881 fail("returned error code %d" % ret, ret)
871 882 else:
872 883 success()
873 884
874 885 if not options.verbose:
875 886 iolock.acquire()
876 887 sys.stdout.write(mark)
877 888 sys.stdout.flush()
878 889 iolock.release()
879 890
880 891 killdaemons()
881 892
882 893 if not options.keep_tmpdir:
883 894 shutil.rmtree(testtmp, True)
884 895 if skipped:
885 896 return None
886 897 return ret == 0
887 898
888 899 _hgpath = None
889 900
890 901 def _gethgpath():
891 902 """Return the path to the mercurial package that is actually found by
892 903 the current Python interpreter."""
893 904 global _hgpath
894 905 if _hgpath is not None:
895 906 return _hgpath
896 907
897 908 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
898 909 pipe = os.popen(cmd % PYTHON)
899 910 try:
900 911 _hgpath = pipe.read().strip()
901 912 finally:
902 913 pipe.close()
903 914 return _hgpath
904 915
905 916 def _checkhglib(verb):
906 917 """Ensure that the 'mercurial' package imported by python is
907 918 the one we expect it to be. If not, print a warning to stderr."""
908 919 expecthg = os.path.join(PYTHONDIR, 'mercurial')
909 920 actualhg = _gethgpath()
910 921 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
911 922 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
912 923 ' (expected %s)\n'
913 924 % (verb, actualhg, expecthg))
914 925
915 926 def runchildren(options, tests):
916 927 if INST:
917 928 installhg(options)
918 929 _checkhglib("Testing")
919 930
920 931 optcopy = dict(options.__dict__)
921 932 optcopy['jobs'] = 1
922 933
934 # Because whitelist has to override keyword matches, we have to
935 # actually load the whitelist in the children as well, so we allow
936 # the list of whitelist files to pass through and be parsed in the
937 # children, but not the dict of whitelisted tests resulting from
938 # the parse, used here to override blacklisted tests.
939 whitelist = optcopy['whitelisted'] or []
940 del optcopy['whitelisted']
941
923 942 blacklist = optcopy['blacklist'] or []
924 943 del optcopy['blacklist']
925 944 blacklisted = []
926 945
927 946 if optcopy['with_hg'] is None:
928 947 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
929 948 optcopy.pop('anycoverage', None)
930 949
931 950 opts = []
932 951 for opt, value in optcopy.iteritems():
933 952 name = '--' + opt.replace('_', '-')
934 953 if value is True:
935 954 opts.append(name)
936 955 elif isinstance(value, list):
937 956 for v in value:
938 957 opts.append(name + '=' + str(v))
939 958 elif value is not None:
940 959 opts.append(name + '=' + str(value))
941 960
942 961 tests.reverse()
943 962 jobs = [[] for j in xrange(options.jobs)]
944 963 while tests:
945 964 for job in jobs:
946 965 if not tests:
947 966 break
948 967 test = tests.pop()
949 if test in blacklist:
968 if test not in whitelist and test in blacklist:
950 969 blacklisted.append(test)
951 970 else:
952 971 job.append(test)
953 972 fps = {}
954 973
955 974 for j, job in enumerate(jobs):
956 975 if not job:
957 976 continue
958 977 rfd, wfd = os.pipe()
959 978 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
960 979 childtmp = os.path.join(HGTMP, 'child%d' % j)
961 980 childopts += ['--tmpdir', childtmp]
962 981 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
963 982 vlog(' '.join(cmdline))
964 983 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
965 984 os.close(wfd)
966 985 signal.signal(signal.SIGINT, signal.SIG_IGN)
967 986 failures = 0
968 987 tested, skipped, failed = 0, 0, 0
969 988 skips = []
970 989 fails = []
971 990 while fps:
972 991 pid, status = os.wait()
973 992 fp = fps.pop(pid)
974 993 l = fp.read().splitlines()
975 994 try:
976 995 test, skip, fail = map(int, l[:3])
977 996 except ValueError:
978 997 test, skip, fail = 0, 0, 0
979 998 split = -fail or len(l)
980 999 for s in l[3:split]:
981 1000 skips.append(s.split(" ", 1))
982 1001 for s in l[split:]:
983 1002 fails.append(s.split(" ", 1))
984 1003 tested += test
985 1004 skipped += skip
986 1005 failed += fail
987 1006 vlog('pid %d exited, status %d' % (pid, status))
988 1007 failures |= status
989 1008 print
990 1009 skipped += len(blacklisted)
991 1010 if not options.noskips:
992 1011 for s in skips:
993 1012 print "Skipped %s: %s" % (s[0], s[1])
994 1013 for s in blacklisted:
995 1014 print "Skipped %s: blacklisted" % s
996 1015 for s in fails:
997 1016 print "Failed %s: %s" % (s[0], s[1])
998 1017
999 1018 _checkhglib("Tested")
1000 1019 print "# Ran %d tests, %d skipped, %d failed." % (
1001 1020 tested, skipped, failed)
1002 1021
1003 1022 if options.anycoverage:
1004 1023 outputcoverage(options)
1005 1024 sys.exit(failures != 0)
1006 1025
1007 1026 results = dict(p=[], f=[], s=[], i=[])
1008 1027 resultslock = threading.Lock()
1009 1028 iolock = threading.Lock()
1010 1029
1011 1030 def runqueue(options, tests, results):
1012 1031 for test in tests:
1013 1032 ret = runone(options, test)
1014 1033 if options.first and ret is not None and not ret:
1015 1034 break
1016 1035
1017 1036 def runtests(options, tests):
1018 1037 global DAEMON_PIDS, HGRCPATH
1019 1038 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1020 1039 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1021 1040
1022 1041 try:
1023 1042 if INST:
1024 1043 installhg(options)
1025 1044 _checkhglib("Testing")
1026 1045
1027 1046 if options.restart:
1028 1047 orig = list(tests)
1029 1048 while tests:
1030 1049 if os.path.exists(tests[0] + ".err"):
1031 1050 break
1032 1051 tests.pop(0)
1033 1052 if not tests:
1034 1053 print "running all tests"
1035 1054 tests = orig
1036 1055
1037 1056 runqueue(options, tests, results)
1038 1057
1039 1058 failed = len(results['f'])
1040 1059 tested = len(results['p']) + failed
1041 1060 skipped = len(results['s'])
1042 1061 ignored = len(results['i'])
1043 1062
1044 1063 if options.child:
1045 1064 fp = os.fdopen(options.child, 'w')
1046 1065 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1047 1066 for s in results['s']:
1048 1067 fp.write("%s %s\n" % s)
1049 1068 for s in results['f']:
1050 1069 fp.write("%s %s\n" % s)
1051 1070 fp.close()
1052 1071 else:
1053 1072 print
1054 1073 for s in results['s']:
1055 1074 print "Skipped %s: %s" % s
1056 1075 for s in results['f']:
1057 1076 print "Failed %s: %s" % s
1058 1077 _checkhglib("Tested")
1059 1078 print "# Ran %d tests, %d skipped, %d failed." % (
1060 1079 tested, skipped + ignored, failed)
1061 1080
1062 1081 if options.anycoverage:
1063 1082 outputcoverage(options)
1064 1083 except KeyboardInterrupt:
1065 1084 failed = True
1066 1085 print "\ninterrupted!"
1067 1086
1068 1087 if failed:
1069 1088 sys.exit(1)
1070 1089
1071 1090 def main():
1072 1091 (options, args) = parseargs()
1073 1092 if not options.child:
1074 1093 os.umask(022)
1075 1094
1076 1095 checktools()
1077 1096
1078 1097 if len(args) == 0:
1079 1098 args = os.listdir(".")
1080 1099 args.sort()
1081 1100
1082 1101 tests = args
1083 1102
1084 1103 # Reset some environment variables to well-known values so that
1085 1104 # the tests produce repeatable output.
1086 1105 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1087 1106 os.environ['TZ'] = 'GMT'
1088 1107 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1089 1108 os.environ['CDPATH'] = ''
1090 1109 os.environ['COLUMNS'] = '80'
1091 1110 os.environ['GREP_OPTIONS'] = ''
1092 1111 os.environ['http_proxy'] = ''
1093 1112
1094 1113 # unset env related to hooks
1095 1114 for k in os.environ.keys():
1096 1115 if k.startswith('HG_'):
1097 1116 # can't remove on solaris
1098 1117 os.environ[k] = ''
1099 1118 del os.environ[k]
1100 1119
1101 1120 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1102 1121 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1103 1122 if options.tmpdir:
1104 1123 options.keep_tmpdir = True
1105 1124 tmpdir = options.tmpdir
1106 1125 if os.path.exists(tmpdir):
1107 1126 # Meaning of tmpdir has changed since 1.3: we used to create
1108 1127 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1109 1128 # tmpdir already exists.
1110 1129 sys.exit("error: temp dir %r already exists" % tmpdir)
1111 1130
1112 1131 # Automatically removing tmpdir sounds convenient, but could
1113 1132 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1114 1133 # or "--tmpdir=$HOME".
1115 1134 #vlog("# Removing temp dir", tmpdir)
1116 1135 #shutil.rmtree(tmpdir)
1117 1136 os.makedirs(tmpdir)
1118 1137 else:
1119 1138 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1120 1139 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1121 1140 DAEMON_PIDS = None
1122 1141 HGRCPATH = None
1123 1142
1124 1143 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1125 1144 os.environ["HGMERGE"] = "internal:merge"
1126 1145 os.environ["HGUSER"] = "test"
1127 1146 os.environ["HGENCODING"] = "ascii"
1128 1147 os.environ["HGENCODINGMODE"] = "strict"
1129 1148 os.environ["HGPORT"] = str(options.port)
1130 1149 os.environ["HGPORT1"] = str(options.port + 1)
1131 1150 os.environ["HGPORT2"] = str(options.port + 2)
1132 1151
1133 1152 if options.with_hg:
1134 1153 INST = None
1135 1154 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1136 1155
1137 1156 # This looks redundant with how Python initializes sys.path from
1138 1157 # the location of the script being executed. Needed because the
1139 1158 # "hg" specified by --with-hg is not the only Python script
1140 1159 # executed in the test suite that needs to import 'mercurial'
1141 1160 # ... which means it's not really redundant at all.
1142 1161 PYTHONDIR = BINDIR
1143 1162 else:
1144 1163 INST = os.path.join(HGTMP, "install")
1145 1164 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1146 1165 PYTHONDIR = os.path.join(INST, "lib", "python")
1147 1166
1148 1167 os.environ["BINDIR"] = BINDIR
1149 1168 os.environ["PYTHON"] = PYTHON
1150 1169
1151 1170 if not options.child:
1152 1171 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1153 1172 os.environ["PATH"] = os.pathsep.join(path)
1154 1173
1155 1174 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1156 1175 # can run .../tests/run-tests.py test-foo where test-foo
1157 1176 # adds an extension to HGRC
1158 1177 pypath = [PYTHONDIR, TESTDIR]
1159 1178 # We have to augment PYTHONPATH, rather than simply replacing
1160 1179 # it, in case external libraries are only available via current
1161 1180 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1162 1181 # are in /opt/subversion.)
1163 1182 oldpypath = os.environ.get(IMPL_PATH)
1164 1183 if oldpypath:
1165 1184 pypath.append(oldpypath)
1166 1185 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1167 1186
1168 1187 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1169 1188
1170 1189 vlog("# Using TESTDIR", TESTDIR)
1171 1190 vlog("# Using HGTMP", HGTMP)
1172 1191 vlog("# Using PATH", os.environ["PATH"])
1173 1192 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1174 1193
1175 1194 try:
1176 1195 if len(tests) > 1 and options.jobs > 1:
1177 1196 runchildren(options, tests)
1178 1197 else:
1179 1198 runtests(options, tests)
1180 1199 finally:
1181 1200 time.sleep(1)
1182 1201 cleanup(options)
1183 1202
1184 1203 if __name__ == '__main__':
1185 1204 main()
General Comments 0
You need to be logged in to leave comments. Login now