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