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