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