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