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