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