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