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