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