##// END OF EJS Templates
run-tests: replace inline python handling with more native scheme...
Matt Mackall -
r15434:5635a401 default
parent child Browse files
Show More
@@ -1,16 +1,19
1 import doctest, tempfile, os, sys
1 import sys
2
3 if __name__ == "__main__":
4 if 'TERM' in os.environ:
5 del os.environ['TERM']
6
7 fd, name = tempfile.mkstemp(suffix='hg-tst')
8
2
9 try:
3 globalvars = {}
10 os.write(fd, sys.stdin.read())
4 localvars = {}
11 os.close(fd)
5 lines = sys.stdin.readlines()
12 failures, _ = doctest.testfile(name, module_relative=False)
6 while lines:
13 if failures:
7 l = lines.pop(0)
14 sys.exit(1)
8 if l.startswith('SALT'):
15 finally:
9 print l[:-1]
16 os.remove(name)
10 elif l.startswith('>>> '):
11 snippet = l[4:]
12 while lines and lines[0].startswith('... '):
13 l = lines.pop(0)
14 snippet += "\n" + l[4:]
15 c = compile(snippet, '<heredoc>', 'single')
16 try:
17 exec c in globalvars, localvars
18 except Exception, inst:
19 print repr(inst)
@@ -1,1253 +1,1256
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 import re
56 import threading
56 import threading
57
57
58 processlock = threading.Lock()
58 processlock = threading.Lock()
59
59
60 closefds = os.name == 'posix'
60 closefds = os.name == 'posix'
61 def Popen4(cmd, wd, timeout):
61 def Popen4(cmd, wd, timeout):
62 processlock.acquire()
62 processlock.acquire()
63 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd,
63 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd,
64 close_fds=closefds,
64 close_fds=closefds,
65 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
66 stderr=subprocess.STDOUT)
66 stderr=subprocess.STDOUT)
67 processlock.release()
67 processlock.release()
68
68
69 p.fromchild = p.stdout
69 p.fromchild = p.stdout
70 p.tochild = p.stdin
70 p.tochild = p.stdin
71 p.childerr = p.stderr
71 p.childerr = p.stderr
72
72
73 p.timeout = False
73 p.timeout = False
74 if timeout:
74 if timeout:
75 def t():
75 def t():
76 start = time.time()
76 start = time.time()
77 while time.time() - start < timeout and p.returncode is None:
77 while time.time() - start < timeout and p.returncode is None:
78 time.sleep(1)
78 time.sleep(1)
79 p.timeout = True
79 p.timeout = True
80 if p.returncode is None:
80 if p.returncode is None:
81 terminate(p)
81 terminate(p)
82 threading.Thread(target=t).start()
82 threading.Thread(target=t).start()
83
83
84 return p
84 return p
85
85
86 # reserved exit code to skip test (used by hghave)
86 # reserved exit code to skip test (used by hghave)
87 SKIPPED_STATUS = 80
87 SKIPPED_STATUS = 80
88 SKIPPED_PREFIX = 'skipped: '
88 SKIPPED_PREFIX = 'skipped: '
89 FAILED_PREFIX = 'hghave check failed: '
89 FAILED_PREFIX = 'hghave check failed: '
90 PYTHON = sys.executable
90 PYTHON = sys.executable
91 IMPL_PATH = 'PYTHONPATH'
91 IMPL_PATH = 'PYTHONPATH'
92 if 'java' in sys.platform:
92 if 'java' in sys.platform:
93 IMPL_PATH = 'JYTHONPATH'
93 IMPL_PATH = 'JYTHONPATH'
94
94
95 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
95 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
96
96
97 defaults = {
97 defaults = {
98 'jobs': ('HGTEST_JOBS', 1),
98 'jobs': ('HGTEST_JOBS', 1),
99 'timeout': ('HGTEST_TIMEOUT', 180),
99 'timeout': ('HGTEST_TIMEOUT', 180),
100 'port': ('HGTEST_PORT', 20059),
100 'port': ('HGTEST_PORT', 20059),
101 'shell': ('HGTEST_SHELL', '/bin/sh'),
101 'shell': ('HGTEST_SHELL', '/bin/sh'),
102 }
102 }
103
103
104 def parselistfiles(files, listtype, warn=True):
104 def parselistfiles(files, listtype, warn=True):
105 entries = dict()
105 entries = dict()
106 for filename in files:
106 for filename in files:
107 try:
107 try:
108 path = os.path.expanduser(os.path.expandvars(filename))
108 path = os.path.expanduser(os.path.expandvars(filename))
109 f = open(path, "r")
109 f = open(path, "r")
110 except IOError, err:
110 except IOError, err:
111 if err.errno != errno.ENOENT:
111 if err.errno != errno.ENOENT:
112 raise
112 raise
113 if warn:
113 if warn:
114 print "warning: no such %s file: %s" % (listtype, filename)
114 print "warning: no such %s file: %s" % (listtype, filename)
115 continue
115 continue
116
116
117 for line in f.readlines():
117 for line in f.readlines():
118 line = line.split('#', 1)[0].strip()
118 line = line.split('#', 1)[0].strip()
119 if line:
119 if line:
120 entries[line] = filename
120 entries[line] = filename
121
121
122 f.close()
122 f.close()
123 return entries
123 return entries
124
124
125 def parseargs():
125 def parseargs():
126 parser = optparse.OptionParser("%prog [options] [tests]")
126 parser = optparse.OptionParser("%prog [options] [tests]")
127
127
128 # keep these sorted
128 # keep these sorted
129 parser.add_option("--blacklist", action="append",
129 parser.add_option("--blacklist", action="append",
130 help="skip tests listed in the specified blacklist file")
130 help="skip tests listed in the specified blacklist file")
131 parser.add_option("--whitelist", action="append",
131 parser.add_option("--whitelist", action="append",
132 help="always run tests listed in the specified whitelist file")
132 help="always run tests listed in the specified whitelist file")
133 parser.add_option("-C", "--annotate", action="store_true",
133 parser.add_option("-C", "--annotate", action="store_true",
134 help="output files annotated with coverage")
134 help="output files annotated with coverage")
135 parser.add_option("--child", type="int",
135 parser.add_option("--child", type="int",
136 help="run as child process, summary to given fd")
136 help="run as child process, summary to given fd")
137 parser.add_option("-c", "--cover", action="store_true",
137 parser.add_option("-c", "--cover", action="store_true",
138 help="print a test coverage report")
138 help="print a test coverage report")
139 parser.add_option("-d", "--debug", action="store_true",
139 parser.add_option("-d", "--debug", action="store_true",
140 help="debug mode: write output of test scripts to console"
140 help="debug mode: write output of test scripts to console"
141 " rather than capturing and diff'ing it (disables timeout)")
141 " rather than capturing and diff'ing it (disables timeout)")
142 parser.add_option("-f", "--first", action="store_true",
142 parser.add_option("-f", "--first", action="store_true",
143 help="exit on the first test failure")
143 help="exit on the first test failure")
144 parser.add_option("--inotify", action="store_true",
144 parser.add_option("--inotify", action="store_true",
145 help="enable inotify extension when running tests")
145 help="enable inotify extension when running tests")
146 parser.add_option("-i", "--interactive", action="store_true",
146 parser.add_option("-i", "--interactive", action="store_true",
147 help="prompt to accept changed output")
147 help="prompt to accept changed output")
148 parser.add_option("-j", "--jobs", type="int",
148 parser.add_option("-j", "--jobs", type="int",
149 help="number of jobs to run in parallel"
149 help="number of jobs to run in parallel"
150 " (default: $%s or %d)" % defaults['jobs'])
150 " (default: $%s or %d)" % defaults['jobs'])
151 parser.add_option("--keep-tmpdir", action="store_true",
151 parser.add_option("--keep-tmpdir", action="store_true",
152 help="keep temporary directory after running tests")
152 help="keep temporary directory after running tests")
153 parser.add_option("-k", "--keywords",
153 parser.add_option("-k", "--keywords",
154 help="run tests matching keywords")
154 help="run tests matching keywords")
155 parser.add_option("-l", "--local", action="store_true",
155 parser.add_option("-l", "--local", action="store_true",
156 help="shortcut for --with-hg=<testdir>/../hg")
156 help="shortcut for --with-hg=<testdir>/../hg")
157 parser.add_option("-n", "--nodiff", action="store_true",
157 parser.add_option("-n", "--nodiff", action="store_true",
158 help="skip showing test changes")
158 help="skip showing test changes")
159 parser.add_option("-p", "--port", type="int",
159 parser.add_option("-p", "--port", type="int",
160 help="port on which servers should listen"
160 help="port on which servers should listen"
161 " (default: $%s or %d)" % defaults['port'])
161 " (default: $%s or %d)" % defaults['port'])
162 parser.add_option("--pure", action="store_true",
162 parser.add_option("--pure", action="store_true",
163 help="use pure Python code instead of C extensions")
163 help="use pure Python code instead of C extensions")
164 parser.add_option("-R", "--restart", action="store_true",
164 parser.add_option("-R", "--restart", action="store_true",
165 help="restart at last error")
165 help="restart at last error")
166 parser.add_option("-r", "--retest", action="store_true",
166 parser.add_option("-r", "--retest", action="store_true",
167 help="retest failed tests")
167 help="retest failed tests")
168 parser.add_option("-S", "--noskips", action="store_true",
168 parser.add_option("-S", "--noskips", action="store_true",
169 help="don't report skip tests verbosely")
169 help="don't report skip tests verbosely")
170 parser.add_option("--shell", type="string",
170 parser.add_option("--shell", type="string",
171 help="shell to use (default: $%s or %s)" % defaults['shell'])
171 help="shell to use (default: $%s or %s)" % defaults['shell'])
172 parser.add_option("-t", "--timeout", type="int",
172 parser.add_option("-t", "--timeout", type="int",
173 help="kill errant tests after TIMEOUT seconds"
173 help="kill errant tests after TIMEOUT seconds"
174 " (default: $%s or %d)" % defaults['timeout'])
174 " (default: $%s or %d)" % defaults['timeout'])
175 parser.add_option("--tmpdir", type="string",
175 parser.add_option("--tmpdir", type="string",
176 help="run tests in the given temporary directory"
176 help="run tests in the given temporary directory"
177 " (implies --keep-tmpdir)")
177 " (implies --keep-tmpdir)")
178 parser.add_option("-v", "--verbose", action="store_true",
178 parser.add_option("-v", "--verbose", action="store_true",
179 help="output verbose messages")
179 help="output verbose messages")
180 parser.add_option("--view", type="string",
180 parser.add_option("--view", type="string",
181 help="external diff viewer")
181 help="external diff viewer")
182 parser.add_option("--with-hg", type="string",
182 parser.add_option("--with-hg", type="string",
183 metavar="HG",
183 metavar="HG",
184 help="test using specified hg script rather than a "
184 help="test using specified hg script rather than a "
185 "temporary installation")
185 "temporary installation")
186 parser.add_option("-3", "--py3k-warnings", action="store_true",
186 parser.add_option("-3", "--py3k-warnings", action="store_true",
187 help="enable Py3k warnings on Python 2.6+")
187 help="enable Py3k warnings on Python 2.6+")
188 parser.add_option('--extra-config-opt', action="append",
188 parser.add_option('--extra-config-opt', action="append",
189 help='set the given config opt in the test hgrc')
189 help='set the given config opt in the test hgrc')
190
190
191 for option, (envvar, default) in defaults.items():
191 for option, (envvar, default) in defaults.items():
192 defaults[option] = type(default)(os.environ.get(envvar, default))
192 defaults[option] = type(default)(os.environ.get(envvar, default))
193 parser.set_defaults(**defaults)
193 parser.set_defaults(**defaults)
194 (options, args) = parser.parse_args()
194 (options, args) = parser.parse_args()
195
195
196 # jython is always pure
196 # jython is always pure
197 if 'java' in sys.platform or '__pypy__' in sys.modules:
197 if 'java' in sys.platform or '__pypy__' in sys.modules:
198 options.pure = True
198 options.pure = True
199
199
200 if options.with_hg:
200 if options.with_hg:
201 if not (os.path.isfile(options.with_hg) and
201 if not (os.path.isfile(options.with_hg) and
202 os.access(options.with_hg, os.X_OK)):
202 os.access(options.with_hg, os.X_OK)):
203 parser.error('--with-hg must specify an executable hg script')
203 parser.error('--with-hg must specify an executable hg script')
204 if not os.path.basename(options.with_hg) == 'hg':
204 if not os.path.basename(options.with_hg) == 'hg':
205 sys.stderr.write('warning: --with-hg should specify an hg script\n')
205 sys.stderr.write('warning: --with-hg should specify an hg script\n')
206 if options.local:
206 if options.local:
207 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
207 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
208 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
208 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
209 if not os.access(hgbin, os.X_OK):
209 if not os.access(hgbin, os.X_OK):
210 parser.error('--local specified, but %r not found or not executable'
210 parser.error('--local specified, but %r not found or not executable'
211 % hgbin)
211 % hgbin)
212 options.with_hg = hgbin
212 options.with_hg = hgbin
213
213
214 options.anycoverage = options.cover or options.annotate
214 options.anycoverage = options.cover or options.annotate
215 if options.anycoverage:
215 if options.anycoverage:
216 try:
216 try:
217 import coverage
217 import coverage
218 covver = version.StrictVersion(coverage.__version__).version
218 covver = version.StrictVersion(coverage.__version__).version
219 if covver < (3, 3):
219 if covver < (3, 3):
220 parser.error('coverage options require coverage 3.3 or later')
220 parser.error('coverage options require coverage 3.3 or later')
221 except ImportError:
221 except ImportError:
222 parser.error('coverage options now require the coverage package')
222 parser.error('coverage options now require the coverage package')
223
223
224 if options.anycoverage and options.local:
224 if options.anycoverage and options.local:
225 # this needs some path mangling somewhere, I guess
225 # this needs some path mangling somewhere, I guess
226 parser.error("sorry, coverage options do not work when --local "
226 parser.error("sorry, coverage options do not work when --local "
227 "is specified")
227 "is specified")
228
228
229 global vlog
229 global vlog
230 if options.verbose:
230 if options.verbose:
231 if options.jobs > 1 or options.child is not None:
231 if options.jobs > 1 or options.child is not None:
232 pid = "[%d]" % os.getpid()
232 pid = "[%d]" % os.getpid()
233 else:
233 else:
234 pid = None
234 pid = None
235 def vlog(*msg):
235 def vlog(*msg):
236 iolock.acquire()
236 iolock.acquire()
237 if pid:
237 if pid:
238 print pid,
238 print pid,
239 for m in msg:
239 for m in msg:
240 print m,
240 print m,
241 print
241 print
242 sys.stdout.flush()
242 sys.stdout.flush()
243 iolock.release()
243 iolock.release()
244 else:
244 else:
245 vlog = lambda *msg: None
245 vlog = lambda *msg: None
246
246
247 if options.tmpdir:
247 if options.tmpdir:
248 options.tmpdir = os.path.expanduser(options.tmpdir)
248 options.tmpdir = os.path.expanduser(options.tmpdir)
249
249
250 if options.jobs < 1:
250 if options.jobs < 1:
251 parser.error('--jobs must be positive')
251 parser.error('--jobs must be positive')
252 if options.interactive and options.jobs > 1:
252 if options.interactive and options.jobs > 1:
253 print '(--interactive overrides --jobs)'
253 print '(--interactive overrides --jobs)'
254 options.jobs = 1
254 options.jobs = 1
255 if options.interactive and options.debug:
255 if options.interactive and options.debug:
256 parser.error("-i/--interactive and -d/--debug are incompatible")
256 parser.error("-i/--interactive and -d/--debug are incompatible")
257 if options.debug:
257 if options.debug:
258 if options.timeout != defaults['timeout']:
258 if options.timeout != defaults['timeout']:
259 sys.stderr.write(
259 sys.stderr.write(
260 'warning: --timeout option ignored with --debug\n')
260 'warning: --timeout option ignored with --debug\n')
261 options.timeout = 0
261 options.timeout = 0
262 if options.py3k_warnings:
262 if options.py3k_warnings:
263 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
263 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
264 parser.error('--py3k-warnings can only be used on Python 2.6+')
264 parser.error('--py3k-warnings can only be used on Python 2.6+')
265 if options.blacklist:
265 if options.blacklist:
266 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
266 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
267 if options.whitelist:
267 if options.whitelist:
268 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
268 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
269 warn=options.child is None)
269 warn=options.child is None)
270 else:
270 else:
271 options.whitelisted = {}
271 options.whitelisted = {}
272
272
273 return (options, args)
273 return (options, args)
274
274
275 def rename(src, dst):
275 def rename(src, dst):
276 """Like os.rename(), trade atomicity and opened files friendliness
276 """Like os.rename(), trade atomicity and opened files friendliness
277 for existing destination support.
277 for existing destination support.
278 """
278 """
279 shutil.copy(src, dst)
279 shutil.copy(src, dst)
280 os.remove(src)
280 os.remove(src)
281
281
282 def splitnewlines(text):
282 def splitnewlines(text):
283 '''like str.splitlines, but only split on newlines.
283 '''like str.splitlines, but only split on newlines.
284 keep line endings.'''
284 keep line endings.'''
285 i = 0
285 i = 0
286 lines = []
286 lines = []
287 while True:
287 while True:
288 n = text.find('\n', i)
288 n = text.find('\n', i)
289 if n == -1:
289 if n == -1:
290 last = text[i:]
290 last = text[i:]
291 if last:
291 if last:
292 lines.append(last)
292 lines.append(last)
293 return lines
293 return lines
294 lines.append(text[i:n + 1])
294 lines.append(text[i:n + 1])
295 i = n + 1
295 i = n + 1
296
296
297 def parsehghaveoutput(lines):
297 def parsehghaveoutput(lines):
298 '''Parse hghave log lines.
298 '''Parse hghave log lines.
299 Return tuple of lists (missing, failed):
299 Return tuple of lists (missing, failed):
300 * the missing/unknown features
300 * the missing/unknown features
301 * the features for which existence check failed'''
301 * the features for which existence check failed'''
302 missing = []
302 missing = []
303 failed = []
303 failed = []
304 for line in lines:
304 for line in lines:
305 if line.startswith(SKIPPED_PREFIX):
305 if line.startswith(SKIPPED_PREFIX):
306 line = line.splitlines()[0]
306 line = line.splitlines()[0]
307 missing.append(line[len(SKIPPED_PREFIX):])
307 missing.append(line[len(SKIPPED_PREFIX):])
308 elif line.startswith(FAILED_PREFIX):
308 elif line.startswith(FAILED_PREFIX):
309 line = line.splitlines()[0]
309 line = line.splitlines()[0]
310 failed.append(line[len(FAILED_PREFIX):])
310 failed.append(line[len(FAILED_PREFIX):])
311
311
312 return missing, failed
312 return missing, failed
313
313
314 def showdiff(expected, output, ref, err):
314 def showdiff(expected, output, ref, err):
315 print
315 print
316 for line in difflib.unified_diff(expected, output, ref, err):
316 for line in difflib.unified_diff(expected, output, ref, err):
317 sys.stdout.write(line)
317 sys.stdout.write(line)
318
318
319 def findprogram(program):
319 def findprogram(program):
320 """Search PATH for a executable program"""
320 """Search PATH for a executable program"""
321 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
321 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
322 name = os.path.join(p, program)
322 name = os.path.join(p, program)
323 if os.name == 'nt' or os.access(name, os.X_OK):
323 if os.name == 'nt' or os.access(name, os.X_OK):
324 return name
324 return name
325 return None
325 return None
326
326
327 def checktools():
327 def checktools():
328 # Before we go any further, check for pre-requisite tools
328 # Before we go any further, check for pre-requisite tools
329 # stuff from coreutils (cat, rm, etc) are not tested
329 # stuff from coreutils (cat, rm, etc) are not tested
330 for p in requiredtools:
330 for p in requiredtools:
331 if os.name == 'nt':
331 if os.name == 'nt':
332 p += '.exe'
332 p += '.exe'
333 found = findprogram(p)
333 found = findprogram(p)
334 if found:
334 if found:
335 vlog("# Found prerequisite", p, "at", found)
335 vlog("# Found prerequisite", p, "at", found)
336 else:
336 else:
337 print "WARNING: Did not find prerequisite tool: "+p
337 print "WARNING: Did not find prerequisite tool: "+p
338
338
339 def terminate(proc):
339 def terminate(proc):
340 """Terminate subprocess (with fallback for Python versions < 2.6)"""
340 """Terminate subprocess (with fallback for Python versions < 2.6)"""
341 vlog('# Terminating process %d' % proc.pid)
341 vlog('# Terminating process %d' % proc.pid)
342 try:
342 try:
343 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
343 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
344 except OSError:
344 except OSError:
345 pass
345 pass
346
346
347 def killdaemons():
347 def killdaemons():
348 # Kill off any leftover daemon processes
348 # Kill off any leftover daemon processes
349 try:
349 try:
350 fp = open(DAEMON_PIDS)
350 fp = open(DAEMON_PIDS)
351 for line in fp:
351 for line in fp:
352 try:
352 try:
353 pid = int(line)
353 pid = int(line)
354 except ValueError:
354 except ValueError:
355 continue
355 continue
356 try:
356 try:
357 os.kill(pid, 0)
357 os.kill(pid, 0)
358 vlog('# Killing daemon process %d' % pid)
358 vlog('# Killing daemon process %d' % pid)
359 os.kill(pid, signal.SIGTERM)
359 os.kill(pid, signal.SIGTERM)
360 time.sleep(0.25)
360 time.sleep(0.25)
361 os.kill(pid, 0)
361 os.kill(pid, 0)
362 vlog('# Daemon process %d is stuck - really killing it' % pid)
362 vlog('# Daemon process %d is stuck - really killing it' % pid)
363 os.kill(pid, signal.SIGKILL)
363 os.kill(pid, signal.SIGKILL)
364 except OSError, err:
364 except OSError, err:
365 if err.errno != errno.ESRCH:
365 if err.errno != errno.ESRCH:
366 raise
366 raise
367 fp.close()
367 fp.close()
368 os.unlink(DAEMON_PIDS)
368 os.unlink(DAEMON_PIDS)
369 except IOError:
369 except IOError:
370 pass
370 pass
371
371
372 def cleanup(options):
372 def cleanup(options):
373 if not options.keep_tmpdir:
373 if not options.keep_tmpdir:
374 vlog("# Cleaning up HGTMP", HGTMP)
374 vlog("# Cleaning up HGTMP", HGTMP)
375 shutil.rmtree(HGTMP, True)
375 shutil.rmtree(HGTMP, True)
376
376
377 def usecorrectpython():
377 def usecorrectpython():
378 # some tests run python interpreter. they must use same
378 # some tests run python interpreter. they must use same
379 # interpreter we use or bad things will happen.
379 # interpreter we use or bad things will happen.
380 exedir, exename = os.path.split(sys.executable)
380 exedir, exename = os.path.split(sys.executable)
381 if exename in ('python', 'python.exe'):
381 if exename in ('python', 'python.exe'):
382 path = findprogram(exename)
382 path = findprogram(exename)
383 if os.path.dirname(path) == exedir:
383 if os.path.dirname(path) == exedir:
384 return
384 return
385 else:
385 else:
386 exename = 'python'
386 exename = 'python'
387 vlog('# Making python executable in test path use correct Python')
387 vlog('# Making python executable in test path use correct Python')
388 mypython = os.path.join(BINDIR, exename)
388 mypython = os.path.join(BINDIR, exename)
389 try:
389 try:
390 os.symlink(sys.executable, mypython)
390 os.symlink(sys.executable, mypython)
391 except AttributeError:
391 except AttributeError:
392 # windows fallback
392 # windows fallback
393 shutil.copyfile(sys.executable, mypython)
393 shutil.copyfile(sys.executable, mypython)
394 shutil.copymode(sys.executable, mypython)
394 shutil.copymode(sys.executable, mypython)
395
395
396 def installhg(options):
396 def installhg(options):
397 vlog("# Performing temporary installation of HG")
397 vlog("# Performing temporary installation of HG")
398 installerrs = os.path.join("tests", "install.err")
398 installerrs = os.path.join("tests", "install.err")
399 pure = options.pure and "--pure" or ""
399 pure = options.pure and "--pure" or ""
400
400
401 # Run installer in hg root
401 # Run installer in hg root
402 script = os.path.realpath(sys.argv[0])
402 script = os.path.realpath(sys.argv[0])
403 hgroot = os.path.dirname(os.path.dirname(script))
403 hgroot = os.path.dirname(os.path.dirname(script))
404 os.chdir(hgroot)
404 os.chdir(hgroot)
405 nohome = '--home=""'
405 nohome = '--home=""'
406 if os.name == 'nt':
406 if os.name == 'nt':
407 # The --home="" trick works only on OS where os.sep == '/'
407 # The --home="" trick works only on OS where os.sep == '/'
408 # because of a distutils convert_path() fast-path. Avoid it at
408 # because of a distutils convert_path() fast-path. Avoid it at
409 # least on Windows for now, deal with .pydistutils.cfg bugs
409 # least on Windows for now, deal with .pydistutils.cfg bugs
410 # when they happen.
410 # when they happen.
411 nohome = ''
411 nohome = ''
412 cmd = ('%s setup.py %s clean --all'
412 cmd = ('%s setup.py %s clean --all'
413 ' build --build-base="%s"'
413 ' build --build-base="%s"'
414 ' install --force --prefix="%s" --install-lib="%s"'
414 ' install --force --prefix="%s" --install-lib="%s"'
415 ' --install-scripts="%s" %s >%s 2>&1'
415 ' --install-scripts="%s" %s >%s 2>&1'
416 % (sys.executable, pure, os.path.join(HGTMP, "build"),
416 % (sys.executable, pure, os.path.join(HGTMP, "build"),
417 INST, PYTHONDIR, BINDIR, nohome, installerrs))
417 INST, PYTHONDIR, BINDIR, nohome, installerrs))
418 vlog("# Running", cmd)
418 vlog("# Running", cmd)
419 if os.system(cmd) == 0:
419 if os.system(cmd) == 0:
420 if not options.verbose:
420 if not options.verbose:
421 os.remove(installerrs)
421 os.remove(installerrs)
422 else:
422 else:
423 f = open(installerrs)
423 f = open(installerrs)
424 for line in f:
424 for line in f:
425 print line,
425 print line,
426 f.close()
426 f.close()
427 sys.exit(1)
427 sys.exit(1)
428 os.chdir(TESTDIR)
428 os.chdir(TESTDIR)
429
429
430 usecorrectpython()
430 usecorrectpython()
431
431
432 vlog("# Installing dummy diffstat")
432 vlog("# Installing dummy diffstat")
433 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
433 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
434 f.write('#!' + sys.executable + '\n'
434 f.write('#!' + sys.executable + '\n'
435 'import sys\n'
435 'import sys\n'
436 'files = 0\n'
436 'files = 0\n'
437 'for line in sys.stdin:\n'
437 'for line in sys.stdin:\n'
438 ' if line.startswith("diff "):\n'
438 ' if line.startswith("diff "):\n'
439 ' files += 1\n'
439 ' files += 1\n'
440 'sys.stdout.write("files patched: %d\\n" % files)\n')
440 'sys.stdout.write("files patched: %d\\n" % files)\n')
441 f.close()
441 f.close()
442 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
442 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
443
443
444 if options.py3k_warnings and not options.anycoverage:
444 if options.py3k_warnings and not options.anycoverage:
445 vlog("# Updating hg command to enable Py3k Warnings switch")
445 vlog("# Updating hg command to enable Py3k Warnings switch")
446 f = open(os.path.join(BINDIR, 'hg'), 'r')
446 f = open(os.path.join(BINDIR, 'hg'), 'r')
447 lines = [line.rstrip() for line in f]
447 lines = [line.rstrip() for line in f]
448 lines[0] += ' -3'
448 lines[0] += ' -3'
449 f.close()
449 f.close()
450 f = open(os.path.join(BINDIR, 'hg'), 'w')
450 f = open(os.path.join(BINDIR, 'hg'), 'w')
451 for line in lines:
451 for line in lines:
452 f.write(line + '\n')
452 f.write(line + '\n')
453 f.close()
453 f.close()
454
454
455 hgbat = os.path.join(BINDIR, 'hg.bat')
455 hgbat = os.path.join(BINDIR, 'hg.bat')
456 if os.path.isfile(hgbat):
456 if os.path.isfile(hgbat):
457 # hg.bat expects to be put in bin/scripts while run-tests.py
457 # hg.bat expects to be put in bin/scripts while run-tests.py
458 # installation layout put it in bin/ directly. Fix it
458 # installation layout put it in bin/ directly. Fix it
459 f = open(hgbat, 'rb')
459 f = open(hgbat, 'rb')
460 data = f.read()
460 data = f.read()
461 f.close()
461 f.close()
462 if '"%~dp0..\python" "%~dp0hg" %*' in data:
462 if '"%~dp0..\python" "%~dp0hg" %*' in data:
463 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
463 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
464 '"%~dp0python" "%~dp0hg" %*')
464 '"%~dp0python" "%~dp0hg" %*')
465 f = open(hgbat, 'wb')
465 f = open(hgbat, 'wb')
466 f.write(data)
466 f.write(data)
467 f.close()
467 f.close()
468 else:
468 else:
469 print 'WARNING: cannot fix hg.bat reference to python.exe'
469 print 'WARNING: cannot fix hg.bat reference to python.exe'
470
470
471 if options.anycoverage:
471 if options.anycoverage:
472 custom = os.path.join(TESTDIR, 'sitecustomize.py')
472 custom = os.path.join(TESTDIR, 'sitecustomize.py')
473 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
473 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
474 vlog('# Installing coverage trigger to %s' % target)
474 vlog('# Installing coverage trigger to %s' % target)
475 shutil.copyfile(custom, target)
475 shutil.copyfile(custom, target)
476 rc = os.path.join(TESTDIR, '.coveragerc')
476 rc = os.path.join(TESTDIR, '.coveragerc')
477 vlog('# Installing coverage rc to %s' % rc)
477 vlog('# Installing coverage rc to %s' % rc)
478 os.environ['COVERAGE_PROCESS_START'] = rc
478 os.environ['COVERAGE_PROCESS_START'] = rc
479 fn = os.path.join(INST, '..', '.coverage')
479 fn = os.path.join(INST, '..', '.coverage')
480 os.environ['COVERAGE_FILE'] = fn
480 os.environ['COVERAGE_FILE'] = fn
481
481
482 def outputcoverage(options):
482 def outputcoverage(options):
483
483
484 vlog('# Producing coverage report')
484 vlog('# Producing coverage report')
485 os.chdir(PYTHONDIR)
485 os.chdir(PYTHONDIR)
486
486
487 def covrun(*args):
487 def covrun(*args):
488 cmd = 'coverage %s' % ' '.join(args)
488 cmd = 'coverage %s' % ' '.join(args)
489 vlog('# Running: %s' % cmd)
489 vlog('# Running: %s' % cmd)
490 os.system(cmd)
490 os.system(cmd)
491
491
492 if options.child:
492 if options.child:
493 return
493 return
494
494
495 covrun('-c')
495 covrun('-c')
496 omit = ','.join([BINDIR, TESTDIR])
496 omit = ','.join([BINDIR, TESTDIR])
497 covrun('-i', '-r', '"--omit=%s"' % omit) # report
497 covrun('-i', '-r', '"--omit=%s"' % omit) # report
498 if options.annotate:
498 if options.annotate:
499 adir = os.path.join(TESTDIR, 'annotated')
499 adir = os.path.join(TESTDIR, 'annotated')
500 if not os.path.isdir(adir):
500 if not os.path.isdir(adir):
501 os.mkdir(adir)
501 os.mkdir(adir)
502 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
502 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
503
503
504 def pytest(test, wd, options, replacements):
504 def pytest(test, wd, options, replacements):
505 py3kswitch = options.py3k_warnings and ' -3' or ''
505 py3kswitch = options.py3k_warnings and ' -3' or ''
506 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
506 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
507 vlog("# Running", cmd)
507 vlog("# Running", cmd)
508 return run(cmd, wd, options, replacements)
508 return run(cmd, wd, options, replacements)
509
509
510 def shtest(test, wd, options, replacements):
510 def shtest(test, wd, options, replacements):
511 cmd = '"%s"' % test
511 cmd = '"%s"' % test
512 vlog("# Running", cmd)
512 vlog("# Running", cmd)
513 return run(cmd, wd, options, replacements)
513 return run(cmd, wd, options, replacements)
514
514
515 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
515 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
516 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
516 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
517 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
517 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
518 escapemap.update({'\\': '\\\\', '\r': r'\r'})
518 escapemap.update({'\\': '\\\\', '\r': r'\r'})
519 def escapef(m):
519 def escapef(m):
520 return escapemap[m.group(0)]
520 return escapemap[m.group(0)]
521 def stringescape(s):
521 def stringescape(s):
522 return escapesub(escapef, s)
522 return escapesub(escapef, s)
523
523
524 def rematch(el, l):
524 def rematch(el, l):
525 try:
525 try:
526 # ensure that the regex matches to the end of the string
526 # ensure that the regex matches to the end of the string
527 return re.match(el + r'\Z', l)
527 return re.match(el + r'\Z', l)
528 except re.error:
528 except re.error:
529 # el is an invalid regex
529 # el is an invalid regex
530 return False
530 return False
531
531
532 def globmatch(el, l):
532 def globmatch(el, l):
533 # The only supported special characters are * and ?. Escaping is
533 # The only supported special characters are * and ?. Escaping is
534 # supported.
534 # supported.
535 i, n = 0, len(el)
535 i, n = 0, len(el)
536 res = ''
536 res = ''
537 while i < n:
537 while i < n:
538 c = el[i]
538 c = el[i]
539 i += 1
539 i += 1
540 if c == '\\' and el[i] in '*?\\':
540 if c == '\\' and el[i] in '*?\\':
541 res += el[i - 1:i + 1]
541 res += el[i - 1:i + 1]
542 i += 1
542 i += 1
543 elif c == '*':
543 elif c == '*':
544 res += '.*'
544 res += '.*'
545 elif c == '?':
545 elif c == '?':
546 res += '.'
546 res += '.'
547 else:
547 else:
548 res += re.escape(c)
548 res += re.escape(c)
549 return rematch(res, l)
549 return rematch(res, l)
550
550
551 def linematch(el, l):
551 def linematch(el, l):
552 if el == l: # perfect match (fast)
552 if el == l: # perfect match (fast)
553 return True
553 return True
554 if (el and
554 if (el and
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or
557 el.endswith(" (esc)\n") and el.decode('string-escape') == l)):
557 el.endswith(" (esc)\n") and el.decode('string-escape') == l)):
558 return True
558 return True
559 return False
559 return False
560
560
561 def tsttest(test, wd, options, replacements):
561 def tsttest(test, wd, options, replacements):
562 # We generate a shell script which outputs unique markers to line
562 # We generate a shell script which outputs unique markers to line
563 # up script results with our source. These markers include input
563 # up script results with our source. These markers include input
564 # line number and the last return code
564 # line number and the last return code
565 salt = "SALT" + str(time.time())
565 salt = "SALT" + str(time.time())
566 def addsalt(line):
566 def addsalt(line, inpython):
567 script.append('echo %s %s $?\n' % (salt, line))
567 if inpython:
568 script.append('%s %d 0\n' % (salt, line))
569 else:
570 script.append('echo %s %s $?\n' % (salt, line))
568
571
569 # After we run the shell script, we re-unify the script output
572 # After we run the shell script, we re-unify the script output
570 # with non-active parts of the source, with synchronization by our
573 # with non-active parts of the source, with synchronization by our
571 # SALT line number markers. The after table contains the
574 # SALT line number markers. The after table contains the
572 # non-active components, ordered by line number
575 # non-active components, ordered by line number
573 after = {}
576 after = {}
574 pos = prepos = -1
577 pos = prepos = -1
575
578
576 # Expected shellscript output
579 # Expected shellscript output
577 expected = {}
580 expected = {}
578
581
579 # We keep track of whether or not we're in a Python block so we
582 # We keep track of whether or not we're in a Python block so we
580 # can generate the surrounding doctest magic
583 # can generate the surrounding doctest magic
581 inpython = False
584 inpython = False
582
585
583 f = open(test)
586 f = open(test)
584 t = f.readlines()
587 t = f.readlines()
585 f.close()
588 f.close()
586
589
587 script = []
590 script = []
588 for n, l in enumerate(t):
591 for n, l in enumerate(t):
589 if not l.endswith('\n'):
592 if not l.endswith('\n'):
590 l += '\n'
593 l += '\n'
591 if l.startswith(' >>> '): # python inlines
594 if l.startswith(' >>> '): # python inlines
595 after.setdefault(pos, []).append(l)
596 prepos = pos
597 pos = n
592 if not inpython:
598 if not inpython:
593 # we've just entered a Python block, add the header
599 # we've just entered a Python block, add the header
594 inpython = True
600 inpython = True
595 addsalt(n)
601 addsalt(prepos, False) # make sure we report the exit code
596 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
602 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
597 prepos = pos
603 addsalt(n, True)
598 pos = n
604 script.append(l[2:])
605 if l.startswith(' ... '): # python inlines
599 after.setdefault(prepos, []).append(l)
606 after.setdefault(prepos, []).append(l)
600 script.append(l[2:])
607 script.append(l[2:])
601 elif l.startswith(' $ '): # commands
608 elif l.startswith(' $ '): # commands
602 if inpython:
609 if inpython:
603 script.append("EOF\n")
610 script.append("EOF\n")
604 inpython = False
611 inpython = False
605 after.setdefault(pos, []).append(l)
612 after.setdefault(pos, []).append(l)
606 prepos = pos
613 prepos = pos
607 pos = n
614 pos = n
608 addsalt(n)
615 addsalt(n, False)
609 script.append(l[4:])
616 script.append(l[4:])
610 elif l.startswith(' > '): # continuations
617 elif l.startswith(' > '): # continuations
611 after.setdefault(prepos, []).append(l)
618 after.setdefault(prepos, []).append(l)
612 script.append(l[4:])
619 script.append(l[4:])
613 elif l.startswith(' '): # results
620 elif l.startswith(' '): # results
614 if inpython:
621 # queue up a list of expected results
615 script.append(l[2:])
622 expected.setdefault(pos, []).append(l[2:])
616 after.setdefault(prepos, []).append(l)
617 else:
618 # queue up a list of expected results
619 expected.setdefault(pos, []).append(l[2:])
620 else:
623 else:
621 if inpython:
624 if inpython:
622 script.append("EOF\n")
625 script.append("EOF\n")
623 inpython = False
626 inpython = False
624 # non-command/result - queue up for merged output
627 # non-command/result - queue up for merged output
625 after.setdefault(pos, []).append(l)
628 after.setdefault(pos, []).append(l)
626
629
627 if inpython:
630 if inpython:
628 script.append("EOF\n")
631 script.append("EOF\n")
629 addsalt(n + 1)
632 addsalt(n + 1, False)
630
633
631 # Write out the script and execute it
634 # Write out the script and execute it
632 fd, name = tempfile.mkstemp(suffix='hg-tst')
635 fd, name = tempfile.mkstemp(suffix='hg-tst')
633 try:
636 try:
634 for l in script:
637 for l in script:
635 os.write(fd, l)
638 os.write(fd, l)
636 os.close(fd)
639 os.close(fd)
637
640
638 cmd = '"%s" "%s"' % (options.shell, name)
641 cmd = '"%s" "%s"' % (options.shell, name)
639 vlog("# Running", cmd)
642 vlog("# Running", cmd)
640 exitcode, output = run(cmd, wd, options, replacements)
643 exitcode, output = run(cmd, wd, options, replacements)
641 # do not merge output if skipped, return hghave message instead
644 # do not merge output if skipped, return hghave message instead
642 # similarly, with --debug, output is None
645 # similarly, with --debug, output is None
643 if exitcode == SKIPPED_STATUS or output is None:
646 if exitcode == SKIPPED_STATUS or output is None:
644 return exitcode, output
647 return exitcode, output
645 finally:
648 finally:
646 os.remove(name)
649 os.remove(name)
647
650
648 # Merge the script output back into a unified test
651 # Merge the script output back into a unified test
649
652
650 pos = -1
653 pos = -1
651 postout = []
654 postout = []
652 ret = 0
655 ret = 0
653 for n, l in enumerate(output):
656 for n, l in enumerate(output):
654 lout, lcmd = l, None
657 lout, lcmd = l, None
655 if salt in l:
658 if salt in l:
656 lout, lcmd = l.split(salt, 1)
659 lout, lcmd = l.split(salt, 1)
657
660
658 if lout:
661 if lout:
659 if lcmd:
662 if lcmd:
660 # output block had no trailing newline, clean up
663 # output block had no trailing newline, clean up
661 lout += ' (no-eol)\n'
664 lout += ' (no-eol)\n'
662
665
663 # find the expected output at the current position
666 # find the expected output at the current position
664 el = None
667 el = None
665 if pos in expected and expected[pos]:
668 if pos in expected and expected[pos]:
666 el = expected[pos].pop(0)
669 el = expected[pos].pop(0)
667
670
668 if linematch(el, lout):
671 if linematch(el, lout):
669 postout.append(" " + el)
672 postout.append(" " + el)
670 else:
673 else:
671 if needescape(lout):
674 if needescape(lout):
672 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
675 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
673 postout.append(" " + lout) # let diff deal with it
676 postout.append(" " + lout) # let diff deal with it
674
677
675 if lcmd:
678 if lcmd:
676 # add on last return code
679 # add on last return code
677 ret = int(lcmd.split()[1])
680 ret = int(lcmd.split()[1])
678 if ret != 0:
681 if ret != 0:
679 postout.append(" [%s]\n" % ret)
682 postout.append(" [%s]\n" % ret)
680 if pos in after:
683 if pos in after:
681 # merge in non-active test bits
684 # merge in non-active test bits
682 postout += after.pop(pos)
685 postout += after.pop(pos)
683 pos = int(lcmd.split()[0])
686 pos = int(lcmd.split()[0])
684
687
685 if pos in after:
688 if pos in after:
686 postout += after.pop(pos)
689 postout += after.pop(pos)
687
690
688 return exitcode, postout
691 return exitcode, postout
689
692
690 wifexited = getattr(os, "WIFEXITED", lambda x: False)
693 wifexited = getattr(os, "WIFEXITED", lambda x: False)
691 def run(cmd, wd, options, replacements):
694 def run(cmd, wd, options, replacements):
692 """Run command in a sub-process, capturing the output (stdout and stderr).
695 """Run command in a sub-process, capturing the output (stdout and stderr).
693 Return a tuple (exitcode, output). output is None in debug mode."""
696 Return a tuple (exitcode, output). output is None in debug mode."""
694 # TODO: Use subprocess.Popen if we're running on Python 2.4
697 # TODO: Use subprocess.Popen if we're running on Python 2.4
695 if options.debug:
698 if options.debug:
696 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
699 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
697 ret = proc.wait()
700 ret = proc.wait()
698 return (ret, None)
701 return (ret, None)
699
702
700 proc = Popen4(cmd, wd, options.timeout)
703 proc = Popen4(cmd, wd, options.timeout)
701 def cleanup():
704 def cleanup():
702 terminate(proc)
705 terminate(proc)
703 ret = proc.wait()
706 ret = proc.wait()
704 if ret == 0:
707 if ret == 0:
705 ret = signal.SIGTERM << 8
708 ret = signal.SIGTERM << 8
706 killdaemons()
709 killdaemons()
707 return ret
710 return ret
708
711
709 output = ''
712 output = ''
710 proc.tochild.close()
713 proc.tochild.close()
711
714
712 try:
715 try:
713 output = proc.fromchild.read()
716 output = proc.fromchild.read()
714 except KeyboardInterrupt:
717 except KeyboardInterrupt:
715 vlog('# Handling keyboard interrupt')
718 vlog('# Handling keyboard interrupt')
716 cleanup()
719 cleanup()
717 raise
720 raise
718
721
719 ret = proc.wait()
722 ret = proc.wait()
720 if wifexited(ret):
723 if wifexited(ret):
721 ret = os.WEXITSTATUS(ret)
724 ret = os.WEXITSTATUS(ret)
722
725
723 if proc.timeout:
726 if proc.timeout:
724 ret = 'timeout'
727 ret = 'timeout'
725
728
726 if ret:
729 if ret:
727 killdaemons()
730 killdaemons()
728
731
729 for s, r in replacements:
732 for s, r in replacements:
730 output = re.sub(s, r, output)
733 output = re.sub(s, r, output)
731 return ret, splitnewlines(output)
734 return ret, splitnewlines(output)
732
735
733 def runone(options, test):
736 def runone(options, test):
734 '''tristate output:
737 '''tristate output:
735 None -> skipped
738 None -> skipped
736 True -> passed
739 True -> passed
737 False -> failed'''
740 False -> failed'''
738
741
739 global results, resultslock, iolock
742 global results, resultslock, iolock
740
743
741 testpath = os.path.join(TESTDIR, test)
744 testpath = os.path.join(TESTDIR, test)
742
745
743 def result(l, e):
746 def result(l, e):
744 resultslock.acquire()
747 resultslock.acquire()
745 results[l].append(e)
748 results[l].append(e)
746 resultslock.release()
749 resultslock.release()
747
750
748 def skip(msg):
751 def skip(msg):
749 if not options.verbose:
752 if not options.verbose:
750 result('s', (test, msg))
753 result('s', (test, msg))
751 else:
754 else:
752 iolock.acquire()
755 iolock.acquire()
753 print "\nSkipping %s: %s" % (testpath, msg)
756 print "\nSkipping %s: %s" % (testpath, msg)
754 iolock.release()
757 iolock.release()
755 return None
758 return None
756
759
757 def fail(msg, ret):
760 def fail(msg, ret):
758 if not options.nodiff:
761 if not options.nodiff:
759 iolock.acquire()
762 iolock.acquire()
760 print "\nERROR: %s %s" % (testpath, msg)
763 print "\nERROR: %s %s" % (testpath, msg)
761 iolock.release()
764 iolock.release()
762 if (not ret and options.interactive
765 if (not ret and options.interactive
763 and os.path.exists(testpath + ".err")):
766 and os.path.exists(testpath + ".err")):
764 iolock.acquire()
767 iolock.acquire()
765 print "Accept this change? [n] ",
768 print "Accept this change? [n] ",
766 answer = sys.stdin.readline().strip()
769 answer = sys.stdin.readline().strip()
767 iolock.release()
770 iolock.release()
768 if answer.lower() in "y yes".split():
771 if answer.lower() in "y yes".split():
769 if test.endswith(".t"):
772 if test.endswith(".t"):
770 rename(testpath + ".err", testpath)
773 rename(testpath + ".err", testpath)
771 else:
774 else:
772 rename(testpath + ".err", testpath + ".out")
775 rename(testpath + ".err", testpath + ".out")
773 result('p', test)
776 result('p', test)
774 return
777 return
775 result('f', (test, msg))
778 result('f', (test, msg))
776
779
777 def success():
780 def success():
778 result('p', test)
781 result('p', test)
779
782
780 def ignore(msg):
783 def ignore(msg):
781 result('i', (test, msg))
784 result('i', (test, msg))
782
785
783 if (os.path.basename(test).startswith("test-") and '~' not in test and
786 if (os.path.basename(test).startswith("test-") and '~' not in test and
784 ('.' not in test or test.endswith('.py') or
787 ('.' not in test or test.endswith('.py') or
785 test.endswith('.bat') or test.endswith('.t'))):
788 test.endswith('.bat') or test.endswith('.t'))):
786 if not os.path.exists(test):
789 if not os.path.exists(test):
787 skip("doesn't exist")
790 skip("doesn't exist")
788 return None
791 return None
789 else:
792 else:
790 vlog('# Test file', test, 'not supported, ignoring')
793 vlog('# Test file', test, 'not supported, ignoring')
791 return None # not a supported test, don't record
794 return None # not a supported test, don't record
792
795
793 if not (options.whitelisted and test in options.whitelisted):
796 if not (options.whitelisted and test in options.whitelisted):
794 if options.blacklist and test in options.blacklist:
797 if options.blacklist and test in options.blacklist:
795 skip("blacklisted")
798 skip("blacklisted")
796 return None
799 return None
797
800
798 if options.retest and not os.path.exists(test + ".err"):
801 if options.retest and not os.path.exists(test + ".err"):
799 ignore("not retesting")
802 ignore("not retesting")
800 return None
803 return None
801
804
802 if options.keywords:
805 if options.keywords:
803 fp = open(test)
806 fp = open(test)
804 t = fp.read().lower() + test.lower()
807 t = fp.read().lower() + test.lower()
805 fp.close()
808 fp.close()
806 for k in options.keywords.lower().split():
809 for k in options.keywords.lower().split():
807 if k in t:
810 if k in t:
808 break
811 break
809 else:
812 else:
810 ignore("doesn't match keyword")
813 ignore("doesn't match keyword")
811 return None
814 return None
812
815
813 vlog("# Test", test)
816 vlog("# Test", test)
814
817
815 # create a fresh hgrc
818 # create a fresh hgrc
816 hgrc = open(HGRCPATH, 'w+')
819 hgrc = open(HGRCPATH, 'w+')
817 hgrc.write('[ui]\n')
820 hgrc.write('[ui]\n')
818 hgrc.write('slash = True\n')
821 hgrc.write('slash = True\n')
819 hgrc.write('[defaults]\n')
822 hgrc.write('[defaults]\n')
820 hgrc.write('backout = -d "0 0"\n')
823 hgrc.write('backout = -d "0 0"\n')
821 hgrc.write('commit = -d "0 0"\n')
824 hgrc.write('commit = -d "0 0"\n')
822 hgrc.write('tag = -d "0 0"\n')
825 hgrc.write('tag = -d "0 0"\n')
823 if options.inotify:
826 if options.inotify:
824 hgrc.write('[extensions]\n')
827 hgrc.write('[extensions]\n')
825 hgrc.write('inotify=\n')
828 hgrc.write('inotify=\n')
826 hgrc.write('[inotify]\n')
829 hgrc.write('[inotify]\n')
827 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
830 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
828 hgrc.write('appendpid=True\n')
831 hgrc.write('appendpid=True\n')
829 if options.extra_config_opt:
832 if options.extra_config_opt:
830 for opt in options.extra_config_opt:
833 for opt in options.extra_config_opt:
831 section, key = opt.split('.', 1)
834 section, key = opt.split('.', 1)
832 assert '=' in key, ('extra config opt %s must '
835 assert '=' in key, ('extra config opt %s must '
833 'have an = for assignment' % opt)
836 'have an = for assignment' % opt)
834 hgrc.write('[%s]\n%s\n' % (section, key))
837 hgrc.write('[%s]\n%s\n' % (section, key))
835 hgrc.close()
838 hgrc.close()
836
839
837 ref = os.path.join(TESTDIR, test+".out")
840 ref = os.path.join(TESTDIR, test+".out")
838 err = os.path.join(TESTDIR, test+".err")
841 err = os.path.join(TESTDIR, test+".err")
839 if os.path.exists(err):
842 if os.path.exists(err):
840 os.remove(err) # Remove any previous output files
843 os.remove(err) # Remove any previous output files
841 try:
844 try:
842 tf = open(testpath)
845 tf = open(testpath)
843 firstline = tf.readline().rstrip()
846 firstline = tf.readline().rstrip()
844 tf.close()
847 tf.close()
845 except:
848 except:
846 firstline = ''
849 firstline = ''
847 lctest = test.lower()
850 lctest = test.lower()
848
851
849 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
852 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
850 runner = pytest
853 runner = pytest
851 elif lctest.endswith('.t'):
854 elif lctest.endswith('.t'):
852 runner = tsttest
855 runner = tsttest
853 ref = testpath
856 ref = testpath
854 else:
857 else:
855 # do not try to run non-executable programs
858 # do not try to run non-executable programs
856 if not os.access(testpath, os.X_OK):
859 if not os.access(testpath, os.X_OK):
857 return skip("not executable")
860 return skip("not executable")
858 runner = shtest
861 runner = shtest
859
862
860 # Make a tmp subdirectory to work in
863 # Make a tmp subdirectory to work in
861 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
864 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
862 os.path.join(HGTMP, os.path.basename(test))
865 os.path.join(HGTMP, os.path.basename(test))
863
866
864 os.mkdir(testtmp)
867 os.mkdir(testtmp)
865 ret, out = runner(testpath, testtmp, options, [
868 ret, out = runner(testpath, testtmp, options, [
866 (re.escape(testtmp), '$TESTTMP'),
869 (re.escape(testtmp), '$TESTTMP'),
867 (r':%s\b' % options.port, ':$HGPORT'),
870 (r':%s\b' % options.port, ':$HGPORT'),
868 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
871 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
869 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
872 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
870 ])
873 ])
871 vlog("# Ret was:", ret)
874 vlog("# Ret was:", ret)
872
875
873 mark = '.'
876 mark = '.'
874
877
875 skipped = (ret == SKIPPED_STATUS)
878 skipped = (ret == SKIPPED_STATUS)
876
879
877 # If we're not in --debug mode and reference output file exists,
880 # If we're not in --debug mode and reference output file exists,
878 # check test output against it.
881 # check test output against it.
879 if options.debug:
882 if options.debug:
880 refout = None # to match "out is None"
883 refout = None # to match "out is None"
881 elif os.path.exists(ref):
884 elif os.path.exists(ref):
882 f = open(ref, "r")
885 f = open(ref, "r")
883 refout = list(splitnewlines(f.read()))
886 refout = list(splitnewlines(f.read()))
884 f.close()
887 f.close()
885 else:
888 else:
886 refout = []
889 refout = []
887
890
888 if (ret != 0 or out != refout) and not skipped and not options.debug:
891 if (ret != 0 or out != refout) and not skipped and not options.debug:
889 # Save errors to a file for diagnosis
892 # Save errors to a file for diagnosis
890 f = open(err, "wb")
893 f = open(err, "wb")
891 for line in out:
894 for line in out:
892 f.write(line)
895 f.write(line)
893 f.close()
896 f.close()
894
897
895 if skipped:
898 if skipped:
896 mark = 's'
899 mark = 's'
897 if out is None: # debug mode: nothing to parse
900 if out is None: # debug mode: nothing to parse
898 missing = ['unknown']
901 missing = ['unknown']
899 failed = None
902 failed = None
900 else:
903 else:
901 missing, failed = parsehghaveoutput(out)
904 missing, failed = parsehghaveoutput(out)
902 if not missing:
905 if not missing:
903 missing = ['irrelevant']
906 missing = ['irrelevant']
904 if failed:
907 if failed:
905 fail("hghave failed checking for %s" % failed[-1], ret)
908 fail("hghave failed checking for %s" % failed[-1], ret)
906 skipped = False
909 skipped = False
907 else:
910 else:
908 skip(missing[-1])
911 skip(missing[-1])
909 elif ret == 'timeout':
912 elif ret == 'timeout':
910 mark = 't'
913 mark = 't'
911 fail("timed out", ret)
914 fail("timed out", ret)
912 elif out != refout:
915 elif out != refout:
913 mark = '!'
916 mark = '!'
914 if not options.nodiff:
917 if not options.nodiff:
915 iolock.acquire()
918 iolock.acquire()
916 if options.view:
919 if options.view:
917 os.system("%s %s %s" % (options.view, ref, err))
920 os.system("%s %s %s" % (options.view, ref, err))
918 else:
921 else:
919 showdiff(refout, out, ref, err)
922 showdiff(refout, out, ref, err)
920 iolock.release()
923 iolock.release()
921 if ret:
924 if ret:
922 fail("output changed and returned error code %d" % ret, ret)
925 fail("output changed and returned error code %d" % ret, ret)
923 else:
926 else:
924 fail("output changed", ret)
927 fail("output changed", ret)
925 ret = 1
928 ret = 1
926 elif ret:
929 elif ret:
927 mark = '!'
930 mark = '!'
928 fail("returned error code %d" % ret, ret)
931 fail("returned error code %d" % ret, ret)
929 else:
932 else:
930 success()
933 success()
931
934
932 if not options.verbose:
935 if not options.verbose:
933 iolock.acquire()
936 iolock.acquire()
934 sys.stdout.write(mark)
937 sys.stdout.write(mark)
935 sys.stdout.flush()
938 sys.stdout.flush()
936 iolock.release()
939 iolock.release()
937
940
938 killdaemons()
941 killdaemons()
939
942
940 if not options.keep_tmpdir:
943 if not options.keep_tmpdir:
941 shutil.rmtree(testtmp, True)
944 shutil.rmtree(testtmp, True)
942 if skipped:
945 if skipped:
943 return None
946 return None
944 return ret == 0
947 return ret == 0
945
948
946 _hgpath = None
949 _hgpath = None
947
950
948 def _gethgpath():
951 def _gethgpath():
949 """Return the path to the mercurial package that is actually found by
952 """Return the path to the mercurial package that is actually found by
950 the current Python interpreter."""
953 the current Python interpreter."""
951 global _hgpath
954 global _hgpath
952 if _hgpath is not None:
955 if _hgpath is not None:
953 return _hgpath
956 return _hgpath
954
957
955 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
958 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
956 pipe = os.popen(cmd % PYTHON)
959 pipe = os.popen(cmd % PYTHON)
957 try:
960 try:
958 _hgpath = pipe.read().strip()
961 _hgpath = pipe.read().strip()
959 finally:
962 finally:
960 pipe.close()
963 pipe.close()
961 return _hgpath
964 return _hgpath
962
965
963 def _checkhglib(verb):
966 def _checkhglib(verb):
964 """Ensure that the 'mercurial' package imported by python is
967 """Ensure that the 'mercurial' package imported by python is
965 the one we expect it to be. If not, print a warning to stderr."""
968 the one we expect it to be. If not, print a warning to stderr."""
966 expecthg = os.path.join(PYTHONDIR, 'mercurial')
969 expecthg = os.path.join(PYTHONDIR, 'mercurial')
967 actualhg = _gethgpath()
970 actualhg = _gethgpath()
968 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
971 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
969 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
972 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
970 ' (expected %s)\n'
973 ' (expected %s)\n'
971 % (verb, actualhg, expecthg))
974 % (verb, actualhg, expecthg))
972
975
973 def runchildren(options, tests):
976 def runchildren(options, tests):
974 if INST:
977 if INST:
975 installhg(options)
978 installhg(options)
976 _checkhglib("Testing")
979 _checkhglib("Testing")
977
980
978 optcopy = dict(options.__dict__)
981 optcopy = dict(options.__dict__)
979 optcopy['jobs'] = 1
982 optcopy['jobs'] = 1
980
983
981 # Because whitelist has to override keyword matches, we have to
984 # Because whitelist has to override keyword matches, we have to
982 # actually load the whitelist in the children as well, so we allow
985 # actually load the whitelist in the children as well, so we allow
983 # the list of whitelist files to pass through and be parsed in the
986 # the list of whitelist files to pass through and be parsed in the
984 # children, but not the dict of whitelisted tests resulting from
987 # children, but not the dict of whitelisted tests resulting from
985 # the parse, used here to override blacklisted tests.
988 # the parse, used here to override blacklisted tests.
986 whitelist = optcopy['whitelisted'] or []
989 whitelist = optcopy['whitelisted'] or []
987 del optcopy['whitelisted']
990 del optcopy['whitelisted']
988
991
989 blacklist = optcopy['blacklist'] or []
992 blacklist = optcopy['blacklist'] or []
990 del optcopy['blacklist']
993 del optcopy['blacklist']
991 blacklisted = []
994 blacklisted = []
992
995
993 if optcopy['with_hg'] is None:
996 if optcopy['with_hg'] is None:
994 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
997 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
995 optcopy.pop('anycoverage', None)
998 optcopy.pop('anycoverage', None)
996
999
997 opts = []
1000 opts = []
998 for opt, value in optcopy.iteritems():
1001 for opt, value in optcopy.iteritems():
999 name = '--' + opt.replace('_', '-')
1002 name = '--' + opt.replace('_', '-')
1000 if value is True:
1003 if value is True:
1001 opts.append(name)
1004 opts.append(name)
1002 elif isinstance(value, list):
1005 elif isinstance(value, list):
1003 for v in value:
1006 for v in value:
1004 opts.append(name + '=' + str(v))
1007 opts.append(name + '=' + str(v))
1005 elif value is not None:
1008 elif value is not None:
1006 opts.append(name + '=' + str(value))
1009 opts.append(name + '=' + str(value))
1007
1010
1008 tests.reverse()
1011 tests.reverse()
1009 jobs = [[] for j in xrange(options.jobs)]
1012 jobs = [[] for j in xrange(options.jobs)]
1010 while tests:
1013 while tests:
1011 for job in jobs:
1014 for job in jobs:
1012 if not tests:
1015 if not tests:
1013 break
1016 break
1014 test = tests.pop()
1017 test = tests.pop()
1015 if test not in whitelist and test in blacklist:
1018 if test not in whitelist and test in blacklist:
1016 blacklisted.append(test)
1019 blacklisted.append(test)
1017 else:
1020 else:
1018 job.append(test)
1021 job.append(test)
1019 fps = {}
1022 fps = {}
1020
1023
1021 for j, job in enumerate(jobs):
1024 for j, job in enumerate(jobs):
1022 if not job:
1025 if not job:
1023 continue
1026 continue
1024 rfd, wfd = os.pipe()
1027 rfd, wfd = os.pipe()
1025 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1028 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1026 childtmp = os.path.join(HGTMP, 'child%d' % j)
1029 childtmp = os.path.join(HGTMP, 'child%d' % j)
1027 childopts += ['--tmpdir', childtmp]
1030 childopts += ['--tmpdir', childtmp]
1028 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1031 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1029 vlog(' '.join(cmdline))
1032 vlog(' '.join(cmdline))
1030 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1033 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1031 os.close(wfd)
1034 os.close(wfd)
1032 signal.signal(signal.SIGINT, signal.SIG_IGN)
1035 signal.signal(signal.SIGINT, signal.SIG_IGN)
1033 failures = 0
1036 failures = 0
1034 tested, skipped, failed = 0, 0, 0
1037 tested, skipped, failed = 0, 0, 0
1035 skips = []
1038 skips = []
1036 fails = []
1039 fails = []
1037 while fps:
1040 while fps:
1038 pid, status = os.wait()
1041 pid, status = os.wait()
1039 fp = fps.pop(pid)
1042 fp = fps.pop(pid)
1040 l = fp.read().splitlines()
1043 l = fp.read().splitlines()
1041 try:
1044 try:
1042 test, skip, fail = map(int, l[:3])
1045 test, skip, fail = map(int, l[:3])
1043 except ValueError:
1046 except ValueError:
1044 test, skip, fail = 0, 0, 0
1047 test, skip, fail = 0, 0, 0
1045 split = -fail or len(l)
1048 split = -fail or len(l)
1046 for s in l[3:split]:
1049 for s in l[3:split]:
1047 skips.append(s.split(" ", 1))
1050 skips.append(s.split(" ", 1))
1048 for s in l[split:]:
1051 for s in l[split:]:
1049 fails.append(s.split(" ", 1))
1052 fails.append(s.split(" ", 1))
1050 tested += test
1053 tested += test
1051 skipped += skip
1054 skipped += skip
1052 failed += fail
1055 failed += fail
1053 vlog('pid %d exited, status %d' % (pid, status))
1056 vlog('pid %d exited, status %d' % (pid, status))
1054 failures |= status
1057 failures |= status
1055 print
1058 print
1056 skipped += len(blacklisted)
1059 skipped += len(blacklisted)
1057 if not options.noskips:
1060 if not options.noskips:
1058 for s in skips:
1061 for s in skips:
1059 print "Skipped %s: %s" % (s[0], s[1])
1062 print "Skipped %s: %s" % (s[0], s[1])
1060 for s in blacklisted:
1063 for s in blacklisted:
1061 print "Skipped %s: blacklisted" % s
1064 print "Skipped %s: blacklisted" % s
1062 for s in fails:
1065 for s in fails:
1063 print "Failed %s: %s" % (s[0], s[1])
1066 print "Failed %s: %s" % (s[0], s[1])
1064
1067
1065 _checkhglib("Tested")
1068 _checkhglib("Tested")
1066 print "# Ran %d tests, %d skipped, %d failed." % (
1069 print "# Ran %d tests, %d skipped, %d failed." % (
1067 tested, skipped, failed)
1070 tested, skipped, failed)
1068
1071
1069 if options.anycoverage:
1072 if options.anycoverage:
1070 outputcoverage(options)
1073 outputcoverage(options)
1071 sys.exit(failures != 0)
1074 sys.exit(failures != 0)
1072
1075
1073 results = dict(p=[], f=[], s=[], i=[])
1076 results = dict(p=[], f=[], s=[], i=[])
1074 resultslock = threading.Lock()
1077 resultslock = threading.Lock()
1075 iolock = threading.Lock()
1078 iolock = threading.Lock()
1076
1079
1077 def runqueue(options, tests, results):
1080 def runqueue(options, tests, results):
1078 for test in tests:
1081 for test in tests:
1079 ret = runone(options, test)
1082 ret = runone(options, test)
1080 if options.first and ret is not None and not ret:
1083 if options.first and ret is not None and not ret:
1081 break
1084 break
1082
1085
1083 def runtests(options, tests):
1086 def runtests(options, tests):
1084 global DAEMON_PIDS, HGRCPATH
1087 global DAEMON_PIDS, HGRCPATH
1085 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1088 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1086 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1089 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1087
1090
1088 try:
1091 try:
1089 if INST:
1092 if INST:
1090 installhg(options)
1093 installhg(options)
1091 _checkhglib("Testing")
1094 _checkhglib("Testing")
1092
1095
1093 if options.restart:
1096 if options.restart:
1094 orig = list(tests)
1097 orig = list(tests)
1095 while tests:
1098 while tests:
1096 if os.path.exists(tests[0] + ".err"):
1099 if os.path.exists(tests[0] + ".err"):
1097 break
1100 break
1098 tests.pop(0)
1101 tests.pop(0)
1099 if not tests:
1102 if not tests:
1100 print "running all tests"
1103 print "running all tests"
1101 tests = orig
1104 tests = orig
1102
1105
1103 runqueue(options, tests, results)
1106 runqueue(options, tests, results)
1104
1107
1105 failed = len(results['f'])
1108 failed = len(results['f'])
1106 tested = len(results['p']) + failed
1109 tested = len(results['p']) + failed
1107 skipped = len(results['s'])
1110 skipped = len(results['s'])
1108 ignored = len(results['i'])
1111 ignored = len(results['i'])
1109
1112
1110 if options.child:
1113 if options.child:
1111 fp = os.fdopen(options.child, 'w')
1114 fp = os.fdopen(options.child, 'w')
1112 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1115 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1113 for s in results['s']:
1116 for s in results['s']:
1114 fp.write("%s %s\n" % s)
1117 fp.write("%s %s\n" % s)
1115 for s in results['f']:
1118 for s in results['f']:
1116 fp.write("%s %s\n" % s)
1119 fp.write("%s %s\n" % s)
1117 fp.close()
1120 fp.close()
1118 else:
1121 else:
1119 print
1122 print
1120 for s in results['s']:
1123 for s in results['s']:
1121 print "Skipped %s: %s" % s
1124 print "Skipped %s: %s" % s
1122 for s in results['f']:
1125 for s in results['f']:
1123 print "Failed %s: %s" % s
1126 print "Failed %s: %s" % s
1124 _checkhglib("Tested")
1127 _checkhglib("Tested")
1125 print "# Ran %d tests, %d skipped, %d failed." % (
1128 print "# Ran %d tests, %d skipped, %d failed." % (
1126 tested, skipped + ignored, failed)
1129 tested, skipped + ignored, failed)
1127
1130
1128 if options.anycoverage:
1131 if options.anycoverage:
1129 outputcoverage(options)
1132 outputcoverage(options)
1130 except KeyboardInterrupt:
1133 except KeyboardInterrupt:
1131 failed = True
1134 failed = True
1132 print "\ninterrupted!"
1135 print "\ninterrupted!"
1133
1136
1134 if failed:
1137 if failed:
1135 sys.exit(1)
1138 sys.exit(1)
1136
1139
1137 def main():
1140 def main():
1138 (options, args) = parseargs()
1141 (options, args) = parseargs()
1139 if not options.child:
1142 if not options.child:
1140 os.umask(022)
1143 os.umask(022)
1141
1144
1142 checktools()
1145 checktools()
1143
1146
1144 if len(args) == 0:
1147 if len(args) == 0:
1145 args = os.listdir(".")
1148 args = os.listdir(".")
1146 args.sort()
1149 args.sort()
1147
1150
1148 tests = args
1151 tests = args
1149
1152
1150 # Reset some environment variables to well-known values so that
1153 # Reset some environment variables to well-known values so that
1151 # the tests produce repeatable output.
1154 # the tests produce repeatable output.
1152 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1155 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1153 os.environ['TZ'] = 'GMT'
1156 os.environ['TZ'] = 'GMT'
1154 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1157 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1155 os.environ['CDPATH'] = ''
1158 os.environ['CDPATH'] = ''
1156 os.environ['COLUMNS'] = '80'
1159 os.environ['COLUMNS'] = '80'
1157 os.environ['GREP_OPTIONS'] = ''
1160 os.environ['GREP_OPTIONS'] = ''
1158 os.environ['http_proxy'] = ''
1161 os.environ['http_proxy'] = ''
1159 os.environ['no_proxy'] = ''
1162 os.environ['no_proxy'] = ''
1160 os.environ['NO_PROXY'] = ''
1163 os.environ['NO_PROXY'] = ''
1161
1164
1162 # unset env related to hooks
1165 # unset env related to hooks
1163 for k in os.environ.keys():
1166 for k in os.environ.keys():
1164 if k.startswith('HG_'):
1167 if k.startswith('HG_'):
1165 # can't remove on solaris
1168 # can't remove on solaris
1166 os.environ[k] = ''
1169 os.environ[k] = ''
1167 del os.environ[k]
1170 del os.environ[k]
1168
1171
1169 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1172 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1170 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1173 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1171 if options.tmpdir:
1174 if options.tmpdir:
1172 options.keep_tmpdir = True
1175 options.keep_tmpdir = True
1173 tmpdir = options.tmpdir
1176 tmpdir = options.tmpdir
1174 if os.path.exists(tmpdir):
1177 if os.path.exists(tmpdir):
1175 # Meaning of tmpdir has changed since 1.3: we used to create
1178 # Meaning of tmpdir has changed since 1.3: we used to create
1176 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1179 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1177 # tmpdir already exists.
1180 # tmpdir already exists.
1178 sys.exit("error: temp dir %r already exists" % tmpdir)
1181 sys.exit("error: temp dir %r already exists" % tmpdir)
1179
1182
1180 # Automatically removing tmpdir sounds convenient, but could
1183 # Automatically removing tmpdir sounds convenient, but could
1181 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1184 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1182 # or "--tmpdir=$HOME".
1185 # or "--tmpdir=$HOME".
1183 #vlog("# Removing temp dir", tmpdir)
1186 #vlog("# Removing temp dir", tmpdir)
1184 #shutil.rmtree(tmpdir)
1187 #shutil.rmtree(tmpdir)
1185 os.makedirs(tmpdir)
1188 os.makedirs(tmpdir)
1186 else:
1189 else:
1187 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1190 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1188 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1191 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1189 DAEMON_PIDS = None
1192 DAEMON_PIDS = None
1190 HGRCPATH = None
1193 HGRCPATH = None
1191
1194
1192 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1195 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1193 os.environ["HGMERGE"] = "internal:merge"
1196 os.environ["HGMERGE"] = "internal:merge"
1194 os.environ["HGUSER"] = "test"
1197 os.environ["HGUSER"] = "test"
1195 os.environ["HGENCODING"] = "ascii"
1198 os.environ["HGENCODING"] = "ascii"
1196 os.environ["HGENCODINGMODE"] = "strict"
1199 os.environ["HGENCODINGMODE"] = "strict"
1197 os.environ["HGPORT"] = str(options.port)
1200 os.environ["HGPORT"] = str(options.port)
1198 os.environ["HGPORT1"] = str(options.port + 1)
1201 os.environ["HGPORT1"] = str(options.port + 1)
1199 os.environ["HGPORT2"] = str(options.port + 2)
1202 os.environ["HGPORT2"] = str(options.port + 2)
1200
1203
1201 if options.with_hg:
1204 if options.with_hg:
1202 INST = None
1205 INST = None
1203 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1206 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1204
1207
1205 # This looks redundant with how Python initializes sys.path from
1208 # This looks redundant with how Python initializes sys.path from
1206 # the location of the script being executed. Needed because the
1209 # the location of the script being executed. Needed because the
1207 # "hg" specified by --with-hg is not the only Python script
1210 # "hg" specified by --with-hg is not the only Python script
1208 # executed in the test suite that needs to import 'mercurial'
1211 # executed in the test suite that needs to import 'mercurial'
1209 # ... which means it's not really redundant at all.
1212 # ... which means it's not really redundant at all.
1210 PYTHONDIR = BINDIR
1213 PYTHONDIR = BINDIR
1211 else:
1214 else:
1212 INST = os.path.join(HGTMP, "install")
1215 INST = os.path.join(HGTMP, "install")
1213 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1216 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1214 PYTHONDIR = os.path.join(INST, "lib", "python")
1217 PYTHONDIR = os.path.join(INST, "lib", "python")
1215
1218
1216 os.environ["BINDIR"] = BINDIR
1219 os.environ["BINDIR"] = BINDIR
1217 os.environ["PYTHON"] = PYTHON
1220 os.environ["PYTHON"] = PYTHON
1218
1221
1219 if not options.child:
1222 if not options.child:
1220 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1223 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1221 os.environ["PATH"] = os.pathsep.join(path)
1224 os.environ["PATH"] = os.pathsep.join(path)
1222
1225
1223 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1226 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1224 # can run .../tests/run-tests.py test-foo where test-foo
1227 # can run .../tests/run-tests.py test-foo where test-foo
1225 # adds an extension to HGRC
1228 # adds an extension to HGRC
1226 pypath = [PYTHONDIR, TESTDIR]
1229 pypath = [PYTHONDIR, TESTDIR]
1227 # We have to augment PYTHONPATH, rather than simply replacing
1230 # We have to augment PYTHONPATH, rather than simply replacing
1228 # it, in case external libraries are only available via current
1231 # it, in case external libraries are only available via current
1229 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1232 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1230 # are in /opt/subversion.)
1233 # are in /opt/subversion.)
1231 oldpypath = os.environ.get(IMPL_PATH)
1234 oldpypath = os.environ.get(IMPL_PATH)
1232 if oldpypath:
1235 if oldpypath:
1233 pypath.append(oldpypath)
1236 pypath.append(oldpypath)
1234 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1237 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1235
1238
1236 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1239 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1237
1240
1238 vlog("# Using TESTDIR", TESTDIR)
1241 vlog("# Using TESTDIR", TESTDIR)
1239 vlog("# Using HGTMP", HGTMP)
1242 vlog("# Using HGTMP", HGTMP)
1240 vlog("# Using PATH", os.environ["PATH"])
1243 vlog("# Using PATH", os.environ["PATH"])
1241 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1244 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1242
1245
1243 try:
1246 try:
1244 if len(tests) > 1 and options.jobs > 1:
1247 if len(tests) > 1 and options.jobs > 1:
1245 runchildren(options, tests)
1248 runchildren(options, tests)
1246 else:
1249 else:
1247 runtests(options, tests)
1250 runtests(options, tests)
1248 finally:
1251 finally:
1249 time.sleep(1)
1252 time.sleep(1)
1250 cleanup(options)
1253 cleanup(options)
1251
1254
1252 if __name__ == '__main__':
1255 if __name__ == '__main__':
1253 main()
1256 main()
@@ -1,53 +1,58
1 Simple commands:
1 Simple commands:
2
2
3 $ echo foo
3 $ echo foo
4 foo
4 foo
5 $ printf 'oh no'
5 $ printf 'oh no'
6 oh no (no-eol)
6 oh no (no-eol)
7 $ printf 'bar\nbaz\n' | cat
7 $ printf 'bar\nbaz\n' | cat
8 bar
8 bar
9 baz
9 baz
10
10
11 Multi-line command:
11 Multi-line command:
12
12
13 $ foo() {
13 $ foo() {
14 > echo bar
14 > echo bar
15 > }
15 > }
16 $ foo
16 $ foo
17 bar
17 bar
18
18
19 Return codes before inline python:
20
21 $ false
22 [1]
23
19 Doctest commands:
24 Doctest commands:
20
25
21 >>> print 'foo'
26 >>> print 'foo'
22 foo
27 foo
23 $ echo interleaved
28 $ echo interleaved
24 interleaved
29 interleaved
25 >>> for c in 'xyz':
30 >>> for c in 'xyz':
26 ... print c
31 ... print c
27 x
32 x
28 y
33 y
29 z
34 z
30 >>> print
35 >>> print
31 <BLANKLINE>
36
32
37
33 Regular expressions:
38 Regular expressions:
34
39
35 $ echo foobarbaz
40 $ echo foobarbaz
36 foobar.* (re)
41 foobar.* (re)
37 $ echo barbazquux
42 $ echo barbazquux
38 .*quux.* (re)
43 .*quux.* (re)
39
44
40 Globs:
45 Globs:
41
46
42 $ printf '* \\foobarbaz {10}\n'
47 $ printf '* \\foobarbaz {10}\n'
43 \* \\fo?bar* {10} (glob)
48 \* \\fo?bar* {10} (glob)
44
49
45 Literal match ending in " (re)":
50 Literal match ending in " (re)":
46
51
47 $ echo 'foo (re)'
52 $ echo 'foo (re)'
48 foo (re)
53 foo (re)
49
54
50 Exit code:
55 Exit code:
51
56
52 $ (exit 1)
57 $ (exit 1)
53 [1]
58 [1]
General Comments 0
You need to be logged in to leave comments. Login now