##// END OF EJS Templates
tests: don't require 'hg' without extension on windows...
Mads Kiilerich -
r16538:dd194e5d stable
parent child Browse files
Show More
@@ -1,1282 +1,1282 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 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 f = open(test)
597 f = open(test)
598 t = f.readlines()
598 t = f.readlines()
599 f.close()
599 f.close()
600
600
601 script = []
601 script = []
602 if options.debug:
602 if options.debug:
603 script.append('set -x\n')
603 script.append('set -x\n')
604 if os.getenv('MSYSTEM'):
604 if os.getenv('MSYSTEM'):
605 script.append('alias pwd="pwd -W"\n')
605 script.append('alias pwd="pwd -W"\n')
606 for n, l in enumerate(t):
606 for n, l in enumerate(t):
607 if not l.endswith('\n'):
607 if not l.endswith('\n'):
608 l += '\n'
608 l += '\n'
609 if l.startswith(' >>> '): # python inlines
609 if l.startswith(' >>> '): # python inlines
610 after.setdefault(pos, []).append(l)
610 after.setdefault(pos, []).append(l)
611 prepos = pos
611 prepos = pos
612 pos = n
612 pos = n
613 if not inpython:
613 if not inpython:
614 # we've just entered a Python block, add the header
614 # we've just entered a Python block, add the header
615 inpython = True
615 inpython = True
616 addsalt(prepos, False) # make sure we report the exit code
616 addsalt(prepos, False) # make sure we report the exit code
617 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
617 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
618 addsalt(n, True)
618 addsalt(n, True)
619 script.append(l[2:])
619 script.append(l[2:])
620 if l.startswith(' ... '): # python inlines
620 if l.startswith(' ... '): # python inlines
621 after.setdefault(prepos, []).append(l)
621 after.setdefault(prepos, []).append(l)
622 script.append(l[2:])
622 script.append(l[2:])
623 elif l.startswith(' $ '): # commands
623 elif l.startswith(' $ '): # commands
624 if inpython:
624 if inpython:
625 script.append("EOF\n")
625 script.append("EOF\n")
626 inpython = False
626 inpython = False
627 after.setdefault(pos, []).append(l)
627 after.setdefault(pos, []).append(l)
628 prepos = pos
628 prepos = pos
629 pos = n
629 pos = n
630 addsalt(n, False)
630 addsalt(n, False)
631 script.append(l[4:])
631 script.append(l[4:])
632 elif l.startswith(' > '): # continuations
632 elif l.startswith(' > '): # continuations
633 after.setdefault(prepos, []).append(l)
633 after.setdefault(prepos, []).append(l)
634 script.append(l[4:])
634 script.append(l[4:])
635 elif l.startswith(' '): # results
635 elif l.startswith(' '): # results
636 # queue up a list of expected results
636 # queue up a list of expected results
637 expected.setdefault(pos, []).append(l[2:])
637 expected.setdefault(pos, []).append(l[2:])
638 else:
638 else:
639 if inpython:
639 if inpython:
640 script.append("EOF\n")
640 script.append("EOF\n")
641 inpython = False
641 inpython = False
642 # non-command/result - queue up for merged output
642 # non-command/result - queue up for merged output
643 after.setdefault(pos, []).append(l)
643 after.setdefault(pos, []).append(l)
644
644
645 if inpython:
645 if inpython:
646 script.append("EOF\n")
646 script.append("EOF\n")
647 addsalt(n + 1, False)
647 addsalt(n + 1, False)
648
648
649 # Write out the script and execute it
649 # Write out the script and execute it
650 fd, name = tempfile.mkstemp(suffix='hg-tst')
650 fd, name = tempfile.mkstemp(suffix='hg-tst')
651 try:
651 try:
652 for l in script:
652 for l in script:
653 os.write(fd, l)
653 os.write(fd, l)
654 os.close(fd)
654 os.close(fd)
655
655
656 cmd = '%s "%s"' % (options.shell, name)
656 cmd = '%s "%s"' % (options.shell, name)
657 vlog("# Running", cmd)
657 vlog("# Running", cmd)
658 exitcode, output = run(cmd, wd, options, replacements)
658 exitcode, output = run(cmd, wd, options, replacements)
659 # do not merge output if skipped, return hghave message instead
659 # do not merge output if skipped, return hghave message instead
660 # similarly, with --debug, output is None
660 # similarly, with --debug, output is None
661 if exitcode == SKIPPED_STATUS or output is None:
661 if exitcode == SKIPPED_STATUS or output is None:
662 return exitcode, output
662 return exitcode, output
663 finally:
663 finally:
664 os.remove(name)
664 os.remove(name)
665
665
666 # Merge the script output back into a unified test
666 # Merge the script output back into a unified test
667
667
668 pos = -1
668 pos = -1
669 postout = []
669 postout = []
670 ret = 0
670 ret = 0
671 for n, l in enumerate(output):
671 for n, l in enumerate(output):
672 lout, lcmd = l, None
672 lout, lcmd = l, None
673 if salt in l:
673 if salt in l:
674 lout, lcmd = l.split(salt, 1)
674 lout, lcmd = l.split(salt, 1)
675
675
676 if lout:
676 if lout:
677 if lcmd:
677 if lcmd:
678 # output block had no trailing newline, clean up
678 # output block had no trailing newline, clean up
679 lout += ' (no-eol)\n'
679 lout += ' (no-eol)\n'
680
680
681 # find the expected output at the current position
681 # find the expected output at the current position
682 el = None
682 el = None
683 if pos in expected and expected[pos]:
683 if pos in expected and expected[pos]:
684 el = expected[pos].pop(0)
684 el = expected[pos].pop(0)
685
685
686 if linematch(el, lout):
686 if linematch(el, lout):
687 postout.append(" " + el)
687 postout.append(" " + el)
688 else:
688 else:
689 if needescape(lout):
689 if needescape(lout):
690 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
690 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
691 postout.append(" " + lout) # let diff deal with it
691 postout.append(" " + lout) # let diff deal with it
692
692
693 if lcmd:
693 if lcmd:
694 # add on last return code
694 # add on last return code
695 ret = int(lcmd.split()[1])
695 ret = int(lcmd.split()[1])
696 if ret != 0:
696 if ret != 0:
697 postout.append(" [%s]\n" % ret)
697 postout.append(" [%s]\n" % ret)
698 if pos in after:
698 if pos in after:
699 # merge in non-active test bits
699 # merge in non-active test bits
700 postout += after.pop(pos)
700 postout += after.pop(pos)
701 pos = int(lcmd.split()[0])
701 pos = int(lcmd.split()[0])
702
702
703 if pos in after:
703 if pos in after:
704 postout += after.pop(pos)
704 postout += after.pop(pos)
705
705
706 return exitcode, postout
706 return exitcode, postout
707
707
708 wifexited = getattr(os, "WIFEXITED", lambda x: False)
708 wifexited = getattr(os, "WIFEXITED", lambda x: False)
709 def run(cmd, wd, options, replacements):
709 def run(cmd, wd, options, replacements):
710 """Run command in a sub-process, capturing the output (stdout and stderr).
710 """Run command in a sub-process, capturing the output (stdout and stderr).
711 Return a tuple (exitcode, output). output is None in debug mode."""
711 Return a tuple (exitcode, output). output is None in debug mode."""
712 # TODO: Use subprocess.Popen if we're running on Python 2.4
712 # TODO: Use subprocess.Popen if we're running on Python 2.4
713 if options.debug:
713 if options.debug:
714 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
714 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
715 ret = proc.wait()
715 ret = proc.wait()
716 return (ret, None)
716 return (ret, None)
717
717
718 proc = Popen4(cmd, wd, options.timeout)
718 proc = Popen4(cmd, wd, options.timeout)
719 def cleanup():
719 def cleanup():
720 terminate(proc)
720 terminate(proc)
721 ret = proc.wait()
721 ret = proc.wait()
722 if ret == 0:
722 if ret == 0:
723 ret = signal.SIGTERM << 8
723 ret = signal.SIGTERM << 8
724 killdaemons()
724 killdaemons()
725 return ret
725 return ret
726
726
727 output = ''
727 output = ''
728 proc.tochild.close()
728 proc.tochild.close()
729
729
730 try:
730 try:
731 output = proc.fromchild.read()
731 output = proc.fromchild.read()
732 except KeyboardInterrupt:
732 except KeyboardInterrupt:
733 vlog('# Handling keyboard interrupt')
733 vlog('# Handling keyboard interrupt')
734 cleanup()
734 cleanup()
735 raise
735 raise
736
736
737 ret = proc.wait()
737 ret = proc.wait()
738 if wifexited(ret):
738 if wifexited(ret):
739 ret = os.WEXITSTATUS(ret)
739 ret = os.WEXITSTATUS(ret)
740
740
741 if proc.timeout:
741 if proc.timeout:
742 ret = 'timeout'
742 ret = 'timeout'
743
743
744 if ret:
744 if ret:
745 killdaemons()
745 killdaemons()
746
746
747 for s, r in replacements:
747 for s, r in replacements:
748 output = re.sub(s, r, output)
748 output = re.sub(s, r, output)
749 return ret, splitnewlines(output)
749 return ret, splitnewlines(output)
750
750
751 def runone(options, test):
751 def runone(options, test):
752 '''tristate output:
752 '''tristate output:
753 None -> skipped
753 None -> skipped
754 True -> passed
754 True -> passed
755 False -> failed'''
755 False -> failed'''
756
756
757 global results, resultslock, iolock
757 global results, resultslock, iolock
758
758
759 testpath = os.path.join(TESTDIR, test)
759 testpath = os.path.join(TESTDIR, test)
760
760
761 def result(l, e):
761 def result(l, e):
762 resultslock.acquire()
762 resultslock.acquire()
763 results[l].append(e)
763 results[l].append(e)
764 resultslock.release()
764 resultslock.release()
765
765
766 def skip(msg):
766 def skip(msg):
767 if not options.verbose:
767 if not options.verbose:
768 result('s', (test, msg))
768 result('s', (test, msg))
769 else:
769 else:
770 iolock.acquire()
770 iolock.acquire()
771 print "\nSkipping %s: %s" % (testpath, msg)
771 print "\nSkipping %s: %s" % (testpath, msg)
772 iolock.release()
772 iolock.release()
773 return None
773 return None
774
774
775 def fail(msg, ret):
775 def fail(msg, ret):
776 if not options.nodiff:
776 if not options.nodiff:
777 iolock.acquire()
777 iolock.acquire()
778 print "\nERROR: %s %s" % (testpath, msg)
778 print "\nERROR: %s %s" % (testpath, msg)
779 iolock.release()
779 iolock.release()
780 if (not ret and options.interactive
780 if (not ret and options.interactive
781 and os.path.exists(testpath + ".err")):
781 and os.path.exists(testpath + ".err")):
782 iolock.acquire()
782 iolock.acquire()
783 print "Accept this change? [n] ",
783 print "Accept this change? [n] ",
784 answer = sys.stdin.readline().strip()
784 answer = sys.stdin.readline().strip()
785 iolock.release()
785 iolock.release()
786 if answer.lower() in "y yes".split():
786 if answer.lower() in "y yes".split():
787 if test.endswith(".t"):
787 if test.endswith(".t"):
788 rename(testpath + ".err", testpath)
788 rename(testpath + ".err", testpath)
789 else:
789 else:
790 rename(testpath + ".err", testpath + ".out")
790 rename(testpath + ".err", testpath + ".out")
791 result('p', test)
791 result('p', test)
792 return
792 return
793 result('f', (test, msg))
793 result('f', (test, msg))
794
794
795 def success():
795 def success():
796 result('p', test)
796 result('p', test)
797
797
798 def ignore(msg):
798 def ignore(msg):
799 result('i', (test, msg))
799 result('i', (test, msg))
800
800
801 if (os.path.basename(test).startswith("test-") and '~' not in test and
801 if (os.path.basename(test).startswith("test-") and '~' not in test and
802 ('.' not in test or test.endswith('.py') or
802 ('.' not in test or test.endswith('.py') or
803 test.endswith('.bat') or test.endswith('.t'))):
803 test.endswith('.bat') or test.endswith('.t'))):
804 if not os.path.exists(test):
804 if not os.path.exists(test):
805 skip("doesn't exist")
805 skip("doesn't exist")
806 return None
806 return None
807 else:
807 else:
808 vlog('# Test file', test, 'not supported, ignoring')
808 vlog('# Test file', test, 'not supported, ignoring')
809 return None # not a supported test, don't record
809 return None # not a supported test, don't record
810
810
811 if not (options.whitelisted and test in options.whitelisted):
811 if not (options.whitelisted and test in options.whitelisted):
812 if options.blacklist and test in options.blacklist:
812 if options.blacklist and test in options.blacklist:
813 skip("blacklisted")
813 skip("blacklisted")
814 return None
814 return None
815
815
816 if options.retest and not os.path.exists(test + ".err"):
816 if options.retest and not os.path.exists(test + ".err"):
817 ignore("not retesting")
817 ignore("not retesting")
818 return None
818 return None
819
819
820 if options.keywords:
820 if options.keywords:
821 fp = open(test)
821 fp = open(test)
822 t = fp.read().lower() + test.lower()
822 t = fp.read().lower() + test.lower()
823 fp.close()
823 fp.close()
824 for k in options.keywords.lower().split():
824 for k in options.keywords.lower().split():
825 if k in t:
825 if k in t:
826 break
826 break
827 else:
827 else:
828 ignore("doesn't match keyword")
828 ignore("doesn't match keyword")
829 return None
829 return None
830
830
831 vlog("# Test", test)
831 vlog("# Test", test)
832
832
833 # create a fresh hgrc
833 # create a fresh hgrc
834 hgrc = open(HGRCPATH, 'w+')
834 hgrc = open(HGRCPATH, 'w+')
835 hgrc.write('[ui]\n')
835 hgrc.write('[ui]\n')
836 hgrc.write('slash = True\n')
836 hgrc.write('slash = True\n')
837 hgrc.write('[defaults]\n')
837 hgrc.write('[defaults]\n')
838 hgrc.write('backout = -d "0 0"\n')
838 hgrc.write('backout = -d "0 0"\n')
839 hgrc.write('commit = -d "0 0"\n')
839 hgrc.write('commit = -d "0 0"\n')
840 hgrc.write('tag = -d "0 0"\n')
840 hgrc.write('tag = -d "0 0"\n')
841 if options.inotify:
841 if options.inotify:
842 hgrc.write('[extensions]\n')
842 hgrc.write('[extensions]\n')
843 hgrc.write('inotify=\n')
843 hgrc.write('inotify=\n')
844 hgrc.write('[inotify]\n')
844 hgrc.write('[inotify]\n')
845 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
845 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
846 hgrc.write('appendpid=True\n')
846 hgrc.write('appendpid=True\n')
847 if options.extra_config_opt:
847 if options.extra_config_opt:
848 for opt in options.extra_config_opt:
848 for opt in options.extra_config_opt:
849 section, key = opt.split('.', 1)
849 section, key = opt.split('.', 1)
850 assert '=' in key, ('extra config opt %s must '
850 assert '=' in key, ('extra config opt %s must '
851 'have an = for assignment' % opt)
851 'have an = for assignment' % opt)
852 hgrc.write('[%s]\n%s\n' % (section, key))
852 hgrc.write('[%s]\n%s\n' % (section, key))
853 hgrc.close()
853 hgrc.close()
854
854
855 ref = os.path.join(TESTDIR, test+".out")
855 ref = os.path.join(TESTDIR, test+".out")
856 err = os.path.join(TESTDIR, test+".err")
856 err = os.path.join(TESTDIR, test+".err")
857 if os.path.exists(err):
857 if os.path.exists(err):
858 os.remove(err) # Remove any previous output files
858 os.remove(err) # Remove any previous output files
859 try:
859 try:
860 tf = open(testpath)
860 tf = open(testpath)
861 firstline = tf.readline().rstrip()
861 firstline = tf.readline().rstrip()
862 tf.close()
862 tf.close()
863 except:
863 except:
864 firstline = ''
864 firstline = ''
865 lctest = test.lower()
865 lctest = test.lower()
866
866
867 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
867 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
868 runner = pytest
868 runner = pytest
869 elif lctest.endswith('.t'):
869 elif lctest.endswith('.t'):
870 runner = tsttest
870 runner = tsttest
871 ref = testpath
871 ref = testpath
872 else:
872 else:
873 # do not try to run non-executable programs
873 # do not try to run non-executable programs
874 if not os.access(testpath, os.X_OK):
874 if not os.access(testpath, os.X_OK):
875 return skip("not executable")
875 return skip("not executable")
876 runner = shtest
876 runner = shtest
877
877
878 # Make a tmp subdirectory to work in
878 # Make a tmp subdirectory to work in
879 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
879 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
880 os.path.join(HGTMP, os.path.basename(test)).replace('\\', '/')
880 os.path.join(HGTMP, os.path.basename(test)).replace('\\', '/')
881
881
882 replacements = [
882 replacements = [
883 (r':%s\b' % options.port, ':$HGPORT'),
883 (r':%s\b' % options.port, ':$HGPORT'),
884 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
884 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
885 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
885 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
886 ]
886 ]
887 if os.name == 'nt':
887 if os.name == 'nt':
888 replacements.append((r'\r\n', '\n'))
888 replacements.append((r'\r\n', '\n'))
889 replacements.append(
889 replacements.append(
890 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
890 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
891 c in '/\\' and r'[/\\]' or
891 c in '/\\' and r'[/\\]' or
892 c.isdigit() and c or
892 c.isdigit() and c or
893 '\\' + c
893 '\\' + c
894 for c in testtmp), '$TESTTMP'))
894 for c in testtmp), '$TESTTMP'))
895 else:
895 else:
896 replacements.append((re.escape(testtmp), '$TESTTMP'))
896 replacements.append((re.escape(testtmp), '$TESTTMP'))
897
897
898 os.mkdir(testtmp)
898 os.mkdir(testtmp)
899 ret, out = runner(testpath, testtmp, options, replacements)
899 ret, out = runner(testpath, testtmp, options, replacements)
900 vlog("# Ret was:", ret)
900 vlog("# Ret was:", ret)
901
901
902 mark = '.'
902 mark = '.'
903
903
904 skipped = (ret == SKIPPED_STATUS)
904 skipped = (ret == SKIPPED_STATUS)
905
905
906 # If we're not in --debug mode and reference output file exists,
906 # If we're not in --debug mode and reference output file exists,
907 # check test output against it.
907 # check test output against it.
908 if options.debug:
908 if options.debug:
909 refout = None # to match "out is None"
909 refout = None # to match "out is None"
910 elif os.path.exists(ref):
910 elif os.path.exists(ref):
911 f = open(ref, "r")
911 f = open(ref, "r")
912 refout = list(splitnewlines(f.read()))
912 refout = list(splitnewlines(f.read()))
913 f.close()
913 f.close()
914 else:
914 else:
915 refout = []
915 refout = []
916
916
917 if (ret != 0 or out != refout) and not skipped and not options.debug:
917 if (ret != 0 or out != refout) and not skipped and not options.debug:
918 # Save errors to a file for diagnosis
918 # Save errors to a file for diagnosis
919 f = open(err, "wb")
919 f = open(err, "wb")
920 for line in out:
920 for line in out:
921 f.write(line)
921 f.write(line)
922 f.close()
922 f.close()
923
923
924 if skipped:
924 if skipped:
925 mark = 's'
925 mark = 's'
926 if out is None: # debug mode: nothing to parse
926 if out is None: # debug mode: nothing to parse
927 missing = ['unknown']
927 missing = ['unknown']
928 failed = None
928 failed = None
929 else:
929 else:
930 missing, failed = parsehghaveoutput(out)
930 missing, failed = parsehghaveoutput(out)
931 if not missing:
931 if not missing:
932 missing = ['irrelevant']
932 missing = ['irrelevant']
933 if failed:
933 if failed:
934 fail("hghave failed checking for %s" % failed[-1], ret)
934 fail("hghave failed checking for %s" % failed[-1], ret)
935 skipped = False
935 skipped = False
936 else:
936 else:
937 skip(missing[-1])
937 skip(missing[-1])
938 elif ret == 'timeout':
938 elif ret == 'timeout':
939 mark = 't'
939 mark = 't'
940 fail("timed out", ret)
940 fail("timed out", ret)
941 elif out != refout:
941 elif out != refout:
942 mark = '!'
942 mark = '!'
943 if not options.nodiff:
943 if not options.nodiff:
944 iolock.acquire()
944 iolock.acquire()
945 if options.view:
945 if options.view:
946 os.system("%s %s %s" % (options.view, ref, err))
946 os.system("%s %s %s" % (options.view, ref, err))
947 else:
947 else:
948 showdiff(refout, out, ref, err)
948 showdiff(refout, out, ref, err)
949 iolock.release()
949 iolock.release()
950 if ret:
950 if ret:
951 fail("output changed and returned error code %d" % ret, ret)
951 fail("output changed and returned error code %d" % ret, ret)
952 else:
952 else:
953 fail("output changed", ret)
953 fail("output changed", ret)
954 ret = 1
954 ret = 1
955 elif ret:
955 elif ret:
956 mark = '!'
956 mark = '!'
957 fail("returned error code %d" % ret, ret)
957 fail("returned error code %d" % ret, ret)
958 else:
958 else:
959 success()
959 success()
960
960
961 if not options.verbose:
961 if not options.verbose:
962 iolock.acquire()
962 iolock.acquire()
963 sys.stdout.write(mark)
963 sys.stdout.write(mark)
964 sys.stdout.flush()
964 sys.stdout.flush()
965 iolock.release()
965 iolock.release()
966
966
967 killdaemons()
967 killdaemons()
968
968
969 if not options.keep_tmpdir:
969 if not options.keep_tmpdir:
970 shutil.rmtree(testtmp, True)
970 shutil.rmtree(testtmp, True)
971 if skipped:
971 if skipped:
972 return None
972 return None
973 return ret == 0
973 return ret == 0
974
974
975 _hgpath = None
975 _hgpath = None
976
976
977 def _gethgpath():
977 def _gethgpath():
978 """Return the path to the mercurial package that is actually found by
978 """Return the path to the mercurial package that is actually found by
979 the current Python interpreter."""
979 the current Python interpreter."""
980 global _hgpath
980 global _hgpath
981 if _hgpath is not None:
981 if _hgpath is not None:
982 return _hgpath
982 return _hgpath
983
983
984 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
984 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
985 pipe = os.popen(cmd % PYTHON)
985 pipe = os.popen(cmd % PYTHON)
986 try:
986 try:
987 _hgpath = pipe.read().strip()
987 _hgpath = pipe.read().strip()
988 finally:
988 finally:
989 pipe.close()
989 pipe.close()
990 return _hgpath
990 return _hgpath
991
991
992 def _checkhglib(verb):
992 def _checkhglib(verb):
993 """Ensure that the 'mercurial' package imported by python is
993 """Ensure that the 'mercurial' package imported by python is
994 the one we expect it to be. If not, print a warning to stderr."""
994 the one we expect it to be. If not, print a warning to stderr."""
995 expecthg = os.path.join(PYTHONDIR, 'mercurial')
995 expecthg = os.path.join(PYTHONDIR, 'mercurial')
996 actualhg = _gethgpath()
996 actualhg = _gethgpath()
997 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
997 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
998 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
998 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
999 ' (expected %s)\n'
999 ' (expected %s)\n'
1000 % (verb, actualhg, expecthg))
1000 % (verb, actualhg, expecthg))
1001
1001
1002 def runchildren(options, tests):
1002 def runchildren(options, tests):
1003 if INST:
1003 if INST:
1004 installhg(options)
1004 installhg(options)
1005 _checkhglib("Testing")
1005 _checkhglib("Testing")
1006
1006
1007 optcopy = dict(options.__dict__)
1007 optcopy = dict(options.__dict__)
1008 optcopy['jobs'] = 1
1008 optcopy['jobs'] = 1
1009
1009
1010 # Because whitelist has to override keyword matches, we have to
1010 # Because whitelist has to override keyword matches, we have to
1011 # actually load the whitelist in the children as well, so we allow
1011 # actually load the whitelist in the children as well, so we allow
1012 # the list of whitelist files to pass through and be parsed in the
1012 # the list of whitelist files to pass through and be parsed in the
1013 # children, but not the dict of whitelisted tests resulting from
1013 # children, but not the dict of whitelisted tests resulting from
1014 # the parse, used here to override blacklisted tests.
1014 # the parse, used here to override blacklisted tests.
1015 whitelist = optcopy['whitelisted'] or []
1015 whitelist = optcopy['whitelisted'] or []
1016 del optcopy['whitelisted']
1016 del optcopy['whitelisted']
1017
1017
1018 blacklist = optcopy['blacklist'] or []
1018 blacklist = optcopy['blacklist'] or []
1019 del optcopy['blacklist']
1019 del optcopy['blacklist']
1020 blacklisted = []
1020 blacklisted = []
1021
1021
1022 if optcopy['with_hg'] is None:
1022 if optcopy['with_hg'] is None:
1023 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1023 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1024 optcopy.pop('anycoverage', None)
1024 optcopy.pop('anycoverage', None)
1025
1025
1026 opts = []
1026 opts = []
1027 for opt, value in optcopy.iteritems():
1027 for opt, value in optcopy.iteritems():
1028 name = '--' + opt.replace('_', '-')
1028 name = '--' + opt.replace('_', '-')
1029 if value is True:
1029 if value is True:
1030 opts.append(name)
1030 opts.append(name)
1031 elif isinstance(value, list):
1031 elif isinstance(value, list):
1032 for v in value:
1032 for v in value:
1033 opts.append(name + '=' + str(v))
1033 opts.append(name + '=' + str(v))
1034 elif value is not None:
1034 elif value is not None:
1035 opts.append(name + '=' + str(value))
1035 opts.append(name + '=' + str(value))
1036
1036
1037 tests.reverse()
1037 tests.reverse()
1038 jobs = [[] for j in xrange(options.jobs)]
1038 jobs = [[] for j in xrange(options.jobs)]
1039 while tests:
1039 while tests:
1040 for job in jobs:
1040 for job in jobs:
1041 if not tests:
1041 if not tests:
1042 break
1042 break
1043 test = tests.pop()
1043 test = tests.pop()
1044 if test not in whitelist and test in blacklist:
1044 if test not in whitelist and test in blacklist:
1045 blacklisted.append(test)
1045 blacklisted.append(test)
1046 else:
1046 else:
1047 job.append(test)
1047 job.append(test)
1048 fps = {}
1048 fps = {}
1049
1049
1050 for j, job in enumerate(jobs):
1050 for j, job in enumerate(jobs):
1051 if not job:
1051 if not job:
1052 continue
1052 continue
1053 rfd, wfd = os.pipe()
1053 rfd, wfd = os.pipe()
1054 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1054 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1055 childtmp = os.path.join(HGTMP, 'child%d' % j)
1055 childtmp = os.path.join(HGTMP, 'child%d' % j)
1056 childopts += ['--tmpdir', childtmp]
1056 childopts += ['--tmpdir', childtmp]
1057 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1057 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1058 vlog(' '.join(cmdline))
1058 vlog(' '.join(cmdline))
1059 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1059 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1060 os.close(wfd)
1060 os.close(wfd)
1061 signal.signal(signal.SIGINT, signal.SIG_IGN)
1061 signal.signal(signal.SIGINT, signal.SIG_IGN)
1062 failures = 0
1062 failures = 0
1063 tested, skipped, failed = 0, 0, 0
1063 tested, skipped, failed = 0, 0, 0
1064 skips = []
1064 skips = []
1065 fails = []
1065 fails = []
1066 while fps:
1066 while fps:
1067 pid, status = os.wait()
1067 pid, status = os.wait()
1068 fp = fps.pop(pid)
1068 fp = fps.pop(pid)
1069 l = fp.read().splitlines()
1069 l = fp.read().splitlines()
1070 try:
1070 try:
1071 test, skip, fail = map(int, l[:3])
1071 test, skip, fail = map(int, l[:3])
1072 except ValueError:
1072 except ValueError:
1073 test, skip, fail = 0, 0, 0
1073 test, skip, fail = 0, 0, 0
1074 split = -fail or len(l)
1074 split = -fail or len(l)
1075 for s in l[3:split]:
1075 for s in l[3:split]:
1076 skips.append(s.split(" ", 1))
1076 skips.append(s.split(" ", 1))
1077 for s in l[split:]:
1077 for s in l[split:]:
1078 fails.append(s.split(" ", 1))
1078 fails.append(s.split(" ", 1))
1079 tested += test
1079 tested += test
1080 skipped += skip
1080 skipped += skip
1081 failed += fail
1081 failed += fail
1082 vlog('pid %d exited, status %d' % (pid, status))
1082 vlog('pid %d exited, status %d' % (pid, status))
1083 failures |= status
1083 failures |= status
1084 print
1084 print
1085 skipped += len(blacklisted)
1085 skipped += len(blacklisted)
1086 if not options.noskips:
1086 if not options.noskips:
1087 for s in skips:
1087 for s in skips:
1088 print "Skipped %s: %s" % (s[0], s[1])
1088 print "Skipped %s: %s" % (s[0], s[1])
1089 for s in blacklisted:
1089 for s in blacklisted:
1090 print "Skipped %s: blacklisted" % s
1090 print "Skipped %s: blacklisted" % s
1091 for s in fails:
1091 for s in fails:
1092 print "Failed %s: %s" % (s[0], s[1])
1092 print "Failed %s: %s" % (s[0], s[1])
1093
1093
1094 _checkhglib("Tested")
1094 _checkhglib("Tested")
1095 print "# Ran %d tests, %d skipped, %d failed." % (
1095 print "# Ran %d tests, %d skipped, %d failed." % (
1096 tested, skipped, failed)
1096 tested, skipped, failed)
1097
1097
1098 if options.anycoverage:
1098 if options.anycoverage:
1099 outputcoverage(options)
1099 outputcoverage(options)
1100 sys.exit(failures != 0)
1100 sys.exit(failures != 0)
1101
1101
1102 results = dict(p=[], f=[], s=[], i=[])
1102 results = dict(p=[], f=[], s=[], i=[])
1103 resultslock = threading.Lock()
1103 resultslock = threading.Lock()
1104 iolock = threading.Lock()
1104 iolock = threading.Lock()
1105
1105
1106 def runqueue(options, tests, results):
1106 def runqueue(options, tests, results):
1107 for test in tests:
1107 for test in tests:
1108 ret = runone(options, test)
1108 ret = runone(options, test)
1109 if options.first and ret is not None and not ret:
1109 if options.first and ret is not None and not ret:
1110 break
1110 break
1111
1111
1112 def runtests(options, tests):
1112 def runtests(options, tests):
1113 global DAEMON_PIDS, HGRCPATH
1113 global DAEMON_PIDS, HGRCPATH
1114 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1114 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1115 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1115 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1116
1116
1117 try:
1117 try:
1118 if INST:
1118 if INST:
1119 installhg(options)
1119 installhg(options)
1120 _checkhglib("Testing")
1120 _checkhglib("Testing")
1121
1121
1122 if options.restart:
1122 if options.restart:
1123 orig = list(tests)
1123 orig = list(tests)
1124 while tests:
1124 while tests:
1125 if os.path.exists(tests[0] + ".err"):
1125 if os.path.exists(tests[0] + ".err"):
1126 break
1126 break
1127 tests.pop(0)
1127 tests.pop(0)
1128 if not tests:
1128 if not tests:
1129 print "running all tests"
1129 print "running all tests"
1130 tests = orig
1130 tests = orig
1131
1131
1132 runqueue(options, tests, results)
1132 runqueue(options, tests, results)
1133
1133
1134 failed = len(results['f'])
1134 failed = len(results['f'])
1135 tested = len(results['p']) + failed
1135 tested = len(results['p']) + failed
1136 skipped = len(results['s'])
1136 skipped = len(results['s'])
1137 ignored = len(results['i'])
1137 ignored = len(results['i'])
1138
1138
1139 if options.child:
1139 if options.child:
1140 fp = os.fdopen(options.child, 'w')
1140 fp = os.fdopen(options.child, 'w')
1141 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1141 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1142 for s in results['s']:
1142 for s in results['s']:
1143 fp.write("%s %s\n" % s)
1143 fp.write("%s %s\n" % s)
1144 for s in results['f']:
1144 for s in results['f']:
1145 fp.write("%s %s\n" % s)
1145 fp.write("%s %s\n" % s)
1146 fp.close()
1146 fp.close()
1147 else:
1147 else:
1148 print
1148 print
1149 for s in results['s']:
1149 for s in results['s']:
1150 print "Skipped %s: %s" % s
1150 print "Skipped %s: %s" % s
1151 for s in results['f']:
1151 for s in results['f']:
1152 print "Failed %s: %s" % s
1152 print "Failed %s: %s" % s
1153 _checkhglib("Tested")
1153 _checkhglib("Tested")
1154 print "# Ran %d tests, %d skipped, %d failed." % (
1154 print "# Ran %d tests, %d skipped, %d failed." % (
1155 tested, skipped + ignored, failed)
1155 tested, skipped + ignored, failed)
1156
1156
1157 if options.anycoverage:
1157 if options.anycoverage:
1158 outputcoverage(options)
1158 outputcoverage(options)
1159 except KeyboardInterrupt:
1159 except KeyboardInterrupt:
1160 failed = True
1160 failed = True
1161 print "\ninterrupted!"
1161 print "\ninterrupted!"
1162
1162
1163 if failed:
1163 if failed:
1164 sys.exit(1)
1164 sys.exit(1)
1165
1165
1166 def main():
1166 def main():
1167 (options, args) = parseargs()
1167 (options, args) = parseargs()
1168 if not options.child:
1168 if not options.child:
1169 os.umask(022)
1169 os.umask(022)
1170
1170
1171 checktools()
1171 checktools()
1172
1172
1173 if len(args) == 0:
1173 if len(args) == 0:
1174 args = os.listdir(".")
1174 args = os.listdir(".")
1175 args.sort()
1175 args.sort()
1176
1176
1177 tests = args
1177 tests = args
1178
1178
1179 # Reset some environment variables to well-known values so that
1179 # Reset some environment variables to well-known values so that
1180 # the tests produce repeatable output.
1180 # the tests produce repeatable output.
1181 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1181 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1182 os.environ['TZ'] = 'GMT'
1182 os.environ['TZ'] = 'GMT'
1183 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1183 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1184 os.environ['CDPATH'] = ''
1184 os.environ['CDPATH'] = ''
1185 os.environ['COLUMNS'] = '80'
1185 os.environ['COLUMNS'] = '80'
1186 os.environ['GREP_OPTIONS'] = ''
1186 os.environ['GREP_OPTIONS'] = ''
1187 os.environ['http_proxy'] = ''
1187 os.environ['http_proxy'] = ''
1188 os.environ['no_proxy'] = ''
1188 os.environ['no_proxy'] = ''
1189 os.environ['NO_PROXY'] = ''
1189 os.environ['NO_PROXY'] = ''
1190
1190
1191 # unset env related to hooks
1191 # unset env related to hooks
1192 for k in os.environ.keys():
1192 for k in os.environ.keys():
1193 if k.startswith('HG_'):
1193 if k.startswith('HG_'):
1194 # can't remove on solaris
1194 # can't remove on solaris
1195 os.environ[k] = ''
1195 os.environ[k] = ''
1196 del os.environ[k]
1196 del os.environ[k]
1197
1197
1198 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1198 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1199 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1199 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1200 if options.tmpdir:
1200 if options.tmpdir:
1201 options.keep_tmpdir = True
1201 options.keep_tmpdir = True
1202 tmpdir = options.tmpdir
1202 tmpdir = options.tmpdir
1203 if os.path.exists(tmpdir):
1203 if os.path.exists(tmpdir):
1204 # Meaning of tmpdir has changed since 1.3: we used to create
1204 # Meaning of tmpdir has changed since 1.3: we used to create
1205 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1205 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1206 # tmpdir already exists.
1206 # tmpdir already exists.
1207 sys.exit("error: temp dir %r already exists" % tmpdir)
1207 sys.exit("error: temp dir %r already exists" % tmpdir)
1208
1208
1209 # Automatically removing tmpdir sounds convenient, but could
1209 # Automatically removing tmpdir sounds convenient, but could
1210 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1210 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1211 # or "--tmpdir=$HOME".
1211 # or "--tmpdir=$HOME".
1212 #vlog("# Removing temp dir", tmpdir)
1212 #vlog("# Removing temp dir", tmpdir)
1213 #shutil.rmtree(tmpdir)
1213 #shutil.rmtree(tmpdir)
1214 os.makedirs(tmpdir)
1214 os.makedirs(tmpdir)
1215 else:
1215 else:
1216 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1216 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1217 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1217 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1218 DAEMON_PIDS = None
1218 DAEMON_PIDS = None
1219 HGRCPATH = None
1219 HGRCPATH = None
1220
1220
1221 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1221 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1222 os.environ["HGMERGE"] = "internal:merge"
1222 os.environ["HGMERGE"] = "internal:merge"
1223 os.environ["HGUSER"] = "test"
1223 os.environ["HGUSER"] = "test"
1224 os.environ["HGENCODING"] = "ascii"
1224 os.environ["HGENCODING"] = "ascii"
1225 os.environ["HGENCODINGMODE"] = "strict"
1225 os.environ["HGENCODINGMODE"] = "strict"
1226 os.environ["HGPORT"] = str(options.port)
1226 os.environ["HGPORT"] = str(options.port)
1227 os.environ["HGPORT1"] = str(options.port + 1)
1227 os.environ["HGPORT1"] = str(options.port + 1)
1228 os.environ["HGPORT2"] = str(options.port + 2)
1228 os.environ["HGPORT2"] = str(options.port + 2)
1229
1229
1230 if options.with_hg:
1230 if options.with_hg:
1231 INST = None
1231 INST = None
1232 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1232 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1233
1233
1234 # This looks redundant with how Python initializes sys.path from
1234 # This looks redundant with how Python initializes sys.path from
1235 # the location of the script being executed. Needed because the
1235 # the location of the script being executed. Needed because the
1236 # "hg" specified by --with-hg is not the only Python script
1236 # "hg" specified by --with-hg is not the only Python script
1237 # executed in the test suite that needs to import 'mercurial'
1237 # executed in the test suite that needs to import 'mercurial'
1238 # ... which means it's not really redundant at all.
1238 # ... which means it's not really redundant at all.
1239 PYTHONDIR = BINDIR
1239 PYTHONDIR = BINDIR
1240 else:
1240 else:
1241 INST = os.path.join(HGTMP, "install")
1241 INST = os.path.join(HGTMP, "install")
1242 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1242 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1243 PYTHONDIR = os.path.join(INST, "lib", "python")
1243 PYTHONDIR = os.path.join(INST, "lib", "python")
1244
1244
1245 os.environ["BINDIR"] = BINDIR
1245 os.environ["BINDIR"] = BINDIR
1246 os.environ["PYTHON"] = PYTHON
1246 os.environ["PYTHON"] = PYTHON
1247
1247
1248 if not options.child:
1248 if not options.child:
1249 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1249 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1250 os.environ["PATH"] = os.pathsep.join(path)
1250 os.environ["PATH"] = os.pathsep.join(path)
1251
1251
1252 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1252 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1253 # can run .../tests/run-tests.py test-foo where test-foo
1253 # can run .../tests/run-tests.py test-foo where test-foo
1254 # adds an extension to HGRC
1254 # adds an extension to HGRC
1255 pypath = [PYTHONDIR, TESTDIR]
1255 pypath = [PYTHONDIR, TESTDIR]
1256 # We have to augment PYTHONPATH, rather than simply replacing
1256 # We have to augment PYTHONPATH, rather than simply replacing
1257 # it, in case external libraries are only available via current
1257 # it, in case external libraries are only available via current
1258 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1258 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1259 # are in /opt/subversion.)
1259 # are in /opt/subversion.)
1260 oldpypath = os.environ.get(IMPL_PATH)
1260 oldpypath = os.environ.get(IMPL_PATH)
1261 if oldpypath:
1261 if oldpypath:
1262 pypath.append(oldpypath)
1262 pypath.append(oldpypath)
1263 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1263 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1264
1264
1265 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1265 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1266
1266
1267 vlog("# Using TESTDIR", TESTDIR)
1267 vlog("# Using TESTDIR", TESTDIR)
1268 vlog("# Using HGTMP", HGTMP)
1268 vlog("# Using HGTMP", HGTMP)
1269 vlog("# Using PATH", os.environ["PATH"])
1269 vlog("# Using PATH", os.environ["PATH"])
1270 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1270 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1271
1271
1272 try:
1272 try:
1273 if len(tests) > 1 and options.jobs > 1:
1273 if len(tests) > 1 and options.jobs > 1:
1274 runchildren(options, tests)
1274 runchildren(options, tests)
1275 else:
1275 else:
1276 runtests(options, tests)
1276 runtests(options, tests)
1277 finally:
1277 finally:
1278 time.sleep(.1)
1278 time.sleep(.1)
1279 cleanup(options)
1279 cleanup(options)
1280
1280
1281 if __name__ == '__main__':
1281 if __name__ == '__main__':
1282 main()
1282 main()
General Comments 0
You need to be logged in to leave comments. Login now