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