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