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