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