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