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