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