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