##// END OF EJS Templates
tests: simplify handling of unknown test types
Matt Mackall -
r19241:a32986de default
parent child Browse files
Show More
@@ -1,1378 +1,1372 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
853 ('.' not in test or test.endswith('.py') or
854 test.endswith('.bat') or test.endswith('.t'))):
855 if not os.path.exists(test):
852 if not os.path.exists(test):
856 skip("doesn't exist")
853 skip("doesn't exist")
857 return None
854 return None
858 else:
859 vlog('# Test file', test, 'not supported, ignoring')
860 return None # not a supported test, don't record
861
855
862 if not (options.whitelisted and test in options.whitelisted):
856 if not (options.whitelisted and test in options.whitelisted):
863 if options.blacklist and test in options.blacklist:
857 if options.blacklist and test in options.blacklist:
864 skip("blacklisted")
858 skip("blacklisted")
865 return None
859 return None
866
860
867 if options.retest and not os.path.exists(test + ".err"):
861 if options.retest and not os.path.exists(test + ".err"):
868 ignore("not retesting")
862 ignore("not retesting")
869 return None
863 return None
870
864
871 if options.keywords:
865 if options.keywords:
872 fp = open(test)
866 fp = open(test)
873 t = fp.read().lower() + test.lower()
867 t = fp.read().lower() + test.lower()
874 fp.close()
868 fp.close()
875 for k in options.keywords.lower().split():
869 for k in options.keywords.lower().split():
876 if k in t:
870 if k in t:
877 break
871 break
878 else:
872 else:
879 ignore("doesn't match keyword")
873 ignore("doesn't match keyword")
880 return None
874 return None
881
875
882 vlog("# Test", test)
876 vlog("# Test", test)
883
877
884 # create a fresh hgrc
878 # create a fresh hgrc
885 hgrc = open(HGRCPATH, 'w+')
879 hgrc = open(HGRCPATH, 'w+')
886 hgrc.write('[ui]\n')
880 hgrc.write('[ui]\n')
887 hgrc.write('slash = True\n')
881 hgrc.write('slash = True\n')
888 hgrc.write('interactive = False\n')
882 hgrc.write('interactive = False\n')
889 hgrc.write('[defaults]\n')
883 hgrc.write('[defaults]\n')
890 hgrc.write('backout = -d "0 0"\n')
884 hgrc.write('backout = -d "0 0"\n')
891 hgrc.write('commit = -d "0 0"\n')
885 hgrc.write('commit = -d "0 0"\n')
892 hgrc.write('tag = -d "0 0"\n')
886 hgrc.write('tag = -d "0 0"\n')
893 if options.inotify:
887 if options.inotify:
894 hgrc.write('[extensions]\n')
888 hgrc.write('[extensions]\n')
895 hgrc.write('inotify=\n')
889 hgrc.write('inotify=\n')
896 hgrc.write('[inotify]\n')
890 hgrc.write('[inotify]\n')
897 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
891 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
898 hgrc.write('appendpid=True\n')
892 hgrc.write('appendpid=True\n')
899 if options.extra_config_opt:
893 if options.extra_config_opt:
900 for opt in options.extra_config_opt:
894 for opt in options.extra_config_opt:
901 section, key = opt.split('.', 1)
895 section, key = opt.split('.', 1)
902 assert '=' in key, ('extra config opt %s must '
896 assert '=' in key, ('extra config opt %s must '
903 'have an = for assignment' % opt)
897 'have an = for assignment' % opt)
904 hgrc.write('[%s]\n%s\n' % (section, key))
898 hgrc.write('[%s]\n%s\n' % (section, key))
905 hgrc.close()
899 hgrc.close()
906
900
907 err = os.path.join(TESTDIR, test+".err")
901 err = os.path.join(TESTDIR, test+".err")
908 if os.path.exists(err):
902 if os.path.exists(err):
909 os.remove(err) # Remove any previous output files
903 os.remove(err) # Remove any previous output files
910 lctest = test.lower()
904 lctest = test.lower()
911
905
912 for ext, func, out in testtypes:
906 for ext, func, out in testtypes:
913 if lctest.endswith(ext):
907 if lctest.startswith("test-") and lctest.endswith(ext):
914 runner = func
908 runner = func
915 ref = os.path.join(TESTDIR, test + out)
909 ref = os.path.join(TESTDIR, test + out)
916 break
910 break
917 else:
911 else:
918 return skip("unknown test type")
912 return skip("unknown test type")
919
913
920 # Make a tmp subdirectory to work in
914 # Make a tmp subdirectory to work in
921 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
915 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
922 os.path.join(HGTMP, os.path.basename(test))
916 os.path.join(HGTMP, os.path.basename(test))
923
917
924 replacements = [
918 replacements = [
925 (r':%s\b' % options.port, ':$HGPORT'),
919 (r':%s\b' % options.port, ':$HGPORT'),
926 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
920 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
927 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
921 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
928 ]
922 ]
929 if os.name == 'nt':
923 if os.name == 'nt':
930 replacements.append(
924 replacements.append(
931 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
925 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
932 c in '/\\' and r'[/\\]' or
926 c in '/\\' and r'[/\\]' or
933 c.isdigit() and c or
927 c.isdigit() and c or
934 '\\' + c
928 '\\' + c
935 for c in testtmp), '$TESTTMP'))
929 for c in testtmp), '$TESTTMP'))
936 else:
930 else:
937 replacements.append((re.escape(testtmp), '$TESTTMP'))
931 replacements.append((re.escape(testtmp), '$TESTTMP'))
938
932
939 os.mkdir(testtmp)
933 os.mkdir(testtmp)
940 if options.time:
934 if options.time:
941 starttime = time.time()
935 starttime = time.time()
942 ret, out = runner(testpath, testtmp, options, replacements)
936 ret, out = runner(testpath, testtmp, options, replacements)
943 if options.time:
937 if options.time:
944 endtime = time.time()
938 endtime = time.time()
945 times.append((test, endtime - starttime))
939 times.append((test, endtime - starttime))
946 vlog("# Ret was:", ret)
940 vlog("# Ret was:", ret)
947
941
948 killdaemons()
942 killdaemons()
949
943
950 mark = '.'
944 mark = '.'
951
945
952 skipped = (ret == SKIPPED_STATUS)
946 skipped = (ret == SKIPPED_STATUS)
953
947
954 # If we're not in --debug mode and reference output file exists,
948 # If we're not in --debug mode and reference output file exists,
955 # check test output against it.
949 # check test output against it.
956 if options.debug:
950 if options.debug:
957 refout = None # to match "out is None"
951 refout = None # to match "out is None"
958 elif os.path.exists(ref):
952 elif os.path.exists(ref):
959 f = open(ref, "r")
953 f = open(ref, "r")
960 refout = f.read().splitlines(True)
954 refout = f.read().splitlines(True)
961 f.close()
955 f.close()
962 else:
956 else:
963 refout = []
957 refout = []
964
958
965 if (ret != 0 or out != refout) and not skipped and not options.debug:
959 if (ret != 0 or out != refout) and not skipped and not options.debug:
966 # Save errors to a file for diagnosis
960 # Save errors to a file for diagnosis
967 f = open(err, "wb")
961 f = open(err, "wb")
968 for line in out:
962 for line in out:
969 f.write(line)
963 f.write(line)
970 f.close()
964 f.close()
971
965
972 def describe(ret):
966 def describe(ret):
973 if ret < 0:
967 if ret < 0:
974 return 'killed by signal %d' % -ret
968 return 'killed by signal %d' % -ret
975 return 'returned error code %d' % ret
969 return 'returned error code %d' % ret
976
970
977 if skipped:
971 if skipped:
978 mark = 's'
972 mark = 's'
979 if out is None: # debug mode: nothing to parse
973 if out is None: # debug mode: nothing to parse
980 missing = ['unknown']
974 missing = ['unknown']
981 failed = None
975 failed = None
982 else:
976 else:
983 missing, failed = parsehghaveoutput(out)
977 missing, failed = parsehghaveoutput(out)
984 if not missing:
978 if not missing:
985 missing = ['irrelevant']
979 missing = ['irrelevant']
986 if failed:
980 if failed:
987 fail("hghave failed checking for %s" % failed[-1], ret)
981 fail("hghave failed checking for %s" % failed[-1], ret)
988 skipped = False
982 skipped = False
989 else:
983 else:
990 skip(missing[-1])
984 skip(missing[-1])
991 elif ret == 'timeout':
985 elif ret == 'timeout':
992 mark = 't'
986 mark = 't'
993 fail("timed out", ret)
987 fail("timed out", ret)
994 elif out != refout:
988 elif out != refout:
995 mark = '!'
989 mark = '!'
996 if not options.nodiff:
990 if not options.nodiff:
997 iolock.acquire()
991 iolock.acquire()
998 if options.view:
992 if options.view:
999 os.system("%s %s %s" % (options.view, ref, err))
993 os.system("%s %s %s" % (options.view, ref, err))
1000 else:
994 else:
1001 showdiff(refout, out, ref, err)
995 showdiff(refout, out, ref, err)
1002 iolock.release()
996 iolock.release()
1003 if ret:
997 if ret:
1004 fail("output changed and " + describe(ret), ret)
998 fail("output changed and " + describe(ret), ret)
1005 else:
999 else:
1006 fail("output changed", ret)
1000 fail("output changed", ret)
1007 ret = 1
1001 ret = 1
1008 elif ret:
1002 elif ret:
1009 mark = '!'
1003 mark = '!'
1010 fail(describe(ret), ret)
1004 fail(describe(ret), ret)
1011 else:
1005 else:
1012 success()
1006 success()
1013
1007
1014 if not options.verbose:
1008 if not options.verbose:
1015 iolock.acquire()
1009 iolock.acquire()
1016 sys.stdout.write(mark)
1010 sys.stdout.write(mark)
1017 sys.stdout.flush()
1011 sys.stdout.flush()
1018 iolock.release()
1012 iolock.release()
1019
1013
1020 if not options.keep_tmpdir:
1014 if not options.keep_tmpdir:
1021 shutil.rmtree(testtmp, True)
1015 shutil.rmtree(testtmp, True)
1022 if skipped:
1016 if skipped:
1023 return None
1017 return None
1024 return ret == 0
1018 return ret == 0
1025
1019
1026 _hgpath = None
1020 _hgpath = None
1027
1021
1028 def _gethgpath():
1022 def _gethgpath():
1029 """Return the path to the mercurial package that is actually found by
1023 """Return the path to the mercurial package that is actually found by
1030 the current Python interpreter."""
1024 the current Python interpreter."""
1031 global _hgpath
1025 global _hgpath
1032 if _hgpath is not None:
1026 if _hgpath is not None:
1033 return _hgpath
1027 return _hgpath
1034
1028
1035 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1029 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1036 pipe = os.popen(cmd % PYTHON)
1030 pipe = os.popen(cmd % PYTHON)
1037 try:
1031 try:
1038 _hgpath = pipe.read().strip()
1032 _hgpath = pipe.read().strip()
1039 finally:
1033 finally:
1040 pipe.close()
1034 pipe.close()
1041 return _hgpath
1035 return _hgpath
1042
1036
1043 def _checkhglib(verb):
1037 def _checkhglib(verb):
1044 """Ensure that the 'mercurial' package imported by python is
1038 """Ensure that the 'mercurial' package imported by python is
1045 the one we expect it to be. If not, print a warning to stderr."""
1039 the one we expect it to be. If not, print a warning to stderr."""
1046 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1040 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1047 actualhg = _gethgpath()
1041 actualhg = _gethgpath()
1048 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1042 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1049 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1043 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1050 ' (expected %s)\n'
1044 ' (expected %s)\n'
1051 % (verb, actualhg, expecthg))
1045 % (verb, actualhg, expecthg))
1052
1046
1053 def runchildren(options, tests):
1047 def runchildren(options, tests):
1054 if INST:
1048 if INST:
1055 installhg(options)
1049 installhg(options)
1056 _checkhglib("Testing")
1050 _checkhglib("Testing")
1057 else:
1051 else:
1058 usecorrectpython()
1052 usecorrectpython()
1059
1053
1060 optcopy = dict(options.__dict__)
1054 optcopy = dict(options.__dict__)
1061 optcopy['jobs'] = 1
1055 optcopy['jobs'] = 1
1062
1056
1063 # Because whitelist has to override keyword matches, we have to
1057 # Because whitelist has to override keyword matches, we have to
1064 # actually load the whitelist in the children as well, so we allow
1058 # actually load the whitelist in the children as well, so we allow
1065 # the list of whitelist files to pass through and be parsed in the
1059 # the list of whitelist files to pass through and be parsed in the
1066 # children, but not the dict of whitelisted tests resulting from
1060 # children, but not the dict of whitelisted tests resulting from
1067 # the parse, used here to override blacklisted tests.
1061 # the parse, used here to override blacklisted tests.
1068 whitelist = optcopy['whitelisted'] or []
1062 whitelist = optcopy['whitelisted'] or []
1069 del optcopy['whitelisted']
1063 del optcopy['whitelisted']
1070
1064
1071 blacklist = optcopy['blacklist'] or []
1065 blacklist = optcopy['blacklist'] or []
1072 del optcopy['blacklist']
1066 del optcopy['blacklist']
1073 blacklisted = []
1067 blacklisted = []
1074
1068
1075 if optcopy['with_hg'] is None:
1069 if optcopy['with_hg'] is None:
1076 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1070 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1077 optcopy.pop('anycoverage', None)
1071 optcopy.pop('anycoverage', None)
1078
1072
1079 opts = []
1073 opts = []
1080 for opt, value in optcopy.iteritems():
1074 for opt, value in optcopy.iteritems():
1081 name = '--' + opt.replace('_', '-')
1075 name = '--' + opt.replace('_', '-')
1082 if value is True:
1076 if value is True:
1083 opts.append(name)
1077 opts.append(name)
1084 elif isinstance(value, list):
1078 elif isinstance(value, list):
1085 for v in value:
1079 for v in value:
1086 opts.append(name + '=' + str(v))
1080 opts.append(name + '=' + str(v))
1087 elif value is not None:
1081 elif value is not None:
1088 opts.append(name + '=' + str(value))
1082 opts.append(name + '=' + str(value))
1089
1083
1090 tests.reverse()
1084 tests.reverse()
1091 jobs = [[] for j in xrange(options.jobs)]
1085 jobs = [[] for j in xrange(options.jobs)]
1092 while tests:
1086 while tests:
1093 for job in jobs:
1087 for job in jobs:
1094 if not tests:
1088 if not tests:
1095 break
1089 break
1096 test = tests.pop()
1090 test = tests.pop()
1097 if test not in whitelist and test in blacklist:
1091 if test not in whitelist and test in blacklist:
1098 blacklisted.append(test)
1092 blacklisted.append(test)
1099 else:
1093 else:
1100 job.append(test)
1094 job.append(test)
1101
1095
1102 waitq = queue.Queue()
1096 waitq = queue.Queue()
1103
1097
1104 # windows lacks os.wait, so we must emulate it
1098 # windows lacks os.wait, so we must emulate it
1105 def waitfor(proc, rfd):
1099 def waitfor(proc, rfd):
1106 fp = os.fdopen(rfd, 'rb')
1100 fp = os.fdopen(rfd, 'rb')
1107 return lambda: waitq.put((proc.pid, proc.wait(), fp))
1101 return lambda: waitq.put((proc.pid, proc.wait(), fp))
1108
1102
1109 for j, job in enumerate(jobs):
1103 for j, job in enumerate(jobs):
1110 if not job:
1104 if not job:
1111 continue
1105 continue
1112 rfd, wfd = os.pipe()
1106 rfd, wfd = os.pipe()
1113 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1107 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1114 childtmp = os.path.join(HGTMP, 'child%d' % j)
1108 childtmp = os.path.join(HGTMP, 'child%d' % j)
1115 childopts += ['--tmpdir', childtmp]
1109 childopts += ['--tmpdir', childtmp]
1116 if options.keep_tmpdir:
1110 if options.keep_tmpdir:
1117 childopts.append('--keep-tmpdir')
1111 childopts.append('--keep-tmpdir')
1118 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1112 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1119 vlog(' '.join(cmdline))
1113 vlog(' '.join(cmdline))
1120 proc = subprocess.Popen(cmdline, executable=cmdline[0])
1114 proc = subprocess.Popen(cmdline, executable=cmdline[0])
1121 threading.Thread(target=waitfor(proc, rfd)).start()
1115 threading.Thread(target=waitfor(proc, rfd)).start()
1122 os.close(wfd)
1116 os.close(wfd)
1123 signal.signal(signal.SIGINT, signal.SIG_IGN)
1117 signal.signal(signal.SIGINT, signal.SIG_IGN)
1124 failures = 0
1118 failures = 0
1125 passed, skipped, failed = 0, 0, 0
1119 passed, skipped, failed = 0, 0, 0
1126 skips = []
1120 skips = []
1127 fails = []
1121 fails = []
1128 for job in jobs:
1122 for job in jobs:
1129 if not job:
1123 if not job:
1130 continue
1124 continue
1131 pid, status, fp = waitq.get()
1125 pid, status, fp = waitq.get()
1132 try:
1126 try:
1133 childresults = pickle.load(fp)
1127 childresults = pickle.load(fp)
1134 except (pickle.UnpicklingError, EOFError):
1128 except (pickle.UnpicklingError, EOFError):
1135 sys.exit(255)
1129 sys.exit(255)
1136 else:
1130 else:
1137 passed += len(childresults['p'])
1131 passed += len(childresults['p'])
1138 skipped += len(childresults['s'])
1132 skipped += len(childresults['s'])
1139 failed += len(childresults['f'])
1133 failed += len(childresults['f'])
1140 skips.extend(childresults['s'])
1134 skips.extend(childresults['s'])
1141 fails.extend(childresults['f'])
1135 fails.extend(childresults['f'])
1142 if options.time:
1136 if options.time:
1143 childtimes = pickle.load(fp)
1137 childtimes = pickle.load(fp)
1144 times.extend(childtimes)
1138 times.extend(childtimes)
1145
1139
1146 vlog('pid %d exited, status %d' % (pid, status))
1140 vlog('pid %d exited, status %d' % (pid, status))
1147 failures |= status
1141 failures |= status
1148 print
1142 print
1149 skipped += len(blacklisted)
1143 skipped += len(blacklisted)
1150 if not options.noskips:
1144 if not options.noskips:
1151 for s in skips:
1145 for s in skips:
1152 print "Skipped %s: %s" % (s[0], s[1])
1146 print "Skipped %s: %s" % (s[0], s[1])
1153 for s in blacklisted:
1147 for s in blacklisted:
1154 print "Skipped %s: blacklisted" % s
1148 print "Skipped %s: blacklisted" % s
1155 for s in fails:
1149 for s in fails:
1156 print "Failed %s: %s" % (s[0], s[1])
1150 print "Failed %s: %s" % (s[0], s[1])
1157
1151
1158 _checkhglib("Tested")
1152 _checkhglib("Tested")
1159 print "# Ran %d tests, %d skipped, %d failed." % (
1153 print "# Ran %d tests, %d skipped, %d failed." % (
1160 passed + failed, skipped, failed)
1154 passed + failed, skipped, failed)
1161
1155
1162 if options.time:
1156 if options.time:
1163 outputtimes(options)
1157 outputtimes(options)
1164 if options.anycoverage:
1158 if options.anycoverage:
1165 outputcoverage(options)
1159 outputcoverage(options)
1166 sys.exit(failures != 0)
1160 sys.exit(failures != 0)
1167
1161
1168 results = dict(p=[], f=[], s=[], i=[])
1162 results = dict(p=[], f=[], s=[], i=[])
1169 resultslock = threading.Lock()
1163 resultslock = threading.Lock()
1170 times = []
1164 times = []
1171 iolock = threading.Lock()
1165 iolock = threading.Lock()
1172
1166
1173 def runqueue(options, tests):
1167 def runqueue(options, tests):
1174 for test in tests:
1168 for test in tests:
1175 ret = runone(options, test)
1169 ret = runone(options, test)
1176 if options.first and ret is not None and not ret:
1170 if options.first and ret is not None and not ret:
1177 break
1171 break
1178
1172
1179 def runtests(options, tests):
1173 def runtests(options, tests):
1180 global DAEMON_PIDS, HGRCPATH
1174 global DAEMON_PIDS, HGRCPATH
1181 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1175 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1182 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1176 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1183
1177
1184 try:
1178 try:
1185 if INST:
1179 if INST:
1186 installhg(options)
1180 installhg(options)
1187 _checkhglib("Testing")
1181 _checkhglib("Testing")
1188 else:
1182 else:
1189 usecorrectpython()
1183 usecorrectpython()
1190
1184
1191 if options.restart:
1185 if options.restart:
1192 orig = list(tests)
1186 orig = list(tests)
1193 while tests:
1187 while tests:
1194 if os.path.exists(tests[0] + ".err"):
1188 if os.path.exists(tests[0] + ".err"):
1195 break
1189 break
1196 tests.pop(0)
1190 tests.pop(0)
1197 if not tests:
1191 if not tests:
1198 print "running all tests"
1192 print "running all tests"
1199 tests = orig
1193 tests = orig
1200
1194
1201 runqueue(options, tests)
1195 runqueue(options, tests)
1202
1196
1203 failed = len(results['f'])
1197 failed = len(results['f'])
1204 tested = len(results['p']) + failed
1198 tested = len(results['p']) + failed
1205 skipped = len(results['s'])
1199 skipped = len(results['s'])
1206 ignored = len(results['i'])
1200 ignored = len(results['i'])
1207
1201
1208 if options.child:
1202 if options.child:
1209 fp = os.fdopen(options.child, 'wb')
1203 fp = os.fdopen(options.child, 'wb')
1210 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1204 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1211 if options.time:
1205 if options.time:
1212 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1206 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1213 fp.close()
1207 fp.close()
1214 else:
1208 else:
1215 print
1209 print
1216 for s in results['s']:
1210 for s in results['s']:
1217 print "Skipped %s: %s" % s
1211 print "Skipped %s: %s" % s
1218 for s in results['f']:
1212 for s in results['f']:
1219 print "Failed %s: %s" % s
1213 print "Failed %s: %s" % s
1220 _checkhglib("Tested")
1214 _checkhglib("Tested")
1221 print "# Ran %d tests, %d skipped, %d failed." % (
1215 print "# Ran %d tests, %d skipped, %d failed." % (
1222 tested, skipped + ignored, failed)
1216 tested, skipped + ignored, failed)
1223 if options.time:
1217 if options.time:
1224 outputtimes(options)
1218 outputtimes(options)
1225
1219
1226 if options.anycoverage:
1220 if options.anycoverage:
1227 outputcoverage(options)
1221 outputcoverage(options)
1228 except KeyboardInterrupt:
1222 except KeyboardInterrupt:
1229 failed = True
1223 failed = True
1230 if not options.child:
1224 if not options.child:
1231 print "\ninterrupted!"
1225 print "\ninterrupted!"
1232
1226
1233 if failed:
1227 if failed:
1234 sys.exit(1)
1228 sys.exit(1)
1235
1229
1236 testtypes = [('.py', pytest, '.out'),
1230 testtypes = [('.py', pytest, '.out'),
1237 ('.t', tsttest, '')]
1231 ('.t', tsttest, '')]
1238
1232
1239 def main():
1233 def main():
1240 (options, args) = parseargs()
1234 (options, args) = parseargs()
1241 if not options.child:
1235 if not options.child:
1242 os.umask(022)
1236 os.umask(022)
1243
1237
1244 checktools()
1238 checktools()
1245
1239
1246 if len(args) == 0:
1240 if len(args) == 0:
1247 args = sorted(t for t in os.listdir(".")
1241 args = sorted(t for t in os.listdir(".")
1248 if t.startswith("test-")
1242 if t.startswith("test-")
1249 and (t.endswith(".py") or t.endswith(".t")))
1243 and (t.endswith(".py") or t.endswith(".t")))
1250
1244
1251 tests = args
1245 tests = args
1252
1246
1253 if options.random:
1247 if options.random:
1254 random.shuffle(tests)
1248 random.shuffle(tests)
1255
1249
1256 # Reset some environment variables to well-known values so that
1250 # Reset some environment variables to well-known values so that
1257 # the tests produce repeatable output.
1251 # the tests produce repeatable output.
1258 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1252 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1259 os.environ['TZ'] = 'GMT'
1253 os.environ['TZ'] = 'GMT'
1260 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1254 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1261 os.environ['CDPATH'] = ''
1255 os.environ['CDPATH'] = ''
1262 os.environ['COLUMNS'] = '80'
1256 os.environ['COLUMNS'] = '80'
1263 os.environ['GREP_OPTIONS'] = ''
1257 os.environ['GREP_OPTIONS'] = ''
1264 os.environ['http_proxy'] = ''
1258 os.environ['http_proxy'] = ''
1265 os.environ['no_proxy'] = ''
1259 os.environ['no_proxy'] = ''
1266 os.environ['NO_PROXY'] = ''
1260 os.environ['NO_PROXY'] = ''
1267 os.environ['TERM'] = 'xterm'
1261 os.environ['TERM'] = 'xterm'
1268 if 'PYTHONHASHSEED' not in os.environ:
1262 if 'PYTHONHASHSEED' not in os.environ:
1269 # use a random python hash seed all the time
1263 # use a random python hash seed all the time
1270 # we do the randomness ourself to know what seed is used
1264 # we do the randomness ourself to know what seed is used
1271 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1265 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1272 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1266 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1273
1267
1274 # unset env related to hooks
1268 # unset env related to hooks
1275 for k in os.environ.keys():
1269 for k in os.environ.keys():
1276 if k.startswith('HG_'):
1270 if k.startswith('HG_'):
1277 # can't remove on solaris
1271 # can't remove on solaris
1278 os.environ[k] = ''
1272 os.environ[k] = ''
1279 del os.environ[k]
1273 del os.environ[k]
1280 if 'HG' in os.environ:
1274 if 'HG' in os.environ:
1281 # can't remove on solaris
1275 # can't remove on solaris
1282 os.environ['HG'] = ''
1276 os.environ['HG'] = ''
1283 del os.environ['HG']
1277 del os.environ['HG']
1284 if 'HGPROF' in os.environ:
1278 if 'HGPROF' in os.environ:
1285 os.environ['HGPROF'] = ''
1279 os.environ['HGPROF'] = ''
1286 del os.environ['HGPROF']
1280 del os.environ['HGPROF']
1287
1281
1288 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1282 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1289 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1283 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1290 if options.tmpdir:
1284 if options.tmpdir:
1291 if not options.child:
1285 if not options.child:
1292 options.keep_tmpdir = True
1286 options.keep_tmpdir = True
1293 tmpdir = options.tmpdir
1287 tmpdir = options.tmpdir
1294 if os.path.exists(tmpdir):
1288 if os.path.exists(tmpdir):
1295 # Meaning of tmpdir has changed since 1.3: we used to create
1289 # Meaning of tmpdir has changed since 1.3: we used to create
1296 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1290 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1297 # tmpdir already exists.
1291 # tmpdir already exists.
1298 sys.exit("error: temp dir %r already exists" % tmpdir)
1292 sys.exit("error: temp dir %r already exists" % tmpdir)
1299
1293
1300 # Automatically removing tmpdir sounds convenient, but could
1294 # Automatically removing tmpdir sounds convenient, but could
1301 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1295 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1302 # or "--tmpdir=$HOME".
1296 # or "--tmpdir=$HOME".
1303 #vlog("# Removing temp dir", tmpdir)
1297 #vlog("# Removing temp dir", tmpdir)
1304 #shutil.rmtree(tmpdir)
1298 #shutil.rmtree(tmpdir)
1305 os.makedirs(tmpdir)
1299 os.makedirs(tmpdir)
1306 else:
1300 else:
1307 d = None
1301 d = None
1308 if os.name == 'nt':
1302 if os.name == 'nt':
1309 # without this, we get the default temp dir location, but
1303 # without this, we get the default temp dir location, but
1310 # in all lowercase, which causes troubles with paths (issue3490)
1304 # in all lowercase, which causes troubles with paths (issue3490)
1311 d = os.getenv('TMP')
1305 d = os.getenv('TMP')
1312 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1306 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1313 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1307 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1314 DAEMON_PIDS = None
1308 DAEMON_PIDS = None
1315 HGRCPATH = None
1309 HGRCPATH = None
1316
1310
1317 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1311 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1318 os.environ["HGMERGE"] = "internal:merge"
1312 os.environ["HGMERGE"] = "internal:merge"
1319 os.environ["HGUSER"] = "test"
1313 os.environ["HGUSER"] = "test"
1320 os.environ["HGENCODING"] = "ascii"
1314 os.environ["HGENCODING"] = "ascii"
1321 os.environ["HGENCODINGMODE"] = "strict"
1315 os.environ["HGENCODINGMODE"] = "strict"
1322 os.environ["HGPORT"] = str(options.port)
1316 os.environ["HGPORT"] = str(options.port)
1323 os.environ["HGPORT1"] = str(options.port + 1)
1317 os.environ["HGPORT1"] = str(options.port + 1)
1324 os.environ["HGPORT2"] = str(options.port + 2)
1318 os.environ["HGPORT2"] = str(options.port + 2)
1325
1319
1326 if options.with_hg:
1320 if options.with_hg:
1327 INST = None
1321 INST = None
1328 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1322 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1329
1323
1330 # This looks redundant with how Python initializes sys.path from
1324 # This looks redundant with how Python initializes sys.path from
1331 # the location of the script being executed. Needed because the
1325 # the location of the script being executed. Needed because the
1332 # "hg" specified by --with-hg is not the only Python script
1326 # "hg" specified by --with-hg is not the only Python script
1333 # executed in the test suite that needs to import 'mercurial'
1327 # executed in the test suite that needs to import 'mercurial'
1334 # ... which means it's not really redundant at all.
1328 # ... which means it's not really redundant at all.
1335 PYTHONDIR = BINDIR
1329 PYTHONDIR = BINDIR
1336 else:
1330 else:
1337 INST = os.path.join(HGTMP, "install")
1331 INST = os.path.join(HGTMP, "install")
1338 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1332 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1339 PYTHONDIR = os.path.join(INST, "lib", "python")
1333 PYTHONDIR = os.path.join(INST, "lib", "python")
1340
1334
1341 os.environ["BINDIR"] = BINDIR
1335 os.environ["BINDIR"] = BINDIR
1342 os.environ["PYTHON"] = PYTHON
1336 os.environ["PYTHON"] = PYTHON
1343
1337
1344 if not options.child:
1338 if not options.child:
1345 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1339 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1346 os.environ["PATH"] = os.pathsep.join(path)
1340 os.environ["PATH"] = os.pathsep.join(path)
1347
1341
1348 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1342 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1349 # can run .../tests/run-tests.py test-foo where test-foo
1343 # can run .../tests/run-tests.py test-foo where test-foo
1350 # adds an extension to HGRC
1344 # adds an extension to HGRC
1351 pypath = [PYTHONDIR, TESTDIR]
1345 pypath = [PYTHONDIR, TESTDIR]
1352 # We have to augment PYTHONPATH, rather than simply replacing
1346 # We have to augment PYTHONPATH, rather than simply replacing
1353 # it, in case external libraries are only available via current
1347 # it, in case external libraries are only available via current
1354 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1348 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1355 # are in /opt/subversion.)
1349 # are in /opt/subversion.)
1356 oldpypath = os.environ.get(IMPL_PATH)
1350 oldpypath = os.environ.get(IMPL_PATH)
1357 if oldpypath:
1351 if oldpypath:
1358 pypath.append(oldpypath)
1352 pypath.append(oldpypath)
1359 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1353 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1360
1354
1361 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1355 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1362
1356
1363 vlog("# Using TESTDIR", TESTDIR)
1357 vlog("# Using TESTDIR", TESTDIR)
1364 vlog("# Using HGTMP", HGTMP)
1358 vlog("# Using HGTMP", HGTMP)
1365 vlog("# Using PATH", os.environ["PATH"])
1359 vlog("# Using PATH", os.environ["PATH"])
1366 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1360 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1367
1361
1368 try:
1362 try:
1369 if len(tests) > 1 and options.jobs > 1:
1363 if len(tests) > 1 and options.jobs > 1:
1370 runchildren(options, tests)
1364 runchildren(options, tests)
1371 else:
1365 else:
1372 runtests(options, tests)
1366 runtests(options, tests)
1373 finally:
1367 finally:
1374 time.sleep(.1)
1368 time.sleep(.1)
1375 cleanup(options)
1369 cleanup(options)
1376
1370
1377 if __name__ == '__main__':
1371 if __name__ == '__main__':
1378 main()
1372 main()
General Comments 0
You need to be logged in to leave comments. Login now