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