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