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