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