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