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