##// END OF EJS Templates
run-tests.py: skipped tests shouldn't change working directory
Mads Kiilerich -
r10406:6586a688 default
parent child Browse files
Show More
@@ -1,950 +1,948 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 import difflib
45 45 import errno
46 46 import optparse
47 47 import os
48 48 import signal
49 49 import subprocess
50 50 import shutil
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55
56 56 closefds = os.name == 'posix'
57 57 def Popen4(cmd, bufsize=-1):
58 58 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
59 59 close_fds=closefds,
60 60 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 61 stderr=subprocess.STDOUT)
62 62 p.fromchild = p.stdout
63 63 p.tochild = p.stdin
64 64 p.childerr = p.stderr
65 65 return p
66 66
67 67 # reserved exit code to skip test (used by hghave)
68 68 SKIPPED_STATUS = 80
69 69 SKIPPED_PREFIX = 'skipped: '
70 70 FAILED_PREFIX = 'hghave check failed: '
71 71 PYTHON = sys.executable
72 72
73 73 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
74 74
75 75 defaults = {
76 76 'jobs': ('HGTEST_JOBS', 1),
77 77 'timeout': ('HGTEST_TIMEOUT', 180),
78 78 'port': ('HGTEST_PORT', 20059),
79 79 }
80 80
81 81 def parseargs():
82 82 parser = optparse.OptionParser("%prog [options] [tests]")
83 83 parser.add_option("-C", "--annotate", action="store_true",
84 84 help="output files annotated with coverage")
85 85 parser.add_option("--child", type="int",
86 86 help="run as child process, summary to given fd")
87 87 parser.add_option("-c", "--cover", action="store_true",
88 88 help="print a test coverage report")
89 89 parser.add_option("-f", "--first", action="store_true",
90 90 help="exit on the first test failure")
91 91 parser.add_option("-i", "--interactive", action="store_true",
92 92 help="prompt to accept changed output")
93 93 parser.add_option("-j", "--jobs", type="int",
94 94 help="number of jobs to run in parallel"
95 95 " (default: $%s or %d)" % defaults['jobs'])
96 96 parser.add_option("-k", "--keywords",
97 97 help="run tests matching keywords")
98 98 parser.add_option("--keep-tmpdir", action="store_true",
99 99 help="keep temporary directory after running tests")
100 100 parser.add_option("--tmpdir", type="string",
101 101 help="run tests in the given temporary directory"
102 102 " (implies --keep-tmpdir)")
103 103 parser.add_option("-d", "--debug", action="store_true",
104 104 help="debug mode: write output of test scripts to console"
105 105 " rather than capturing and diff'ing it (disables timeout)")
106 106 parser.add_option("-R", "--restart", action="store_true",
107 107 help="restart at last error")
108 108 parser.add_option("-p", "--port", type="int",
109 109 help="port on which servers should listen"
110 110 " (default: $%s or %d)" % defaults['port'])
111 111 parser.add_option("-r", "--retest", action="store_true",
112 112 help="retest failed tests")
113 113 parser.add_option("-s", "--cover_stdlib", action="store_true",
114 114 help="print a test coverage report inc. standard libraries")
115 115 parser.add_option("-S", "--noskips", action="store_true",
116 116 help="don't report skip tests verbosely")
117 117 parser.add_option("-t", "--timeout", type="int",
118 118 help="kill errant tests after TIMEOUT seconds"
119 119 " (default: $%s or %d)" % defaults['timeout'])
120 120 parser.add_option("-v", "--verbose", action="store_true",
121 121 help="output verbose messages")
122 122 parser.add_option("-n", "--nodiff", action="store_true",
123 123 help="skip showing test changes")
124 124 parser.add_option("--with-hg", type="string",
125 125 metavar="HG",
126 126 help="test using specified hg script rather than a "
127 127 "temporary installation")
128 128 parser.add_option("--local", action="store_true",
129 129 help="shortcut for --with-hg=<testdir>/../hg")
130 130 parser.add_option("--pure", action="store_true",
131 131 help="use pure Python code instead of C extensions")
132 132 parser.add_option("-3", "--py3k-warnings", action="store_true",
133 133 help="enable Py3k warnings on Python 2.6+")
134 134 parser.add_option("--inotify", action="store_true",
135 135 help="enable inotify extension when running tests")
136 136 parser.add_option("--blacklist", action="append",
137 137 help="skip tests listed in the specified blacklist file")
138 138
139 139 for option, default in defaults.items():
140 140 defaults[option] = int(os.environ.get(*default))
141 141 parser.set_defaults(**defaults)
142 142 (options, args) = parser.parse_args()
143 143
144 144 if options.with_hg:
145 145 if not (os.path.isfile(options.with_hg) and
146 146 os.access(options.with_hg, os.X_OK)):
147 147 parser.error('--with-hg must specify an executable hg script')
148 148 if not os.path.basename(options.with_hg) == 'hg':
149 149 sys.stderr.write('warning: --with-hg should specify an hg script')
150 150 if options.local:
151 151 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
152 152 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
153 153 if not os.access(hgbin, os.X_OK):
154 154 parser.error('--local specified, but %r not found or not executable'
155 155 % hgbin)
156 156 options.with_hg = hgbin
157 157
158 158 options.anycoverage = (options.cover or
159 159 options.cover_stdlib or
160 160 options.annotate)
161 161
162 162 if options.anycoverage and options.with_hg:
163 163 # I'm not sure if this is a fundamental limitation or just a
164 164 # bug. But I don't want to waste people's time and energy doing
165 165 # test runs that don't give the results they want.
166 166 parser.error("sorry, coverage options do not work when --with-hg "
167 167 "or --local specified")
168 168
169 169 global vlog
170 170 if options.verbose:
171 171 if options.jobs > 1 or options.child is not None:
172 172 pid = "[%d]" % os.getpid()
173 173 else:
174 174 pid = None
175 175 def vlog(*msg):
176 176 if pid:
177 177 print pid,
178 178 for m in msg:
179 179 print m,
180 180 print
181 181 sys.stdout.flush()
182 182 else:
183 183 vlog = lambda *msg: None
184 184
185 185 if options.tmpdir:
186 186 options.tmpdir = os.path.expanduser(options.tmpdir)
187 187
188 188 if options.jobs < 1:
189 189 parser.error('--jobs must be positive')
190 190 if options.interactive and options.jobs > 1:
191 191 print '(--interactive overrides --jobs)'
192 192 options.jobs = 1
193 193 if options.interactive and options.debug:
194 194 parser.error("-i/--interactive and -d/--debug are incompatible")
195 195 if options.debug:
196 196 if options.timeout != defaults['timeout']:
197 197 sys.stderr.write(
198 198 'warning: --timeout option ignored with --debug\n')
199 199 options.timeout = 0
200 200 if options.py3k_warnings:
201 201 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
202 202 parser.error('--py3k-warnings can only be used on Python 2.6+')
203 203 if options.blacklist:
204 204 blacklist = dict()
205 205 for filename in options.blacklist:
206 206 try:
207 207 path = os.path.expanduser(os.path.expandvars(filename))
208 208 f = open(path, "r")
209 209 except IOError, err:
210 210 if err.errno != errno.ENOENT:
211 211 raise
212 212 print "warning: no such blacklist file: %s" % filename
213 213 continue
214 214
215 215 for line in f.readlines():
216 216 line = line.strip()
217 217 if line and not line.startswith('#'):
218 218 blacklist[line] = filename
219 219
220 220 options.blacklist = blacklist
221 221
222 222 return (options, args)
223 223
224 224 def rename(src, dst):
225 225 """Like os.rename(), trade atomicity and opened files friendliness
226 226 for existing destination support.
227 227 """
228 228 shutil.copy(src, dst)
229 229 os.remove(src)
230 230
231 231 def splitnewlines(text):
232 232 '''like str.splitlines, but only split on newlines.
233 233 keep line endings.'''
234 234 i = 0
235 235 lines = []
236 236 while True:
237 237 n = text.find('\n', i)
238 238 if n == -1:
239 239 last = text[i:]
240 240 if last:
241 241 lines.append(last)
242 242 return lines
243 243 lines.append(text[i:n + 1])
244 244 i = n + 1
245 245
246 246 def parsehghaveoutput(lines):
247 247 '''Parse hghave log lines.
248 248 Return tuple of lists (missing, failed):
249 249 * the missing/unknown features
250 250 * the features for which existence check failed'''
251 251 missing = []
252 252 failed = []
253 253 for line in lines:
254 254 if line.startswith(SKIPPED_PREFIX):
255 255 line = line.splitlines()[0]
256 256 missing.append(line[len(SKIPPED_PREFIX):])
257 257 elif line.startswith(FAILED_PREFIX):
258 258 line = line.splitlines()[0]
259 259 failed.append(line[len(FAILED_PREFIX):])
260 260
261 261 return missing, failed
262 262
263 263 def showdiff(expected, output, ref, err):
264 264 for line in difflib.unified_diff(expected, output, ref, err):
265 265 sys.stdout.write(line)
266 266
267 267 def findprogram(program):
268 268 """Search PATH for a executable program"""
269 269 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
270 270 name = os.path.join(p, program)
271 271 if os.access(name, os.X_OK):
272 272 return name
273 273 return None
274 274
275 275 def checktools():
276 276 # Before we go any further, check for pre-requisite tools
277 277 # stuff from coreutils (cat, rm, etc) are not tested
278 278 for p in requiredtools:
279 279 if os.name == 'nt':
280 280 p += '.exe'
281 281 found = findprogram(p)
282 282 if found:
283 283 vlog("# Found prerequisite", p, "at", found)
284 284 else:
285 285 print "WARNING: Did not find prerequisite tool: "+p
286 286
287 287 def killdaemons():
288 288 # Kill off any leftover daemon processes
289 289 try:
290 290 fp = open(DAEMON_PIDS)
291 291 for line in fp:
292 292 try:
293 293 pid = int(line)
294 294 except ValueError:
295 295 continue
296 296 try:
297 297 os.kill(pid, 0)
298 298 vlog('# Killing daemon process %d' % pid)
299 299 os.kill(pid, signal.SIGTERM)
300 300 time.sleep(0.25)
301 301 os.kill(pid, 0)
302 302 vlog('# Daemon process %d is stuck - really killing it' % pid)
303 303 os.kill(pid, signal.SIGKILL)
304 304 except OSError, err:
305 305 if err.errno != errno.ESRCH:
306 306 raise
307 307 fp.close()
308 308 os.unlink(DAEMON_PIDS)
309 309 except IOError:
310 310 pass
311 311
312 312 def cleanup(options):
313 313 if not options.keep_tmpdir:
314 314 vlog("# Cleaning up HGTMP", HGTMP)
315 315 shutil.rmtree(HGTMP, True)
316 316
317 317 def usecorrectpython():
318 318 # some tests run python interpreter. they must use same
319 319 # interpreter we use or bad things will happen.
320 320 exedir, exename = os.path.split(sys.executable)
321 321 if exename == 'python':
322 322 path = findprogram('python')
323 323 if os.path.dirname(path) == exedir:
324 324 return
325 325 vlog('# Making python executable in test path use correct Python')
326 326 mypython = os.path.join(BINDIR, 'python')
327 327 try:
328 328 os.symlink(sys.executable, mypython)
329 329 except AttributeError:
330 330 # windows fallback
331 331 shutil.copyfile(sys.executable, mypython)
332 332 shutil.copymode(sys.executable, mypython)
333 333
334 334 def installhg(options):
335 335 vlog("# Performing temporary installation of HG")
336 336 installerrs = os.path.join("tests", "install.err")
337 337 pure = options.pure and "--pure" or ""
338 338
339 339 # Run installer in hg root
340 340 script = os.path.realpath(sys.argv[0])
341 341 hgroot = os.path.dirname(os.path.dirname(script))
342 342 os.chdir(hgroot)
343 343 nohome = '--home=""'
344 344 if os.name == 'nt':
345 345 # The --home="" trick works only on OS where os.sep == '/'
346 346 # because of a distutils convert_path() fast-path. Avoid it at
347 347 # least on Windows for now, deal with .pydistutils.cfg bugs
348 348 # when they happen.
349 349 nohome = ''
350 350 cmd = ('%s setup.py %s clean --all'
351 351 ' install --force --prefix="%s" --install-lib="%s"'
352 352 ' --install-scripts="%s" %s >%s 2>&1'
353 353 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
354 354 installerrs))
355 355 vlog("# Running", cmd)
356 356 if os.system(cmd) == 0:
357 357 if not options.verbose:
358 358 os.remove(installerrs)
359 359 else:
360 360 f = open(installerrs)
361 361 for line in f:
362 362 print line,
363 363 f.close()
364 364 sys.exit(1)
365 365 os.chdir(TESTDIR)
366 366
367 367 usecorrectpython()
368 368
369 369 vlog("# Installing dummy diffstat")
370 370 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
371 371 f.write('#!' + sys.executable + '\n'
372 372 'import sys\n'
373 373 'files = 0\n'
374 374 'for line in sys.stdin:\n'
375 375 ' if line.startswith("diff "):\n'
376 376 ' files += 1\n'
377 377 'sys.stdout.write("files patched: %d\\n" % files)\n')
378 378 f.close()
379 379 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
380 380
381 381 if options.py3k_warnings and not options.anycoverage:
382 382 vlog("# Updating hg command to enable Py3k Warnings switch")
383 383 f = open(os.path.join(BINDIR, 'hg'), 'r')
384 384 lines = [line.rstrip() for line in f]
385 385 lines[0] += ' -3'
386 386 f.close()
387 387 f = open(os.path.join(BINDIR, 'hg'), 'w')
388 388 for line in lines:
389 389 f.write(line + '\n')
390 390 f.close()
391 391
392 392 if options.anycoverage:
393 393 vlog("# Installing coverage wrapper")
394 394 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
395 395 if os.path.exists(COVERAGE_FILE):
396 396 os.unlink(COVERAGE_FILE)
397 397 # Create a wrapper script to invoke hg via coverage.py
398 398 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
399 399 f = open(os.path.join(BINDIR, 'hg'), 'w')
400 400 f.write('#!' + sys.executable + '\n')
401 401 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
402 402 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
403 403 (os.path.join(TESTDIR, 'coverage.py'),
404 404 os.path.join(BINDIR, '_hg.py')))
405 405 f.close()
406 406 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
407 407
408 408 def outputcoverage(options):
409 409
410 410 vlog('# Producing coverage report')
411 411 os.chdir(PYTHONDIR)
412 412
413 413 def covrun(*args):
414 414 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
415 415 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
416 416 vlog('# Running: %s' % cmd)
417 417 os.system(cmd)
418 418
419 419 omit = [BINDIR, TESTDIR, PYTHONDIR]
420 420 if not options.cover_stdlib:
421 421 # Exclude as system paths (ignoring empty strings seen on win)
422 422 omit += [x for x in sys.path if x != '']
423 423 omit = ','.join(omit)
424 424
425 425 covrun('-c') # combine from parallel processes
426 426 for fn in os.listdir(TESTDIR):
427 427 if fn.startswith('.coverage.'):
428 428 os.unlink(os.path.join(TESTDIR, fn))
429 429
430 430 covrun('-i', '-r', '"--omit=%s"' % omit) # report
431 431 if options.annotate:
432 432 adir = os.path.join(TESTDIR, 'annotated')
433 433 if not os.path.isdir(adir):
434 434 os.mkdir(adir)
435 435 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
436 436
437 437 class Timeout(Exception):
438 438 pass
439 439
440 440 def alarmed(signum, frame):
441 441 raise Timeout
442 442
443 443 def run(cmd, options):
444 444 """Run command in a sub-process, capturing the output (stdout and stderr).
445 445 Return a tuple (exitcode, output). output is None in debug mode."""
446 446 # TODO: Use subprocess.Popen if we're running on Python 2.4
447 447 if options.debug:
448 448 proc = subprocess.Popen(cmd, shell=True)
449 449 ret = proc.wait()
450 450 return (ret, None)
451 451
452 452 if os.name == 'nt' or sys.platform.startswith('java'):
453 453 tochild, fromchild = os.popen4(cmd)
454 454 tochild.close()
455 455 output = fromchild.read()
456 456 ret = fromchild.close()
457 457 if ret == None:
458 458 ret = 0
459 459 else:
460 460 proc = Popen4(cmd)
461 461 def cleanup():
462 462 os.kill(proc.pid, signal.SIGTERM)
463 463 ret = proc.wait()
464 464 if ret == 0:
465 465 ret = signal.SIGTERM << 8
466 466 killdaemons()
467 467 return ret
468 468
469 469 try:
470 470 output = ''
471 471 proc.tochild.close()
472 472 output = proc.fromchild.read()
473 473 ret = proc.wait()
474 474 if os.WIFEXITED(ret):
475 475 ret = os.WEXITSTATUS(ret)
476 476 except Timeout:
477 477 vlog('# Process %d timed out - killing it' % proc.pid)
478 478 ret = cleanup()
479 479 output += ("\n### Abort: timeout after %d seconds.\n"
480 480 % options.timeout)
481 481 except KeyboardInterrupt:
482 482 vlog('# Handling keyboard interrupt')
483 483 cleanup()
484 484 raise
485 485
486 486 return ret, splitnewlines(output)
487 487
488 488 def runone(options, test, skips, fails):
489 489 '''tristate output:
490 490 None -> skipped
491 491 True -> passed
492 492 False -> failed'''
493 493
494 494 def skip(msg):
495 495 if not options.verbose:
496 496 skips.append((test, msg))
497 497 else:
498 498 print "\nSkipping %s: %s" % (testpath, msg)
499 499 return None
500 500
501 501 def fail(msg):
502 502 fails.append((test, msg))
503 503 if not options.nodiff:
504 504 print "\nERROR: %s %s" % (testpath, msg)
505 505 return None
506 506
507 507 vlog("# Test", test)
508 508
509 509 # create a fresh hgrc
510 510 hgrc = open(HGRCPATH, 'w+')
511 511 hgrc.write('[ui]\n')
512 512 hgrc.write('slash = True\n')
513 513 hgrc.write('[defaults]\n')
514 514 hgrc.write('backout = -d "0 0"\n')
515 515 hgrc.write('commit = -d "0 0"\n')
516 516 hgrc.write('tag = -d "0 0"\n')
517 517 if options.inotify:
518 518 hgrc.write('[extensions]\n')
519 519 hgrc.write('inotify=\n')
520 520 hgrc.write('[inotify]\n')
521 521 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
522 522 hgrc.write('appendpid=True\n')
523 523 hgrc.close()
524 524
525 err = os.path.join(TESTDIR, test+".err")
525 testpath = os.path.join(TESTDIR, test)
526 526 ref = os.path.join(TESTDIR, test+".out")
527 testpath = os.path.join(TESTDIR, test)
528
527 err = os.path.join(TESTDIR, test+".err")
529 528 if os.path.exists(err):
530 529 os.remove(err) # Remove any previous output files
531
532 # Make a tmp subdirectory to work in
533 tmpd = os.path.join(HGTMP, test)
534 os.mkdir(tmpd)
535 os.chdir(tmpd)
536
537 530 try:
538 531 tf = open(testpath)
539 532 firstline = tf.readline().rstrip()
540 533 tf.close()
541 534 except:
542 535 firstline = ''
543 536 lctest = test.lower()
544 537
545 538 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
546 539 py3kswitch = options.py3k_warnings and ' -3' or ''
547 540 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
548 541 elif lctest.endswith('.bat'):
549 542 # do not run batch scripts on non-windows
550 543 if os.name != 'nt':
551 544 return skip("batch script")
552 545 # To reliably get the error code from batch files on WinXP,
553 546 # the "cmd /c call" prefix is needed. Grrr
554 547 cmd = 'cmd /c call "%s"' % testpath
555 548 else:
556 549 # do not run shell scripts on windows
557 550 if os.name == 'nt':
558 551 return skip("shell script")
559 552 # do not try to run non-executable programs
560 553 if not os.path.exists(testpath):
561 554 return fail("does not exist")
562 555 elif not os.access(testpath, os.X_OK):
563 556 return skip("not executable")
564 557 cmd = '"%s"' % testpath
565 558
559 # Make a tmp subdirectory to work in
560 tmpd = os.path.join(HGTMP, test)
561 os.mkdir(tmpd)
562 os.chdir(tmpd)
563
566 564 if options.timeout > 0:
567 565 signal.alarm(options.timeout)
568 566
569 567 vlog("# Running", cmd)
570 568 ret, out = run(cmd, options)
571 569 vlog("# Ret was:", ret)
572 570
573 571 if options.timeout > 0:
574 572 signal.alarm(0)
575 573
576 574 mark = '.'
577 575
578 576 skipped = (ret == SKIPPED_STATUS)
579 577 # If we're not in --debug mode and reference output file exists,
580 578 # check test output against it.
581 579 if options.debug:
582 580 refout = None # to match out == None
583 581 elif os.path.exists(ref):
584 582 f = open(ref, "r")
585 583 refout = splitnewlines(f.read())
586 584 f.close()
587 585 else:
588 586 refout = []
589 587
590 588 if skipped:
591 589 mark = 's'
592 590 if out is None: # debug mode: nothing to parse
593 591 missing = ['unknown']
594 592 failed = None
595 593 else:
596 594 missing, failed = parsehghaveoutput(out)
597 595 if not missing:
598 596 missing = ['irrelevant']
599 597 if failed:
600 598 fail("hghave failed checking for %s" % failed[-1])
601 599 skipped = False
602 600 else:
603 601 skip(missing[-1])
604 602 elif out != refout:
605 603 mark = '!'
606 604 if ret:
607 605 fail("output changed and returned error code %d" % ret)
608 606 else:
609 607 fail("output changed")
610 608 if not options.nodiff:
611 609 showdiff(refout, out, ref, err)
612 610 ret = 1
613 611 elif ret:
614 612 mark = '!'
615 613 fail("returned error code %d" % ret)
616 614
617 615 if not options.verbose:
618 616 sys.stdout.write(mark)
619 617 sys.stdout.flush()
620 618
621 619 if ret != 0 and not skipped and not options.debug:
622 620 # Save errors to a file for diagnosis
623 621 f = open(err, "wb")
624 622 for line in out:
625 623 f.write(line)
626 624 f.close()
627 625
628 626 killdaemons()
629 627
630 628 os.chdir(TESTDIR)
631 629 if not options.keep_tmpdir:
632 630 shutil.rmtree(tmpd, True)
633 631 if skipped:
634 632 return None
635 633 return ret == 0
636 634
637 635 _hgpath = None
638 636
639 637 def _gethgpath():
640 638 """Return the path to the mercurial package that is actually found by
641 639 the current Python interpreter."""
642 640 global _hgpath
643 641 if _hgpath is not None:
644 642 return _hgpath
645 643
646 644 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
647 645 pipe = os.popen(cmd % PYTHON)
648 646 try:
649 647 _hgpath = pipe.read().strip()
650 648 finally:
651 649 pipe.close()
652 650 return _hgpath
653 651
654 652 def _checkhglib(verb):
655 653 """Ensure that the 'mercurial' package imported by python is
656 654 the one we expect it to be. If not, print a warning to stderr."""
657 655 expecthg = os.path.join(PYTHONDIR, 'mercurial')
658 656 actualhg = _gethgpath()
659 657 if actualhg != expecthg:
660 658 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
661 659 ' (expected %s)\n'
662 660 % (verb, actualhg, expecthg))
663 661
664 662 def runchildren(options, tests):
665 663 if INST:
666 664 installhg(options)
667 665 _checkhglib("Testing")
668 666
669 667 optcopy = dict(options.__dict__)
670 668 optcopy['jobs'] = 1
671 669 if optcopy['with_hg'] is None:
672 670 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
673 671 opts = []
674 672 for opt, value in optcopy.iteritems():
675 673 name = '--' + opt.replace('_', '-')
676 674 if value is True:
677 675 opts.append(name)
678 676 elif value is not None:
679 677 opts.append(name + '=' + str(value))
680 678
681 679 tests.reverse()
682 680 jobs = [[] for j in xrange(options.jobs)]
683 681 while tests:
684 682 for job in jobs:
685 683 if not tests:
686 684 break
687 685 job.append(tests.pop())
688 686 fps = {}
689 687
690 688 for j, job in enumerate(jobs):
691 689 if not job:
692 690 continue
693 691 rfd, wfd = os.pipe()
694 692 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
695 693 childtmp = os.path.join(HGTMP, 'child%d' % j)
696 694 childopts += ['--tmpdir', childtmp]
697 695 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
698 696 vlog(' '.join(cmdline))
699 697 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
700 698 os.close(wfd)
701 699 signal.signal(signal.SIGINT, signal.SIG_IGN)
702 700 failures = 0
703 701 tested, skipped, failed = 0, 0, 0
704 702 skips = []
705 703 fails = []
706 704 while fps:
707 705 pid, status = os.wait()
708 706 fp = fps.pop(pid)
709 707 l = fp.read().splitlines()
710 708 try:
711 709 test, skip, fail = map(int, l[:3])
712 710 except ValueError:
713 711 test, skip, fail = 0, 0, 0
714 712 split = -fail or len(l)
715 713 for s in l[3:split]:
716 714 skips.append(s.split(" ", 1))
717 715 for s in l[split:]:
718 716 fails.append(s.split(" ", 1))
719 717 tested += test
720 718 skipped += skip
721 719 failed += fail
722 720 vlog('pid %d exited, status %d' % (pid, status))
723 721 failures |= status
724 722 print
725 723 if not options.noskips:
726 724 for s in skips:
727 725 print "Skipped %s: %s" % (s[0], s[1])
728 726 for s in fails:
729 727 print "Failed %s: %s" % (s[0], s[1])
730 728
731 729 _checkhglib("Tested")
732 730 print "# Ran %d tests, %d skipped, %d failed." % (
733 731 tested, skipped, failed)
734 732 sys.exit(failures != 0)
735 733
736 734 def runtests(options, tests):
737 735 global DAEMON_PIDS, HGRCPATH
738 736 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
739 737 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
740 738
741 739 try:
742 740 if INST:
743 741 installhg(options)
744 742 _checkhglib("Testing")
745 743
746 744 if options.timeout > 0:
747 745 try:
748 746 signal.signal(signal.SIGALRM, alarmed)
749 747 vlog('# Running each test with %d second timeout' %
750 748 options.timeout)
751 749 except AttributeError:
752 750 print 'WARNING: cannot run tests with timeouts'
753 751 options.timeout = 0
754 752
755 753 tested = 0
756 754 failed = 0
757 755 skipped = 0
758 756
759 757 if options.restart:
760 758 orig = list(tests)
761 759 while tests:
762 760 if os.path.exists(tests[0] + ".err"):
763 761 break
764 762 tests.pop(0)
765 763 if not tests:
766 764 print "running all tests"
767 765 tests = orig
768 766
769 767 skips = []
770 768 fails = []
771 769
772 770 for test in tests:
773 771 if options.blacklist:
774 772 filename = options.blacklist.get(test)
775 773 if filename is not None:
776 774 skips.append((test, "blacklisted (%s)" % filename))
777 775 skipped += 1
778 776 continue
779 777
780 778 if options.retest and not os.path.exists(test + ".err"):
781 779 skipped += 1
782 780 continue
783 781
784 782 if options.keywords:
785 783 t = open(test).read().lower() + test.lower()
786 784 for k in options.keywords.lower().split():
787 785 if k in t:
788 786 break
789 787 else:
790 788 skipped +=1
791 789 continue
792 790
793 791 ret = runone(options, test, skips, fails)
794 792 if ret is None:
795 793 skipped += 1
796 794 elif not ret:
797 795 if options.interactive:
798 796 print "Accept this change? [n] ",
799 797 answer = sys.stdin.readline().strip()
800 798 if answer.lower() in "y yes".split():
801 799 rename(test + ".err", test + ".out")
802 800 tested += 1
803 801 fails.pop()
804 802 continue
805 803 failed += 1
806 804 if options.first:
807 805 break
808 806 tested += 1
809 807
810 808 if options.child:
811 809 fp = os.fdopen(options.child, 'w')
812 810 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
813 811 for s in skips:
814 812 fp.write("%s %s\n" % s)
815 813 for s in fails:
816 814 fp.write("%s %s\n" % s)
817 815 fp.close()
818 816 else:
819 817 print
820 818 for s in skips:
821 819 print "Skipped %s: %s" % s
822 820 for s in fails:
823 821 print "Failed %s: %s" % s
824 822 _checkhglib("Tested")
825 823 print "# Ran %d tests, %d skipped, %d failed." % (
826 824 tested, skipped, failed)
827 825
828 826 if options.anycoverage:
829 827 outputcoverage(options)
830 828 except KeyboardInterrupt:
831 829 failed = True
832 830 print "\ninterrupted!"
833 831
834 832 if failed:
835 833 sys.exit(1)
836 834
837 835 def main():
838 836 (options, args) = parseargs()
839 837 if not options.child:
840 838 os.umask(022)
841 839
842 840 checktools()
843 841
844 842 # Reset some environment variables to well-known values so that
845 843 # the tests produce repeatable output.
846 844 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
847 845 os.environ['TZ'] = 'GMT'
848 846 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
849 847 os.environ['CDPATH'] = ''
850 848 os.environ['COLUMNS'] = '80'
851 849 os.environ['http_proxy'] = ''
852 850
853 851 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
854 852 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
855 853 if options.tmpdir:
856 854 options.keep_tmpdir = True
857 855 tmpdir = options.tmpdir
858 856 if os.path.exists(tmpdir):
859 857 # Meaning of tmpdir has changed since 1.3: we used to create
860 858 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
861 859 # tmpdir already exists.
862 860 sys.exit("error: temp dir %r already exists" % tmpdir)
863 861
864 862 # Automatically removing tmpdir sounds convenient, but could
865 863 # really annoy anyone in the habit of using "--tmpdir=/tmp"
866 864 # or "--tmpdir=$HOME".
867 865 #vlog("# Removing temp dir", tmpdir)
868 866 #shutil.rmtree(tmpdir)
869 867 os.makedirs(tmpdir)
870 868 else:
871 869 tmpdir = tempfile.mkdtemp('', 'hgtests.')
872 870 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
873 871 DAEMON_PIDS = None
874 872 HGRCPATH = None
875 873
876 874 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
877 875 os.environ["HGMERGE"] = "internal:merge"
878 876 os.environ["HGUSER"] = "test"
879 877 os.environ["HGENCODING"] = "ascii"
880 878 os.environ["HGENCODINGMODE"] = "strict"
881 879 os.environ["HGPORT"] = str(options.port)
882 880 os.environ["HGPORT1"] = str(options.port + 1)
883 881 os.environ["HGPORT2"] = str(options.port + 2)
884 882
885 883 if options.with_hg:
886 884 INST = None
887 885 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
888 886
889 887 # This looks redundant with how Python initializes sys.path from
890 888 # the location of the script being executed. Needed because the
891 889 # "hg" specified by --with-hg is not the only Python script
892 890 # executed in the test suite that needs to import 'mercurial'
893 891 # ... which means it's not really redundant at all.
894 892 PYTHONDIR = BINDIR
895 893 else:
896 894 INST = os.path.join(HGTMP, "install")
897 895 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
898 896 PYTHONDIR = os.path.join(INST, "lib", "python")
899 897
900 898 os.environ["BINDIR"] = BINDIR
901 899 os.environ["PYTHON"] = PYTHON
902 900
903 901 if not options.child:
904 902 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
905 903 os.environ["PATH"] = os.pathsep.join(path)
906 904
907 905 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
908 906 # can run .../tests/run-tests.py test-foo where test-foo
909 907 # adds an extension to HGRC
910 908 pypath = [PYTHONDIR, TESTDIR]
911 909 # We have to augment PYTHONPATH, rather than simply replacing
912 910 # it, in case external libraries are only available via current
913 911 # PYTHONPATH. (In particular, the Subversion bindings on OS X
914 912 # are in /opt/subversion.)
915 913 oldpypath = os.environ.get('PYTHONPATH')
916 914 if oldpypath:
917 915 pypath.append(oldpypath)
918 916 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
919 917
920 918 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
921 919
922 920 if len(args) == 0:
923 921 args = os.listdir(".")
924 922 args.sort()
925 923
926 924 tests = []
927 925 for test in args:
928 926 if (test.startswith("test-") and '~' not in test and
929 927 ('.' not in test or test.endswith('.py') or
930 928 test.endswith('.bat'))):
931 929 tests.append(test)
932 930 if not tests:
933 931 print "# Ran 0 tests, 0 skipped, 0 failed."
934 932 return
935 933
936 934 vlog("# Using TESTDIR", TESTDIR)
937 935 vlog("# Using HGTMP", HGTMP)
938 936 vlog("# Using PATH", os.environ["PATH"])
939 937 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
940 938
941 939 try:
942 940 if len(tests) > 1 and options.jobs > 1:
943 941 runchildren(options, tests)
944 942 else:
945 943 runtests(options, tests)
946 944 finally:
947 945 time.sleep(1)
948 946 cleanup(options)
949 947
950 948 main()
General Comments 0
You need to be logged in to leave comments. Login now