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