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