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