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