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