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