##// END OF EJS Templates
run-tests: work around a distutils bug triggered by 0a8a43b4ca75
Patrick Mezard -
r9905:95517eb3 default
parent child Browse files
Show More
@@ -1,885 +1,893 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 nohome = '--home=""'
297 if os.name == 'nt':
298 # The --home="" trick works only on OS where os.sep == '/'
299 # because of a distutils convert_path() fast-path. Avoid it at
300 # least on Windows for now, deal with .pydistutils.cfg bugs
301 # when they happen.
302 nohome = ''
296 303 cmd = ('%s setup.py %s clean --all'
297 304 ' install --force --prefix="%s" --install-lib="%s"'
298 ' --install-scripts="%s" --home="" >%s 2>&1'
299 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
305 ' --install-scripts="%s" %s >%s 2>&1'
306 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
307 installerrs))
300 308 vlog("# Running", cmd)
301 309 if os.system(cmd) == 0:
302 310 if not options.verbose:
303 311 os.remove(installerrs)
304 312 else:
305 313 f = open(installerrs)
306 314 for line in f:
307 315 print line,
308 316 f.close()
309 317 sys.exit(1)
310 318 os.chdir(TESTDIR)
311 319
312 320 usecorrectpython()
313 321
314 322 vlog("# Installing dummy diffstat")
315 323 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
316 324 f.write('#!' + sys.executable + '\n'
317 325 'import sys\n'
318 326 'files = 0\n'
319 327 'for line in sys.stdin:\n'
320 328 ' if line.startswith("diff "):\n'
321 329 ' files += 1\n'
322 330 'sys.stdout.write("files patched: %d\\n" % files)\n')
323 331 f.close()
324 332 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
325 333
326 334 if options.py3k_warnings and not options.anycoverage:
327 335 vlog("# Updating hg command to enable Py3k Warnings switch")
328 336 f = open(os.path.join(BINDIR, 'hg'), 'r')
329 337 lines = [line.rstrip() for line in f]
330 338 lines[0] += ' -3'
331 339 f.close()
332 340 f = open(os.path.join(BINDIR, 'hg'), 'w')
333 341 for line in lines:
334 342 f.write(line + '\n')
335 343 f.close()
336 344
337 345 if options.anycoverage:
338 346 vlog("# Installing coverage wrapper")
339 347 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
340 348 if os.path.exists(COVERAGE_FILE):
341 349 os.unlink(COVERAGE_FILE)
342 350 # Create a wrapper script to invoke hg via coverage.py
343 351 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
344 352 f = open(os.path.join(BINDIR, 'hg'), 'w')
345 353 f.write('#!' + sys.executable + '\n')
346 354 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
347 355 '"%s", "-x", "-p", "%s"] + sys.argv[1:])\n' %
348 356 (os.path.join(TESTDIR, 'coverage.py'),
349 357 os.path.join(BINDIR, '_hg.py')))
350 358 f.close()
351 359 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
352 360
353 361 def outputcoverage(options):
354 362
355 363 vlog('# Producing coverage report')
356 364 os.chdir(PYTHONDIR)
357 365
358 366 def covrun(*args):
359 367 start = sys.executable, os.path.join(TESTDIR, 'coverage.py')
360 368 cmd = '"%s" "%s" %s' % (start[0], start[1], ' '.join(args))
361 369 vlog('# Running: %s' % cmd)
362 370 os.system(cmd)
363 371
364 372 omit = [BINDIR, TESTDIR, PYTHONDIR]
365 373 if not options.cover_stdlib:
366 374 # Exclude as system paths (ignoring empty strings seen on win)
367 375 omit += [x for x in sys.path if x != '']
368 376 omit = ','.join(omit)
369 377
370 378 covrun('-c') # combine from parallel processes
371 379 for fn in os.listdir(TESTDIR):
372 380 if fn.startswith('.coverage.'):
373 381 os.unlink(os.path.join(TESTDIR, fn))
374 382
375 383 covrun('-i', '-r', '"--omit=%s"' % omit) # report
376 384 if options.annotate:
377 385 adir = os.path.join(TESTDIR, 'annotated')
378 386 if not os.path.isdir(adir):
379 387 os.mkdir(adir)
380 388 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
381 389
382 390 class Timeout(Exception):
383 391 pass
384 392
385 393 def alarmed(signum, frame):
386 394 raise Timeout
387 395
388 396 def run(cmd, options):
389 397 """Run command in a sub-process, capturing the output (stdout and stderr).
390 398 Return a tuple (exitcode, output). output is None in debug mode."""
391 399 # TODO: Use subprocess.Popen if we're running on Python 2.4
392 400 if options.debug:
393 401 proc = subprocess.Popen(cmd, shell=True)
394 402 ret = proc.wait()
395 403 return (ret, None)
396 404
397 405 if os.name == 'nt' or sys.platform.startswith('java'):
398 406 tochild, fromchild = os.popen4(cmd)
399 407 tochild.close()
400 408 output = fromchild.read()
401 409 ret = fromchild.close()
402 410 if ret == None:
403 411 ret = 0
404 412 else:
405 413 proc = Popen4(cmd)
406 414 try:
407 415 output = ''
408 416 proc.tochild.close()
409 417 output = proc.fromchild.read()
410 418 ret = proc.wait()
411 419 if os.WIFEXITED(ret):
412 420 ret = os.WEXITSTATUS(ret)
413 421 except Timeout:
414 422 vlog('# Process %d timed out - killing it' % proc.pid)
415 423 os.kill(proc.pid, signal.SIGTERM)
416 424 ret = proc.wait()
417 425 if ret == 0:
418 426 ret = signal.SIGTERM << 8
419 427 output += ("\n### Abort: timeout after %d seconds.\n"
420 428 % options.timeout)
421 429 return ret, splitnewlines(output)
422 430
423 431 def runone(options, test, skips, fails):
424 432 '''tristate output:
425 433 None -> skipped
426 434 True -> passed
427 435 False -> failed'''
428 436
429 437 def skip(msg):
430 438 if not options.verbose:
431 439 skips.append((test, msg))
432 440 else:
433 441 print "\nSkipping %s: %s" % (test, msg)
434 442 return None
435 443
436 444 def fail(msg):
437 445 fails.append((test, msg))
438 446 if not options.nodiff:
439 447 print "\nERROR: %s %s" % (test, msg)
440 448 return None
441 449
442 450 vlog("# Test", test)
443 451
444 452 # create a fresh hgrc
445 453 hgrc = open(HGRCPATH, 'w+')
446 454 hgrc.write('[ui]\n')
447 455 hgrc.write('slash = True\n')
448 456 hgrc.write('[defaults]\n')
449 457 hgrc.write('backout = -d "0 0"\n')
450 458 hgrc.write('commit = -d "0 0"\n')
451 459 hgrc.write('tag = -d "0 0"\n')
452 460 hgrc.close()
453 461
454 462 err = os.path.join(TESTDIR, test+".err")
455 463 ref = os.path.join(TESTDIR, test+".out")
456 464 testpath = os.path.join(TESTDIR, test)
457 465
458 466 if os.path.exists(err):
459 467 os.remove(err) # Remove any previous output files
460 468
461 469 # Make a tmp subdirectory to work in
462 470 tmpd = os.path.join(HGTMP, test)
463 471 os.mkdir(tmpd)
464 472 os.chdir(tmpd)
465 473
466 474 try:
467 475 tf = open(testpath)
468 476 firstline = tf.readline().rstrip()
469 477 tf.close()
470 478 except:
471 479 firstline = ''
472 480 lctest = test.lower()
473 481
474 482 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
475 483 py3kswitch = options.py3k_warnings and ' -3' or ''
476 484 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
477 485 elif lctest.endswith('.bat'):
478 486 # do not run batch scripts on non-windows
479 487 if os.name != 'nt':
480 488 return skip("batch script")
481 489 # To reliably get the error code from batch files on WinXP,
482 490 # the "cmd /c call" prefix is needed. Grrr
483 491 cmd = 'cmd /c call "%s"' % testpath
484 492 else:
485 493 # do not run shell scripts on windows
486 494 if os.name == 'nt':
487 495 return skip("shell script")
488 496 # do not try to run non-executable programs
489 497 if not os.path.exists(testpath):
490 498 return fail("does not exist")
491 499 elif not os.access(testpath, os.X_OK):
492 500 return skip("not executable")
493 501 cmd = '"%s"' % testpath
494 502
495 503 if options.timeout > 0:
496 504 signal.alarm(options.timeout)
497 505
498 506 vlog("# Running", cmd)
499 507 ret, out = run(cmd, options)
500 508 vlog("# Ret was:", ret)
501 509
502 510 if options.timeout > 0:
503 511 signal.alarm(0)
504 512
505 513 mark = '.'
506 514
507 515 skipped = (ret == SKIPPED_STATUS)
508 516 # If we're not in --debug mode and reference output file exists,
509 517 # check test output against it.
510 518 if options.debug:
511 519 refout = None # to match out == None
512 520 elif os.path.exists(ref):
513 521 f = open(ref, "r")
514 522 refout = splitnewlines(f.read())
515 523 f.close()
516 524 else:
517 525 refout = []
518 526
519 527 if skipped:
520 528 mark = 's'
521 529 if out is None: # debug mode: nothing to parse
522 530 missing = ['unknown']
523 531 failed = None
524 532 else:
525 533 missing, failed = parsehghaveoutput(out)
526 534 if not missing:
527 535 missing = ['irrelevant']
528 536 if failed:
529 537 fail("hghave failed checking for %s" % failed[-1])
530 538 skipped = False
531 539 else:
532 540 skip(missing[-1])
533 541 elif out != refout:
534 542 mark = '!'
535 543 if ret:
536 544 fail("output changed and returned error code %d" % ret)
537 545 else:
538 546 fail("output changed")
539 547 if not options.nodiff:
540 548 showdiff(refout, out)
541 549 ret = 1
542 550 elif ret:
543 551 mark = '!'
544 552 fail("returned error code %d" % ret)
545 553
546 554 if not options.verbose:
547 555 sys.stdout.write(mark)
548 556 sys.stdout.flush()
549 557
550 558 if ret != 0 and not skipped and not options.debug:
551 559 # Save errors to a file for diagnosis
552 560 f = open(err, "wb")
553 561 for line in out:
554 562 f.write(line)
555 563 f.close()
556 564
557 565 # Kill off any leftover daemon processes
558 566 try:
559 567 fp = open(DAEMON_PIDS)
560 568 for line in fp:
561 569 try:
562 570 pid = int(line)
563 571 except ValueError:
564 572 continue
565 573 try:
566 574 os.kill(pid, 0)
567 575 vlog('# Killing daemon process %d' % pid)
568 576 os.kill(pid, signal.SIGTERM)
569 577 time.sleep(0.25)
570 578 os.kill(pid, 0)
571 579 vlog('# Daemon process %d is stuck - really killing it' % pid)
572 580 os.kill(pid, signal.SIGKILL)
573 581 except OSError, err:
574 582 if err.errno != errno.ESRCH:
575 583 raise
576 584 fp.close()
577 585 os.unlink(DAEMON_PIDS)
578 586 except IOError:
579 587 pass
580 588
581 589 os.chdir(TESTDIR)
582 590 if not options.keep_tmpdir:
583 591 shutil.rmtree(tmpd, True)
584 592 if skipped:
585 593 return None
586 594 return ret == 0
587 595
588 596 _hgpath = None
589 597
590 598 def _gethgpath():
591 599 """Return the path to the mercurial package that is actually found by
592 600 the current Python interpreter."""
593 601 global _hgpath
594 602 if _hgpath is not None:
595 603 return _hgpath
596 604
597 605 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
598 606 pipe = os.popen(cmd % PYTHON)
599 607 try:
600 608 _hgpath = pipe.read().strip()
601 609 finally:
602 610 pipe.close()
603 611 return _hgpath
604 612
605 613 def _checkhglib(verb):
606 614 """Ensure that the 'mercurial' package imported by python is
607 615 the one we expect it to be. If not, print a warning to stderr."""
608 616 expecthg = os.path.join(PYTHONDIR, 'mercurial')
609 617 actualhg = _gethgpath()
610 618 if actualhg != expecthg:
611 619 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
612 620 ' (expected %s)\n'
613 621 % (verb, actualhg, expecthg))
614 622
615 623 def runchildren(options, tests):
616 624 if INST:
617 625 installhg(options)
618 626 _checkhglib("Testing")
619 627
620 628 optcopy = dict(options.__dict__)
621 629 optcopy['jobs'] = 1
622 630 if optcopy['with_hg'] is None:
623 631 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
624 632 opts = []
625 633 for opt, value in optcopy.iteritems():
626 634 name = '--' + opt.replace('_', '-')
627 635 if value is True:
628 636 opts.append(name)
629 637 elif value is not None:
630 638 opts.append(name + '=' + str(value))
631 639
632 640 tests.reverse()
633 641 jobs = [[] for j in xrange(options.jobs)]
634 642 while tests:
635 643 for job in jobs:
636 644 if not tests: break
637 645 job.append(tests.pop())
638 646 fps = {}
639 647 for j, job in enumerate(jobs):
640 648 if not job:
641 649 continue
642 650 rfd, wfd = os.pipe()
643 651 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
644 652 childtmp = os.path.join(HGTMP, 'child%d' % j)
645 653 childopts += ['--tmpdir', childtmp]
646 654 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
647 655 vlog(' '.join(cmdline))
648 656 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
649 657 os.close(wfd)
650 658 failures = 0
651 659 tested, skipped, failed = 0, 0, 0
652 660 skips = []
653 661 fails = []
654 662 while fps:
655 663 pid, status = os.wait()
656 664 fp = fps.pop(pid)
657 665 l = fp.read().splitlines()
658 666 test, skip, fail = map(int, l[:3])
659 667 split = -fail or len(l)
660 668 for s in l[3:split]:
661 669 skips.append(s.split(" ", 1))
662 670 for s in l[split:]:
663 671 fails.append(s.split(" ", 1))
664 672 tested += test
665 673 skipped += skip
666 674 failed += fail
667 675 vlog('pid %d exited, status %d' % (pid, status))
668 676 failures |= status
669 677 print
670 678 if not options.noskips:
671 679 for s in skips:
672 680 print "Skipped %s: %s" % (s[0], s[1])
673 681 for s in fails:
674 682 print "Failed %s: %s" % (s[0], s[1])
675 683
676 684 _checkhglib("Tested")
677 685 print "# Ran %d tests, %d skipped, %d failed." % (
678 686 tested, skipped, failed)
679 687 sys.exit(failures != 0)
680 688
681 689 def runtests(options, tests):
682 690 global DAEMON_PIDS, HGRCPATH
683 691 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
684 692 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
685 693
686 694 try:
687 695 if INST:
688 696 installhg(options)
689 697 _checkhglib("Testing")
690 698
691 699 if options.timeout > 0:
692 700 try:
693 701 signal.signal(signal.SIGALRM, alarmed)
694 702 vlog('# Running each test with %d second timeout' %
695 703 options.timeout)
696 704 except AttributeError:
697 705 print 'WARNING: cannot run tests with timeouts'
698 706 options.timeout = 0
699 707
700 708 tested = 0
701 709 failed = 0
702 710 skipped = 0
703 711
704 712 if options.restart:
705 713 orig = list(tests)
706 714 while tests:
707 715 if os.path.exists(tests[0] + ".err"):
708 716 break
709 717 tests.pop(0)
710 718 if not tests:
711 719 print "running all tests"
712 720 tests = orig
713 721
714 722 skips = []
715 723 fails = []
716 724
717 725 for test in tests:
718 726 if options.retest and not os.path.exists(test + ".err"):
719 727 skipped += 1
720 728 continue
721 729
722 730 if options.keywords:
723 731 t = open(test).read().lower() + test.lower()
724 732 for k in options.keywords.lower().split():
725 733 if k in t:
726 734 break
727 735 else:
728 736 skipped +=1
729 737 continue
730 738
731 739 ret = runone(options, test, skips, fails)
732 740 if ret is None:
733 741 skipped += 1
734 742 elif not ret:
735 743 if options.interactive:
736 744 print "Accept this change? [n] ",
737 745 answer = sys.stdin.readline().strip()
738 746 if answer.lower() in "y yes".split():
739 747 rename(test + ".err", test + ".out")
740 748 tested += 1
741 749 fails.pop()
742 750 continue
743 751 failed += 1
744 752 if options.first:
745 753 break
746 754 tested += 1
747 755
748 756 if options.child:
749 757 fp = os.fdopen(options.child, 'w')
750 758 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
751 759 for s in skips:
752 760 fp.write("%s %s\n" % s)
753 761 for s in fails:
754 762 fp.write("%s %s\n" % s)
755 763 fp.close()
756 764 else:
757 765 print
758 766 for s in skips:
759 767 print "Skipped %s: %s" % s
760 768 for s in fails:
761 769 print "Failed %s: %s" % s
762 770 _checkhglib("Tested")
763 771 print "# Ran %d tests, %d skipped, %d failed." % (
764 772 tested, skipped, failed)
765 773
766 774 if options.anycoverage:
767 775 outputcoverage(options)
768 776 except KeyboardInterrupt:
769 777 failed = True
770 778 print "\ninterrupted!"
771 779
772 780 if failed:
773 781 sys.exit(1)
774 782
775 783 def main():
776 784 (options, args) = parseargs()
777 785 if not options.child:
778 786 os.umask(022)
779 787
780 788 checktools()
781 789
782 790 # Reset some environment variables to well-known values so that
783 791 # the tests produce repeatable output.
784 792 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
785 793 os.environ['TZ'] = 'GMT'
786 794 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
787 795 os.environ['CDPATH'] = ''
788 796
789 797 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
790 798 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
791 799 if options.tmpdir:
792 800 options.keep_tmpdir = True
793 801 tmpdir = options.tmpdir
794 802 if os.path.exists(tmpdir):
795 803 # Meaning of tmpdir has changed since 1.3: we used to create
796 804 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
797 805 # tmpdir already exists.
798 806 sys.exit("error: temp dir %r already exists" % tmpdir)
799 807
800 808 # Automatically removing tmpdir sounds convenient, but could
801 809 # really annoy anyone in the habit of using "--tmpdir=/tmp"
802 810 # or "--tmpdir=$HOME".
803 811 #vlog("# Removing temp dir", tmpdir)
804 812 #shutil.rmtree(tmpdir)
805 813 os.makedirs(tmpdir)
806 814 else:
807 815 tmpdir = tempfile.mkdtemp('', 'hgtests.')
808 816 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
809 817 DAEMON_PIDS = None
810 818 HGRCPATH = None
811 819
812 820 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
813 821 os.environ["HGMERGE"] = "internal:merge"
814 822 os.environ["HGUSER"] = "test"
815 823 os.environ["HGENCODING"] = "ascii"
816 824 os.environ["HGENCODINGMODE"] = "strict"
817 825 os.environ["HGPORT"] = str(options.port)
818 826 os.environ["HGPORT1"] = str(options.port + 1)
819 827 os.environ["HGPORT2"] = str(options.port + 2)
820 828
821 829 if options.with_hg:
822 830 INST = None
823 831 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
824 832
825 833 # This looks redundant with how Python initializes sys.path from
826 834 # the location of the script being executed. Needed because the
827 835 # "hg" specified by --with-hg is not the only Python script
828 836 # executed in the test suite that needs to import 'mercurial'
829 837 # ... which means it's not really redundant at all.
830 838 PYTHONDIR = BINDIR
831 839 else:
832 840 INST = os.path.join(HGTMP, "install")
833 841 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
834 842 PYTHONDIR = os.path.join(INST, "lib", "python")
835 843
836 844 os.environ["BINDIR"] = BINDIR
837 845 os.environ["PYTHON"] = PYTHON
838 846
839 847 if not options.child:
840 848 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
841 849 os.environ["PATH"] = os.pathsep.join(path)
842 850
843 851 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
844 852 # can run .../tests/run-tests.py test-foo where test-foo
845 853 # adds an extension to HGRC
846 854 pypath = [PYTHONDIR, TESTDIR]
847 855 # We have to augment PYTHONPATH, rather than simply replacing
848 856 # it, in case external libraries are only available via current
849 857 # PYTHONPATH. (In particular, the Subversion bindings on OS X
850 858 # are in /opt/subversion.)
851 859 oldpypath = os.environ.get('PYTHONPATH')
852 860 if oldpypath:
853 861 pypath.append(oldpypath)
854 862 os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
855 863
856 864 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
857 865
858 866 if len(args) == 0:
859 867 args = os.listdir(".")
860 868 args.sort()
861 869
862 870 tests = []
863 871 for test in args:
864 872 if (test.startswith("test-") and '~' not in test and
865 873 ('.' not in test or test.endswith('.py') or
866 874 test.endswith('.bat'))):
867 875 tests.append(test)
868 876 if not tests:
869 877 print "# Ran 0 tests, 0 skipped, 0 failed."
870 878 return
871 879
872 880 vlog("# Using TESTDIR", TESTDIR)
873 881 vlog("# Using HGTMP", HGTMP)
874 882 vlog("# Using PATH", os.environ["PATH"])
875 883 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
876 884
877 885 try:
878 886 if len(tests) > 1 and options.jobs > 1:
879 887 runchildren(options, tests)
880 888 else:
881 889 runtests(options, tests)
882 890 finally:
883 891 cleanup(options)
884 892
885 893 main()
General Comments 0
You need to be logged in to leave comments. Login now