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