comparison tests/run-tests.py @ 8674:0941ee76489e

run-tests: redefine --with-hg so it takes the 'hg' script to run. - in parseargs(), check that --with-hg value is valid - add handy --local option for "--with-hg=../hg" - ensure that we always set PATH and PYTHONPATH (not just when doing a temporary install) - override any existing PYTHONPATH, so test success does not depend on whatever happens to be in the caller's environment - give tests a little more control by exporting $PYTHON to the environment; needed by test-convert and test-mergetool when they run hg with a stripped-down $PATH Also, add a big comment explaining all the corner cases to test for the next person who tries to modify this script.
author Greg Ward <greg-hg@gerg.ca>
date Sun, 31 May 2009 15:20:31 -0400
parents a8066f2fd1aa
children 78ab2a12b4d9
comparison
equal deleted inserted replaced
8673:a8066f2fd1aa 8674:0941ee76489e
4 # 4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com> 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 # 6 #
7 # This software may be used and distributed according to the terms of the 7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference. 8 # GNU General Public License version 2, incorporated herein by reference.
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.)
9 41
10 import difflib 42 import difflib
11 import errno 43 import errno
12 import optparse 44 import optparse
13 import os 45 import os
32 # reserved exit code to skip test (used by hghave) 64 # reserved exit code to skip test (used by hghave)
33 SKIPPED_STATUS = 80 65 SKIPPED_STATUS = 80
34 SKIPPED_PREFIX = 'skipped: ' 66 SKIPPED_PREFIX = 'skipped: '
35 FAILED_PREFIX = 'hghave check failed: ' 67 FAILED_PREFIX = 'hghave check failed: '
36 PYTHON = sys.executable 68 PYTHON = sys.executable
37 hgpkg = None
38 69
39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] 70 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
40 71
41 defaults = { 72 defaults = {
42 'jobs': ('HGTEST_JOBS', 1), 73 'jobs': ('HGTEST_JOBS', 1),
79 parser.add_option("-v", "--verbose", action="store_true", 110 parser.add_option("-v", "--verbose", action="store_true",
80 help="output verbose messages") 111 help="output verbose messages")
81 parser.add_option("-n", "--nodiff", action="store_true", 112 parser.add_option("-n", "--nodiff", action="store_true",
82 help="skip showing test changes") 113 help="skip showing test changes")
83 parser.add_option("--with-hg", type="string", 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 parser.add_option("--pure", action="store_true", 120 parser.add_option("--pure", action="store_true",
86 help="use pure Python code instead of C extensions") 121 help="use pure Python code instead of C extensions")
87 122
88 for option, default in defaults.items(): 123 for option, default in defaults.items():
89 defaults[option] = int(os.environ.get(*default)) 124 defaults[option] = int(os.environ.get(*default))
90 parser.set_defaults(**defaults) 125 parser.set_defaults(**defaults)
91 (options, args) = parser.parse_args() 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 options.anycoverage = (options.cover or 142 options.anycoverage = (options.cover or
95 options.cover_stdlib or 143 options.cover_stdlib or
96 options.annotate) 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 if options.verbose: 154 if options.verbose:
99 if options.jobs > 1 or options.child is not None: 155 if options.jobs > 1 or options.child is not None:
100 pid = "[%d]" % os.getpid() 156 pid = "[%d]" % os.getpid()
101 else: 157 else:
102 pid = None 158 pid = None
224 for line in f: 280 for line in f:
225 print line, 281 print line,
226 f.close() 282 f.close()
227 sys.exit(1) 283 sys.exit(1)
228 os.chdir(TESTDIR) 284 os.chdir(TESTDIR)
229
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 285
240 usecorrectpython() 286 usecorrectpython()
241 287
242 vlog("# Installing dummy diffstat") 288 vlog("# Installing dummy diffstat")
243 f = open(os.path.join(BINDIR, 'diffstat'), 'w') 289 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
510 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' 556 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
511 ' (expected %s)\n' 557 ' (expected %s)\n'
512 % (verb, actualhg, expecthg)) 558 % (verb, actualhg, expecthg))
513 559
514 def runchildren(options, tests): 560 def runchildren(options, tests):
515 if not options.with_hg: 561 if INST:
516 installhg(options) 562 installhg(options)
517 _checkhglib("Testing") 563 _checkhglib("Testing")
518 564
519 optcopy = dict(options.__dict__) 565 optcopy = dict(options.__dict__)
520 optcopy['jobs'] = 1 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 opts = [] 569 opts = []
523 for opt, value in optcopy.iteritems(): 570 for opt, value in optcopy.iteritems():
524 name = '--' + opt.replace('_', '-') 571 name = '--' + opt.replace('_', '-')
525 if value is True: 572 if value is True:
526 opts.append(name) 573 opts.append(name)
577 global DAEMON_PIDS, HGRCPATH 624 global DAEMON_PIDS, HGRCPATH
578 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') 625 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
579 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') 626 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
580 627
581 try: 628 try:
582 if not options.with_hg: 629 if INST:
583 installhg(options) 630 installhg(options)
584 _checkhglib("Testing") 631 _checkhglib("Testing")
585 632
586 if options.timeout > 0: 633 if options.timeout > 0:
587 try: 634 try:
685 os.environ["HGPORT"] = str(options.port) 732 os.environ["HGPORT"] = str(options.port)
686 os.environ["HGPORT1"] = str(options.port + 1) 733 os.environ["HGPORT1"] = str(options.port + 1)
687 os.environ["HGPORT2"] = str(options.port + 2) 734 os.environ["HGPORT2"] = str(options.port + 2)
688 735
689 if options.with_hg: 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 else: 746 else:
692 INST = os.path.join(HGTMP, "install") 747 INST = os.path.join(HGTMP, "install")
693 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") 748 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
694 PYTHONDIR = os.path.join(INST, "lib", "python") 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 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") 762 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
696 763
697 if len(args) == 0: 764 if len(args) == 0:
698 args = os.listdir(".") 765 args = os.listdir(".")
699 args.sort() 766 args.sort()
708 print "# Ran 0 tests, 0 skipped, 0 failed." 775 print "# Ran 0 tests, 0 skipped, 0 failed."
709 return 776 return
710 777
711 vlog("# Using TESTDIR", TESTDIR) 778 vlog("# Using TESTDIR", TESTDIR)
712 vlog("# Using HGTMP", HGTMP) 779 vlog("# Using HGTMP", HGTMP)
780 vlog("# Using PATH", os.environ["PATH"])
781 vlog("# Using PYTHONPATH", os.environ["PYTHONPATH"])
713 782
714 try: 783 try:
715 if len(tests) > 1 and options.jobs > 1: 784 if len(tests) > 1 and options.jobs > 1:
716 runchildren(options, tests) 785 runchildren(options, tests)
717 else: 786 else: