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