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