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