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