##// END OF EJS Templates
run-tests: teach unittest about warned results
Gregory Szorc -
r21433:ff4a270b default
parent child Browse files
Show More
@@ -1,1579 +1,1593 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 import unittest
60 import unittest
61
61
62 processlock = threading.Lock()
62 processlock = threading.Lock()
63
63
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
66 # zombies but it's pretty harmless even if we do.
66 # zombies but it's pretty harmless even if we do.
67 if sys.version_info < (2, 5):
67 if sys.version_info < (2, 5):
68 subprocess._cleanup = lambda: None
68 subprocess._cleanup = lambda: None
69
69
70 closefds = os.name == 'posix'
70 closefds = os.name == 'posix'
71 def Popen4(cmd, wd, timeout, env=None):
71 def Popen4(cmd, wd, timeout, env=None):
72 processlock.acquire()
72 processlock.acquire()
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
74 close_fds=closefds,
74 close_fds=closefds,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
76 stderr=subprocess.STDOUT)
76 stderr=subprocess.STDOUT)
77 processlock.release()
77 processlock.release()
78
78
79 p.fromchild = p.stdout
79 p.fromchild = p.stdout
80 p.tochild = p.stdin
80 p.tochild = p.stdin
81 p.childerr = p.stderr
81 p.childerr = p.stderr
82
82
83 p.timeout = False
83 p.timeout = False
84 if timeout:
84 if timeout:
85 def t():
85 def t():
86 start = time.time()
86 start = time.time()
87 while time.time() - start < timeout and p.returncode is None:
87 while time.time() - start < timeout and p.returncode is None:
88 time.sleep(.1)
88 time.sleep(.1)
89 p.timeout = True
89 p.timeout = True
90 if p.returncode is None:
90 if p.returncode is None:
91 terminate(p)
91 terminate(p)
92 threading.Thread(target=t).start()
92 threading.Thread(target=t).start()
93
93
94 return p
94 return p
95
95
96 PYTHON = sys.executable.replace('\\', '/')
96 PYTHON = sys.executable.replace('\\', '/')
97 IMPL_PATH = 'PYTHONPATH'
97 IMPL_PATH = 'PYTHONPATH'
98 if 'java' in sys.platform:
98 if 'java' in sys.platform:
99 IMPL_PATH = 'JYTHONPATH'
99 IMPL_PATH = 'JYTHONPATH'
100
100
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
102
102
103 defaults = {
103 defaults = {
104 'jobs': ('HGTEST_JOBS', 1),
104 'jobs': ('HGTEST_JOBS', 1),
105 'timeout': ('HGTEST_TIMEOUT', 180),
105 'timeout': ('HGTEST_TIMEOUT', 180),
106 'port': ('HGTEST_PORT', 20059),
106 'port': ('HGTEST_PORT', 20059),
107 'shell': ('HGTEST_SHELL', 'sh'),
107 'shell': ('HGTEST_SHELL', 'sh'),
108 }
108 }
109
109
110 def parselistfiles(files, listtype, warn=True):
110 def parselistfiles(files, listtype, warn=True):
111 entries = dict()
111 entries = dict()
112 for filename in files:
112 for filename in files:
113 try:
113 try:
114 path = os.path.expanduser(os.path.expandvars(filename))
114 path = os.path.expanduser(os.path.expandvars(filename))
115 f = open(path, "r")
115 f = open(path, "r")
116 except IOError, err:
116 except IOError, err:
117 if err.errno != errno.ENOENT:
117 if err.errno != errno.ENOENT:
118 raise
118 raise
119 if warn:
119 if warn:
120 print "warning: no such %s file: %s" % (listtype, filename)
120 print "warning: no such %s file: %s" % (listtype, filename)
121 continue
121 continue
122
122
123 for line in f.readlines():
123 for line in f.readlines():
124 line = line.split('#', 1)[0].strip()
124 line = line.split('#', 1)[0].strip()
125 if line:
125 if line:
126 entries[line] = filename
126 entries[line] = filename
127
127
128 f.close()
128 f.close()
129 return entries
129 return entries
130
130
131 def getparser():
131 def getparser():
132 """Obtain the OptionParser used by the CLI."""
132 """Obtain the OptionParser used by the CLI."""
133 parser = optparse.OptionParser("%prog [options] [tests]")
133 parser = optparse.OptionParser("%prog [options] [tests]")
134
134
135 # keep these sorted
135 # keep these sorted
136 parser.add_option("--blacklist", action="append",
136 parser.add_option("--blacklist", action="append",
137 help="skip tests listed in the specified blacklist file")
137 help="skip tests listed in the specified blacklist file")
138 parser.add_option("--whitelist", action="append",
138 parser.add_option("--whitelist", action="append",
139 help="always run tests listed in the specified whitelist file")
139 help="always run tests listed in the specified whitelist file")
140 parser.add_option("--changed", type="string",
140 parser.add_option("--changed", type="string",
141 help="run tests that are changed in parent rev or working directory")
141 help="run tests that are changed in parent rev or working directory")
142 parser.add_option("-C", "--annotate", action="store_true",
142 parser.add_option("-C", "--annotate", action="store_true",
143 help="output files annotated with coverage")
143 help="output files annotated with coverage")
144 parser.add_option("-c", "--cover", action="store_true",
144 parser.add_option("-c", "--cover", action="store_true",
145 help="print a test coverage report")
145 help="print a test coverage report")
146 parser.add_option("-d", "--debug", action="store_true",
146 parser.add_option("-d", "--debug", action="store_true",
147 help="debug mode: write output of test scripts to console"
147 help="debug mode: write output of test scripts to console"
148 " rather than capturing and diffing it (disables timeout)")
148 " rather than capturing and diffing it (disables timeout)")
149 parser.add_option("-f", "--first", action="store_true",
149 parser.add_option("-f", "--first", action="store_true",
150 help="exit on the first test failure")
150 help="exit on the first test failure")
151 parser.add_option("-H", "--htmlcov", action="store_true",
151 parser.add_option("-H", "--htmlcov", action="store_true",
152 help="create an HTML report of the coverage of the files")
152 help="create an HTML report of the coverage of the files")
153 parser.add_option("-i", "--interactive", action="store_true",
153 parser.add_option("-i", "--interactive", action="store_true",
154 help="prompt to accept changed output")
154 help="prompt to accept changed output")
155 parser.add_option("-j", "--jobs", type="int",
155 parser.add_option("-j", "--jobs", type="int",
156 help="number of jobs to run in parallel"
156 help="number of jobs to run in parallel"
157 " (default: $%s or %d)" % defaults['jobs'])
157 " (default: $%s or %d)" % defaults['jobs'])
158 parser.add_option("--keep-tmpdir", action="store_true",
158 parser.add_option("--keep-tmpdir", action="store_true",
159 help="keep temporary directory after running tests")
159 help="keep temporary directory after running tests")
160 parser.add_option("-k", "--keywords",
160 parser.add_option("-k", "--keywords",
161 help="run tests matching keywords")
161 help="run tests matching keywords")
162 parser.add_option("-l", "--local", action="store_true",
162 parser.add_option("-l", "--local", action="store_true",
163 help="shortcut for --with-hg=<testdir>/../hg")
163 help="shortcut for --with-hg=<testdir>/../hg")
164 parser.add_option("--loop", action="store_true",
164 parser.add_option("--loop", action="store_true",
165 help="loop tests repeatedly")
165 help="loop tests repeatedly")
166 parser.add_option("-n", "--nodiff", action="store_true",
166 parser.add_option("-n", "--nodiff", action="store_true",
167 help="skip showing test changes")
167 help="skip showing test changes")
168 parser.add_option("-p", "--port", type="int",
168 parser.add_option("-p", "--port", type="int",
169 help="port on which servers should listen"
169 help="port on which servers should listen"
170 " (default: $%s or %d)" % defaults['port'])
170 " (default: $%s or %d)" % defaults['port'])
171 parser.add_option("--compiler", type="string",
171 parser.add_option("--compiler", type="string",
172 help="compiler to build with")
172 help="compiler to build with")
173 parser.add_option("--pure", action="store_true",
173 parser.add_option("--pure", action="store_true",
174 help="use pure Python code instead of C extensions")
174 help="use pure Python code instead of C extensions")
175 parser.add_option("-R", "--restart", action="store_true",
175 parser.add_option("-R", "--restart", action="store_true",
176 help="restart at last error")
176 help="restart at last error")
177 parser.add_option("-r", "--retest", action="store_true",
177 parser.add_option("-r", "--retest", action="store_true",
178 help="retest failed tests")
178 help="retest failed tests")
179 parser.add_option("-S", "--noskips", action="store_true",
179 parser.add_option("-S", "--noskips", action="store_true",
180 help="don't report skip tests verbosely")
180 help="don't report skip tests verbosely")
181 parser.add_option("--shell", type="string",
181 parser.add_option("--shell", type="string",
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
183 parser.add_option("-t", "--timeout", type="int",
183 parser.add_option("-t", "--timeout", type="int",
184 help="kill errant tests after TIMEOUT seconds"
184 help="kill errant tests after TIMEOUT seconds"
185 " (default: $%s or %d)" % defaults['timeout'])
185 " (default: $%s or %d)" % defaults['timeout'])
186 parser.add_option("--time", action="store_true",
186 parser.add_option("--time", action="store_true",
187 help="time how long each test takes")
187 help="time how long each test takes")
188 parser.add_option("--tmpdir", type="string",
188 parser.add_option("--tmpdir", type="string",
189 help="run tests in the given temporary directory"
189 help="run tests in the given temporary directory"
190 " (implies --keep-tmpdir)")
190 " (implies --keep-tmpdir)")
191 parser.add_option("--unittest", action="store_true",
191 parser.add_option("--unittest", action="store_true",
192 help="run tests with Python's unittest package"
192 help="run tests with Python's unittest package"
193 " (this is an experimental feature)")
193 " (this is an experimental feature)")
194 parser.add_option("-v", "--verbose", action="store_true",
194 parser.add_option("-v", "--verbose", action="store_true",
195 help="output verbose messages")
195 help="output verbose messages")
196 parser.add_option("--view", type="string",
196 parser.add_option("--view", type="string",
197 help="external diff viewer")
197 help="external diff viewer")
198 parser.add_option("--with-hg", type="string",
198 parser.add_option("--with-hg", type="string",
199 metavar="HG",
199 metavar="HG",
200 help="test using specified hg script rather than a "
200 help="test using specified hg script rather than a "
201 "temporary installation")
201 "temporary installation")
202 parser.add_option("-3", "--py3k-warnings", action="store_true",
202 parser.add_option("-3", "--py3k-warnings", action="store_true",
203 help="enable Py3k warnings on Python 2.6+")
203 help="enable Py3k warnings on Python 2.6+")
204 parser.add_option('--extra-config-opt', action="append",
204 parser.add_option('--extra-config-opt', action="append",
205 help='set the given config opt in the test hgrc')
205 help='set the given config opt in the test hgrc')
206 parser.add_option('--random', action="store_true",
206 parser.add_option('--random', action="store_true",
207 help='run tests in random order')
207 help='run tests in random order')
208
208
209 for option, (envvar, default) in defaults.items():
209 for option, (envvar, default) in defaults.items():
210 defaults[option] = type(default)(os.environ.get(envvar, default))
210 defaults[option] = type(default)(os.environ.get(envvar, default))
211 parser.set_defaults(**defaults)
211 parser.set_defaults(**defaults)
212
212
213 return parser
213 return parser
214
214
215 def parseargs(args, parser):
215 def parseargs(args, parser):
216 """Parse arguments with our OptionParser and validate results."""
216 """Parse arguments with our OptionParser and validate results."""
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.unittest:
262 if options.unittest:
263 if options.jobs > 1:
263 if options.jobs > 1:
264 sys.stderr.write(
264 sys.stderr.write(
265 'warning: --jobs has no effect with --unittest')
265 'warning: --jobs has no effect with --unittest')
266 if options.loop:
266 if options.loop:
267 sys.stderr.write(
267 sys.stderr.write(
268 'warning: --loop has no effect with --unittest')
268 'warning: --loop has no effect with --unittest')
269 if options.interactive and options.debug:
269 if options.interactive and options.debug:
270 parser.error("-i/--interactive and -d/--debug are incompatible")
270 parser.error("-i/--interactive and -d/--debug are incompatible")
271 if options.debug:
271 if options.debug:
272 if options.timeout != defaults['timeout']:
272 if options.timeout != defaults['timeout']:
273 sys.stderr.write(
273 sys.stderr.write(
274 'warning: --timeout option ignored with --debug\n')
274 'warning: --timeout option ignored with --debug\n')
275 options.timeout = 0
275 options.timeout = 0
276 if options.py3k_warnings:
276 if options.py3k_warnings:
277 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
277 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
278 parser.error('--py3k-warnings can only be used on Python 2.6+')
278 parser.error('--py3k-warnings can only be used on Python 2.6+')
279 if options.blacklist:
279 if options.blacklist:
280 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
280 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
281 if options.whitelist:
281 if options.whitelist:
282 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
282 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
283 else:
283 else:
284 options.whitelisted = {}
284 options.whitelisted = {}
285
285
286 return (options, args)
286 return (options, args)
287
287
288 def rename(src, dst):
288 def rename(src, dst):
289 """Like os.rename(), trade atomicity and opened files friendliness
289 """Like os.rename(), trade atomicity and opened files friendliness
290 for existing destination support.
290 for existing destination support.
291 """
291 """
292 shutil.copy(src, dst)
292 shutil.copy(src, dst)
293 os.remove(src)
293 os.remove(src)
294
294
295 def showdiff(expected, output, ref, err):
295 def showdiff(expected, output, ref, err):
296 print
296 print
297 servefail = False
297 servefail = False
298 for line in difflib.unified_diff(expected, output, ref, err):
298 for line in difflib.unified_diff(expected, output, ref, err):
299 sys.stdout.write(line)
299 sys.stdout.write(line)
300 if not servefail and line.startswith(
300 if not servefail and line.startswith(
301 '+ abort: child process failed to start'):
301 '+ abort: child process failed to start'):
302 servefail = True
302 servefail = True
303 return {'servefail': servefail}
303 return {'servefail': servefail}
304
304
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 terminate(proc):
328 def terminate(proc):
329 """Terminate subprocess (with fallback for Python versions < 2.6)"""
329 """Terminate subprocess (with fallback for Python versions < 2.6)"""
330 vlog('# Terminating process %d' % proc.pid)
330 vlog('# Terminating process %d' % proc.pid)
331 try:
331 try:
332 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
332 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
333 except OSError:
333 except OSError:
334 pass
334 pass
335
335
336 def killdaemons(pidfile):
336 def killdaemons(pidfile):
337 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
337 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
338 logfn=vlog)
338 logfn=vlog)
339
339
340 class Test(object):
340 class Test(object):
341 """Encapsulates a single, runnable test.
341 """Encapsulates a single, runnable test.
342
342
343 Test instances can be run multiple times via run(). However, multiple
343 Test instances can be run multiple times via run(). However, multiple
344 runs cannot be run concurrently.
344 runs cannot be run concurrently.
345 """
345 """
346
346
347 # Status code reserved for skipped tests (used by hghave).
347 # Status code reserved for skipped tests (used by hghave).
348 SKIPPED_STATUS = 80
348 SKIPPED_STATUS = 80
349
349
350 def __init__(self, runner, test, count, refpath):
350 def __init__(self, runner, test, count, refpath):
351 path = os.path.join(runner.testdir, test)
351 path = os.path.join(runner.testdir, test)
352 errpath = os.path.join(runner.testdir, '%s.err' % test)
352 errpath = os.path.join(runner.testdir, '%s.err' % test)
353
353
354 self._runner = runner
354 self._runner = runner
355 self._testdir = runner.testdir
355 self._testdir = runner.testdir
356 self._test = test
356 self._test = test
357 self._path = path
357 self._path = path
358 self._options = runner.options
358 self._options = runner.options
359 self._count = count
359 self._count = count
360 self._daemonpids = []
360 self._daemonpids = []
361 self._refpath = refpath
361 self._refpath = refpath
362 self._errpath = errpath
362 self._errpath = errpath
363
363
364 # If we're not in --debug mode and reference output file exists,
364 # If we're not in --debug mode and reference output file exists,
365 # check test output against it.
365 # check test output against it.
366 if runner.options.debug:
366 if runner.options.debug:
367 self._refout = None # to match "out is None"
367 self._refout = None # to match "out is None"
368 elif os.path.exists(refpath):
368 elif os.path.exists(refpath):
369 f = open(refpath, 'r')
369 f = open(refpath, 'r')
370 self._refout = f.read().splitlines(True)
370 self._refout = f.read().splitlines(True)
371 f.close()
371 f.close()
372 else:
372 else:
373 self._refout = []
373 self._refout = []
374
374
375 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
375 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
376 os.mkdir(self._threadtmp)
376 os.mkdir(self._threadtmp)
377
377
378 def cleanup(self):
378 def cleanup(self):
379 for entry in self._daemonpids:
379 for entry in self._daemonpids:
380 killdaemons(entry)
380 killdaemons(entry)
381
381
382 if self._threadtmp and not self._options.keep_tmpdir:
382 if self._threadtmp and not self._options.keep_tmpdir:
383 shutil.rmtree(self._threadtmp, True)
383 shutil.rmtree(self._threadtmp, True)
384
384
385 def run(self):
385 def run(self):
386 """Run this test instance.
386 """Run this test instance.
387
387
388 This will return a tuple describing the result of the test.
388 This will return a tuple describing the result of the test.
389 """
389 """
390 if not os.path.exists(self._path):
390 if not os.path.exists(self._path):
391 return self.skip("Doesn't exist")
391 return self.skip("Doesn't exist")
392
392
393 options = self._options
393 options = self._options
394 if not (options.whitelisted and self._test in options.whitelisted):
394 if not (options.whitelisted and self._test in options.whitelisted):
395 if options.blacklist and self._test in options.blacklist:
395 if options.blacklist and self._test in options.blacklist:
396 return self.skip('blacklisted')
396 return self.skip('blacklisted')
397
397
398 if options.retest and not os.path.exists('%s.err' % self._test):
398 if options.retest and not os.path.exists('%s.err' % self._test):
399 return self.ignore('not retesting')
399 return self.ignore('not retesting')
400
400
401 if options.keywords:
401 if options.keywords:
402 f = open(self._test)
402 f = open(self._test)
403 t = f.read().lower() + self._test.lower()
403 t = f.read().lower() + self._test.lower()
404 f.close()
404 f.close()
405 for k in options.keywords.lower().split():
405 for k in options.keywords.lower().split():
406 if k in t:
406 if k in t:
407 break
407 break
408 else:
408 else:
409 return self.ignore("doesn't match keyword")
409 return self.ignore("doesn't match keyword")
410
410
411 if not os.path.basename(self._test.lower()).startswith('test-'):
411 if not os.path.basename(self._test.lower()).startswith('test-'):
412 return self.skip('not a test file')
412 return self.skip('not a test file')
413
413
414 # Remove any previous output files.
414 # Remove any previous output files.
415 if os.path.exists(self._errpath):
415 if os.path.exists(self._errpath):
416 os.remove(self._errpath)
416 os.remove(self._errpath)
417
417
418 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
418 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
419 os.mkdir(testtmp)
419 os.mkdir(testtmp)
420 replacements, port = self._getreplacements(testtmp)
420 replacements, port = self._getreplacements(testtmp)
421 env = self._getenv(testtmp, port)
421 env = self._getenv(testtmp, port)
422 self._daemonpids.append(env['DAEMON_PIDS'])
422 self._daemonpids.append(env['DAEMON_PIDS'])
423 self._createhgrc(env['HGRCPATH'])
423 self._createhgrc(env['HGRCPATH'])
424
424
425 vlog('# Test', self._test)
425 vlog('# Test', self._test)
426
426
427 starttime = time.time()
427 starttime = time.time()
428 try:
428 try:
429 ret, out = self._run(testtmp, replacements, env)
429 ret, out = self._run(testtmp, replacements, env)
430 duration = time.time() - starttime
430 duration = time.time() - starttime
431 except KeyboardInterrupt:
431 except KeyboardInterrupt:
432 duration = time.time() - starttime
432 duration = time.time() - starttime
433 log('INTERRUPTED: %s (after %d seconds)' % (self._test, duration))
433 log('INTERRUPTED: %s (after %d seconds)' % (self._test, duration))
434 raise
434 raise
435 except Exception, e:
435 except Exception, e:
436 return self.fail('Exception during execution: %s' % e, 255)
436 return self.fail('Exception during execution: %s' % e, 255)
437
437
438 killdaemons(env['DAEMON_PIDS'])
438 killdaemons(env['DAEMON_PIDS'])
439
439
440 if not options.keep_tmpdir:
440 if not options.keep_tmpdir:
441 shutil.rmtree(testtmp)
441 shutil.rmtree(testtmp)
442
442
443 def describe(ret):
443 def describe(ret):
444 if ret < 0:
444 if ret < 0:
445 return 'killed by signal: %d' % -ret
445 return 'killed by signal: %d' % -ret
446 return 'returned error code %d' % ret
446 return 'returned error code %d' % ret
447
447
448 skipped = False
448 skipped = False
449
449
450 if ret == self.SKIPPED_STATUS:
450 if ret == self.SKIPPED_STATUS:
451 if out is None: # Debug mode, nothing to parse.
451 if out is None: # Debug mode, nothing to parse.
452 missing = ['unknown']
452 missing = ['unknown']
453 failed = None
453 failed = None
454 else:
454 else:
455 missing, failed = TTest.parsehghaveoutput(out)
455 missing, failed = TTest.parsehghaveoutput(out)
456
456
457 if not missing:
457 if not missing:
458 missing = ['irrelevant']
458 missing = ['irrelevant']
459
459
460 if failed:
460 if failed:
461 res = self.fail('hg have failed checking for %s' % failed[-1],
461 res = self.fail('hg have failed checking for %s' % failed[-1],
462 ret)
462 ret)
463 else:
463 else:
464 skipped = True
464 skipped = True
465 res = self.skip(missing[-1])
465 res = self.skip(missing[-1])
466 elif ret == 'timeout':
466 elif ret == 'timeout':
467 res = self.fail('timed out', ret)
467 res = self.fail('timed out', ret)
468 elif out != self._refout:
468 elif out != self._refout:
469 info = {}
469 info = {}
470 if not options.nodiff:
470 if not options.nodiff:
471 iolock.acquire()
471 iolock.acquire()
472 if options.view:
472 if options.view:
473 os.system("%s %s %s" % (options.view, self._refpath,
473 os.system("%s %s %s" % (options.view, self._refpath,
474 self._errpath))
474 self._errpath))
475 else:
475 else:
476 info = showdiff(self._refout, out, self._refpath,
476 info = showdiff(self._refout, out, self._refpath,
477 self._errpath)
477 self._errpath)
478 iolock.release()
478 iolock.release()
479 msg = ''
479 msg = ''
480 if info.get('servefail'):
480 if info.get('servefail'):
481 msg += 'serve failed and '
481 msg += 'serve failed and '
482 if ret:
482 if ret:
483 msg += 'output changed and ' + describe(ret)
483 msg += 'output changed and ' + describe(ret)
484 else:
484 else:
485 msg += 'output changed'
485 msg += 'output changed'
486
486
487 if (ret != 0 or out != self._refout) and not skipped \
487 if (ret != 0 or out != self._refout) and not skipped \
488 and not options.debug:
488 and not options.debug:
489 f = open(self._errpath, 'wb')
489 f = open(self._errpath, 'wb')
490 for line in out:
490 for line in out:
491 f.write(line)
491 f.write(line)
492 f.close()
492 f.close()
493 res = self.fail(msg, ret)
493 res = self.fail(msg, ret)
494 elif ret:
494 elif ret:
495 res = self.fail(describe(ret), ret)
495 res = self.fail(describe(ret), ret)
496 else:
496 else:
497 res = self.success()
497 res = self.success()
498
498
499
499
500 vlog("# Ret was:", ret)
500 vlog("# Ret was:", ret)
501
501
502 if not options.verbose:
502 if not options.verbose:
503 iolock.acquire()
503 iolock.acquire()
504 sys.stdout.write(res[0])
504 sys.stdout.write(res[0])
505 sys.stdout.flush()
505 sys.stdout.flush()
506 iolock.release()
506 iolock.release()
507
507
508 self._runner.times.append((self._test, duration))
508 self._runner.times.append((self._test, duration))
509
509
510 return res
510 return res
511
511
512 def _run(self, testtmp, replacements, env):
512 def _run(self, testtmp, replacements, env):
513 # This should be implemented in child classes to run tests.
513 # This should be implemented in child classes to run tests.
514 return self._skip('unknown test type')
514 return self._skip('unknown test type')
515
515
516 def _getreplacements(self, testtmp):
516 def _getreplacements(self, testtmp):
517 port = self._options.port + self._count * 3
517 port = self._options.port + self._count * 3
518 r = [
518 r = [
519 (r':%s\b' % port, ':$HGPORT'),
519 (r':%s\b' % port, ':$HGPORT'),
520 (r':%s\b' % (port + 1), ':$HGPORT1'),
520 (r':%s\b' % (port + 1), ':$HGPORT1'),
521 (r':%s\b' % (port + 2), ':$HGPORT2'),
521 (r':%s\b' % (port + 2), ':$HGPORT2'),
522 ]
522 ]
523
523
524 if os.name == 'nt':
524 if os.name == 'nt':
525 r.append(
525 r.append(
526 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
526 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
527 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
527 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
528 for c in testtmp), '$TESTTMP'))
528 for c in testtmp), '$TESTTMP'))
529 else:
529 else:
530 r.append((re.escape(testtmp), '$TESTTMP'))
530 r.append((re.escape(testtmp), '$TESTTMP'))
531
531
532 return r, port
532 return r, port
533
533
534 def _getenv(self, testtmp, port):
534 def _getenv(self, testtmp, port):
535 env = os.environ.copy()
535 env = os.environ.copy()
536 env['TESTTMP'] = testtmp
536 env['TESTTMP'] = testtmp
537 env['HOME'] = testtmp
537 env['HOME'] = testtmp
538 env["HGPORT"] = str(port)
538 env["HGPORT"] = str(port)
539 env["HGPORT1"] = str(port + 1)
539 env["HGPORT1"] = str(port + 1)
540 env["HGPORT2"] = str(port + 2)
540 env["HGPORT2"] = str(port + 2)
541 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
541 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
542 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
542 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
543 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
543 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
544 env["HGMERGE"] = "internal:merge"
544 env["HGMERGE"] = "internal:merge"
545 env["HGUSER"] = "test"
545 env["HGUSER"] = "test"
546 env["HGENCODING"] = "ascii"
546 env["HGENCODING"] = "ascii"
547 env["HGENCODINGMODE"] = "strict"
547 env["HGENCODINGMODE"] = "strict"
548
548
549 # Reset some environment variables to well-known values so that
549 # Reset some environment variables to well-known values so that
550 # the tests produce repeatable output.
550 # the tests produce repeatable output.
551 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
551 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
552 env['TZ'] = 'GMT'
552 env['TZ'] = 'GMT'
553 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
553 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
554 env['COLUMNS'] = '80'
554 env['COLUMNS'] = '80'
555 env['TERM'] = 'xterm'
555 env['TERM'] = 'xterm'
556
556
557 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
557 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
558 'NO_PROXY').split():
558 'NO_PROXY').split():
559 if k in env:
559 if k in env:
560 del env[k]
560 del env[k]
561
561
562 # unset env related to hooks
562 # unset env related to hooks
563 for k in env.keys():
563 for k in env.keys():
564 if k.startswith('HG_'):
564 if k.startswith('HG_'):
565 del env[k]
565 del env[k]
566
566
567 return env
567 return env
568
568
569 def _createhgrc(self, path):
569 def _createhgrc(self, path):
570 # create a fresh hgrc
570 # create a fresh hgrc
571 hgrc = open(path, 'w')
571 hgrc = open(path, 'w')
572 hgrc.write('[ui]\n')
572 hgrc.write('[ui]\n')
573 hgrc.write('slash = True\n')
573 hgrc.write('slash = True\n')
574 hgrc.write('interactive = False\n')
574 hgrc.write('interactive = False\n')
575 hgrc.write('[defaults]\n')
575 hgrc.write('[defaults]\n')
576 hgrc.write('backout = -d "0 0"\n')
576 hgrc.write('backout = -d "0 0"\n')
577 hgrc.write('commit = -d "0 0"\n')
577 hgrc.write('commit = -d "0 0"\n')
578 hgrc.write('shelve = --date "0 0"\n')
578 hgrc.write('shelve = --date "0 0"\n')
579 hgrc.write('tag = -d "0 0"\n')
579 hgrc.write('tag = -d "0 0"\n')
580 if self._options.extra_config_opt:
580 if self._options.extra_config_opt:
581 for opt in self._options.extra_config_opt:
581 for opt in self._options.extra_config_opt:
582 section, key = opt.split('.', 1)
582 section, key = opt.split('.', 1)
583 assert '=' in key, ('extra config opt %s must '
583 assert '=' in key, ('extra config opt %s must '
584 'have an = for assignment' % opt)
584 'have an = for assignment' % opt)
585 hgrc.write('[%s]\n%s\n' % (section, key))
585 hgrc.write('[%s]\n%s\n' % (section, key))
586 hgrc.close()
586 hgrc.close()
587
587
588 def success(self):
588 def success(self):
589 return '.', self._test, ''
589 return '.', self._test, ''
590
590
591 def fail(self, msg, ret):
591 def fail(self, msg, ret):
592 warned = ret is False
592 warned = ret is False
593 if not self._options.nodiff:
593 if not self._options.nodiff:
594 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
594 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
595 msg))
595 msg))
596 if (not ret and self._options.interactive and
596 if (not ret and self._options.interactive and
597 os.path.exists(self._errpath)):
597 os.path.exists(self._errpath)):
598 iolock.acquire()
598 iolock.acquire()
599 print 'Accept this change? [n] ',
599 print 'Accept this change? [n] ',
600 answer = sys.stdin.readline().strip()
600 answer = sys.stdin.readline().strip()
601 iolock.release()
601 iolock.release()
602 if answer.lower() in ('y', 'yes'):
602 if answer.lower() in ('y', 'yes'):
603 if self._test.endswith('.t'):
603 if self._test.endswith('.t'):
604 rename(self._errpath, self._path)
604 rename(self._errpath, self._path)
605 else:
605 else:
606 rename(self._errpath, '%s.out' % self._path)
606 rename(self._errpath, '%s.out' % self._path)
607
607
608 return '.', self._test, ''
608 return '.', self._test, ''
609
609
610 return warned and '~' or '!', self._test, msg
610 return warned and '~' or '!', self._test, msg
611
611
612 def skip(self, msg):
612 def skip(self, msg):
613 if self._options.verbose:
613 if self._options.verbose:
614 log("\nSkipping %s: %s" % (self._path, msg))
614 log("\nSkipping %s: %s" % (self._path, msg))
615
615
616 return 's', self._test, msg
616 return 's', self._test, msg
617
617
618 def ignore(self, msg):
618 def ignore(self, msg):
619 return 'i', self._test, msg
619 return 'i', self._test, msg
620
620
621 class PythonTest(Test):
621 class PythonTest(Test):
622 """A Python-based test."""
622 """A Python-based test."""
623 def _run(self, testtmp, replacements, env):
623 def _run(self, testtmp, replacements, env):
624 py3kswitch = self._options.py3k_warnings and ' -3' or ''
624 py3kswitch = self._options.py3k_warnings and ' -3' or ''
625 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
625 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
626 vlog("# Running", cmd)
626 vlog("# Running", cmd)
627 if os.name == 'nt':
627 if os.name == 'nt':
628 replacements.append((r'\r\n', '\n'))
628 replacements.append((r'\r\n', '\n'))
629 return run(cmd, testtmp, self._options, replacements, env,
629 return run(cmd, testtmp, self._options, replacements, env,
630 self._runner.abort)
630 self._runner.abort)
631
631
632 class TTest(Test):
632 class TTest(Test):
633 """A "t test" is a test backed by a .t file."""
633 """A "t test" is a test backed by a .t file."""
634
634
635 SKIPPED_PREFIX = 'skipped: '
635 SKIPPED_PREFIX = 'skipped: '
636 FAILED_PREFIX = 'hghave check failed: '
636 FAILED_PREFIX = 'hghave check failed: '
637 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
637 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
638
638
639 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
639 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
640 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
640 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
641 {'\\': '\\\\', '\r': r'\r'})
641 {'\\': '\\\\', '\r': r'\r'})
642
642
643 def _run(self, testtmp, replacements, env):
643 def _run(self, testtmp, replacements, env):
644 f = open(self._path)
644 f = open(self._path)
645 lines = f.readlines()
645 lines = f.readlines()
646 f.close()
646 f.close()
647
647
648 salt, script, after, expected = self._parsetest(lines, testtmp)
648 salt, script, after, expected = self._parsetest(lines, testtmp)
649
649
650 # Write out the generated script.
650 # Write out the generated script.
651 fname = '%s.sh' % testtmp
651 fname = '%s.sh' % testtmp
652 f = open(fname, 'w')
652 f = open(fname, 'w')
653 for l in script:
653 for l in script:
654 f.write(l)
654 f.write(l)
655 f.close()
655 f.close()
656
656
657 cmd = '%s "%s"' % (self._options.shell, fname)
657 cmd = '%s "%s"' % (self._options.shell, fname)
658 vlog("# Running", cmd)
658 vlog("# Running", cmd)
659
659
660 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
660 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
661 self._runner.abort)
661 self._runner.abort)
662 # Do not merge output if skipped. Return hghave message instead.
662 # Do not merge output if skipped. Return hghave message instead.
663 # Similarly, with --debug, output is None.
663 # Similarly, with --debug, output is None.
664 if exitcode == self.SKIPPED_STATUS or output is None:
664 if exitcode == self.SKIPPED_STATUS or output is None:
665 return exitcode, output
665 return exitcode, output
666
666
667 return self._processoutput(exitcode, output, salt, after, expected)
667 return self._processoutput(exitcode, output, salt, after, expected)
668
668
669 def _hghave(self, reqs, testtmp):
669 def _hghave(self, reqs, testtmp):
670 # TODO do something smarter when all other uses of hghave are gone.
670 # TODO do something smarter when all other uses of hghave are gone.
671 tdir = self._testdir.replace('\\', '/')
671 tdir = self._testdir.replace('\\', '/')
672 proc = Popen4('%s -c "%s/hghave %s"' %
672 proc = Popen4('%s -c "%s/hghave %s"' %
673 (self._options.shell, tdir, ' '.join(reqs)),
673 (self._options.shell, tdir, ' '.join(reqs)),
674 testtmp, 0)
674 testtmp, 0)
675 stdout, stderr = proc.communicate()
675 stdout, stderr = proc.communicate()
676 ret = proc.wait()
676 ret = proc.wait()
677 if wifexited(ret):
677 if wifexited(ret):
678 ret = os.WEXITSTATUS(ret)
678 ret = os.WEXITSTATUS(ret)
679 if ret == 2:
679 if ret == 2:
680 print stdout
680 print stdout
681 sys.exit(1)
681 sys.exit(1)
682
682
683 return ret == 0
683 return ret == 0
684
684
685 def _parsetest(self, lines, testtmp):
685 def _parsetest(self, lines, testtmp):
686 # We generate a shell script which outputs unique markers to line
686 # We generate a shell script which outputs unique markers to line
687 # up script results with our source. These markers include input
687 # up script results with our source. These markers include input
688 # line number and the last return code.
688 # line number and the last return code.
689 salt = "SALT" + str(time.time())
689 salt = "SALT" + str(time.time())
690 def addsalt(line, inpython):
690 def addsalt(line, inpython):
691 if inpython:
691 if inpython:
692 script.append('%s %d 0\n' % (salt, line))
692 script.append('%s %d 0\n' % (salt, line))
693 else:
693 else:
694 script.append('echo %s %s $?\n' % (salt, line))
694 script.append('echo %s %s $?\n' % (salt, line))
695
695
696 script = []
696 script = []
697
697
698 # After we run the shell script, we re-unify the script output
698 # After we run the shell script, we re-unify the script output
699 # with non-active parts of the source, with synchronization by our
699 # with non-active parts of the source, with synchronization by our
700 # SALT line number markers. The after table contains the non-active
700 # SALT line number markers. The after table contains the non-active
701 # components, ordered by line number.
701 # components, ordered by line number.
702 after = {}
702 after = {}
703
703
704 # Expected shell script output.
704 # Expected shell script output.
705 expected = {}
705 expected = {}
706
706
707 pos = prepos = -1
707 pos = prepos = -1
708
708
709 # True or False when in a true or false conditional section
709 # True or False when in a true or false conditional section
710 skipping = None
710 skipping = None
711
711
712 # We keep track of whether or not we're in a Python block so we
712 # We keep track of whether or not we're in a Python block so we
713 # can generate the surrounding doctest magic.
713 # can generate the surrounding doctest magic.
714 inpython = False
714 inpython = False
715
715
716 if self._options.debug:
716 if self._options.debug:
717 script.append('set -x\n')
717 script.append('set -x\n')
718 if os.getenv('MSYSTEM'):
718 if os.getenv('MSYSTEM'):
719 script.append('alias pwd="pwd -W"\n')
719 script.append('alias pwd="pwd -W"\n')
720
720
721 for n, l in enumerate(lines):
721 for n, l in enumerate(lines):
722 if not l.endswith('\n'):
722 if not l.endswith('\n'):
723 l += '\n'
723 l += '\n'
724 if l.startswith('#if'):
724 if l.startswith('#if'):
725 lsplit = l.split()
725 lsplit = l.split()
726 if len(lsplit) < 2 or lsplit[0] != '#if':
726 if len(lsplit) < 2 or lsplit[0] != '#if':
727 after.setdefault(pos, []).append(' !!! invalid #if\n')
727 after.setdefault(pos, []).append(' !!! invalid #if\n')
728 if skipping is not None:
728 if skipping is not None:
729 after.setdefault(pos, []).append(' !!! nested #if\n')
729 after.setdefault(pos, []).append(' !!! nested #if\n')
730 skipping = not self._hghave(lsplit[1:], testtmp)
730 skipping = not self._hghave(lsplit[1:], testtmp)
731 after.setdefault(pos, []).append(l)
731 after.setdefault(pos, []).append(l)
732 elif l.startswith('#else'):
732 elif l.startswith('#else'):
733 if skipping is None:
733 if skipping is None:
734 after.setdefault(pos, []).append(' !!! missing #if\n')
734 after.setdefault(pos, []).append(' !!! missing #if\n')
735 skipping = not skipping
735 skipping = not skipping
736 after.setdefault(pos, []).append(l)
736 after.setdefault(pos, []).append(l)
737 elif l.startswith('#endif'):
737 elif l.startswith('#endif'):
738 if skipping is None:
738 if skipping is None:
739 after.setdefault(pos, []).append(' !!! missing #if\n')
739 after.setdefault(pos, []).append(' !!! missing #if\n')
740 skipping = None
740 skipping = None
741 after.setdefault(pos, []).append(l)
741 after.setdefault(pos, []).append(l)
742 elif skipping:
742 elif skipping:
743 after.setdefault(pos, []).append(l)
743 after.setdefault(pos, []).append(l)
744 elif l.startswith(' >>> '): # python inlines
744 elif l.startswith(' >>> '): # python inlines
745 after.setdefault(pos, []).append(l)
745 after.setdefault(pos, []).append(l)
746 prepos = pos
746 prepos = pos
747 pos = n
747 pos = n
748 if not inpython:
748 if not inpython:
749 # We've just entered a Python block. Add the header.
749 # We've just entered a Python block. Add the header.
750 inpython = True
750 inpython = True
751 addsalt(prepos, False) # Make sure we report the exit code.
751 addsalt(prepos, False) # Make sure we report the exit code.
752 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
752 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
753 addsalt(n, True)
753 addsalt(n, True)
754 script.append(l[2:])
754 script.append(l[2:])
755 elif l.startswith(' ... '): # python inlines
755 elif l.startswith(' ... '): # python inlines
756 after.setdefault(prepos, []).append(l)
756 after.setdefault(prepos, []).append(l)
757 script.append(l[2:])
757 script.append(l[2:])
758 elif l.startswith(' $ '): # commands
758 elif l.startswith(' $ '): # commands
759 if inpython:
759 if inpython:
760 script.append('EOF\n')
760 script.append('EOF\n')
761 inpython = False
761 inpython = False
762 after.setdefault(pos, []).append(l)
762 after.setdefault(pos, []).append(l)
763 prepos = pos
763 prepos = pos
764 pos = n
764 pos = n
765 addsalt(n, False)
765 addsalt(n, False)
766 cmd = l[4:].split()
766 cmd = l[4:].split()
767 if len(cmd) == 2 and cmd[0] == 'cd':
767 if len(cmd) == 2 and cmd[0] == 'cd':
768 l = ' $ cd %s || exit 1\n' % cmd[1]
768 l = ' $ cd %s || exit 1\n' % cmd[1]
769 script.append(l[4:])
769 script.append(l[4:])
770 elif l.startswith(' > '): # continuations
770 elif l.startswith(' > '): # continuations
771 after.setdefault(prepos, []).append(l)
771 after.setdefault(prepos, []).append(l)
772 script.append(l[4:])
772 script.append(l[4:])
773 elif l.startswith(' '): # results
773 elif l.startswith(' '): # results
774 # Queue up a list of expected results.
774 # Queue up a list of expected results.
775 expected.setdefault(pos, []).append(l[2:])
775 expected.setdefault(pos, []).append(l[2:])
776 else:
776 else:
777 if inpython:
777 if inpython:
778 script.append('EOF\n')
778 script.append('EOF\n')
779 inpython = False
779 inpython = False
780 # Non-command/result. Queue up for merged output.
780 # Non-command/result. Queue up for merged output.
781 after.setdefault(pos, []).append(l)
781 after.setdefault(pos, []).append(l)
782
782
783 if inpython:
783 if inpython:
784 script.append('EOF\n')
784 script.append('EOF\n')
785 if skipping is not None:
785 if skipping is not None:
786 after.setdefault(pos, []).append(' !!! missing #endif\n')
786 after.setdefault(pos, []).append(' !!! missing #endif\n')
787 addsalt(n + 1, False)
787 addsalt(n + 1, False)
788
788
789 return salt, script, after, expected
789 return salt, script, after, expected
790
790
791 def _processoutput(self, exitcode, output, salt, after, expected):
791 def _processoutput(self, exitcode, output, salt, after, expected):
792 # Merge the script output back into a unified test.
792 # Merge the script output back into a unified test.
793 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
793 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
794 if exitcode != 0:
794 if exitcode != 0:
795 warnonly = 3
795 warnonly = 3
796
796
797 pos = -1
797 pos = -1
798 postout = []
798 postout = []
799 for l in output:
799 for l in output:
800 lout, lcmd = l, None
800 lout, lcmd = l, None
801 if salt in l:
801 if salt in l:
802 lout, lcmd = l.split(salt, 1)
802 lout, lcmd = l.split(salt, 1)
803
803
804 if lout:
804 if lout:
805 if not lout.endswith('\n'):
805 if not lout.endswith('\n'):
806 lout += ' (no-eol)\n'
806 lout += ' (no-eol)\n'
807
807
808 # Find the expected output at the current position.
808 # Find the expected output at the current position.
809 el = None
809 el = None
810 if expected.get(pos, None):
810 if expected.get(pos, None):
811 el = expected[pos].pop(0)
811 el = expected[pos].pop(0)
812
812
813 r = TTest.linematch(el, lout)
813 r = TTest.linematch(el, lout)
814 if isinstance(r, str):
814 if isinstance(r, str):
815 if r == '+glob':
815 if r == '+glob':
816 lout = el[:-1] + ' (glob)\n'
816 lout = el[:-1] + ' (glob)\n'
817 r = '' # Warn only this line.
817 r = '' # Warn only this line.
818 elif r == '-glob':
818 elif r == '-glob':
819 lout = ''.join(el.rsplit(' (glob)', 1))
819 lout = ''.join(el.rsplit(' (glob)', 1))
820 r = '' # Warn only this line.
820 r = '' # Warn only this line.
821 else:
821 else:
822 log('\ninfo, unknown linematch result: %r\n' % r)
822 log('\ninfo, unknown linematch result: %r\n' % r)
823 r = False
823 r = False
824 if r:
824 if r:
825 postout.append(' ' + el)
825 postout.append(' ' + el)
826 else:
826 else:
827 if self.NEEDESCAPE(lout):
827 if self.NEEDESCAPE(lout):
828 lout = TTest.stringescape('%s (esc)\n' %
828 lout = TTest.stringescape('%s (esc)\n' %
829 lout.rstrip('\n'))
829 lout.rstrip('\n'))
830 postout.append(' ' + lout) # Let diff deal with it.
830 postout.append(' ' + lout) # Let diff deal with it.
831 if r != '': # If line failed.
831 if r != '': # If line failed.
832 warnonly = 3 # for sure not
832 warnonly = 3 # for sure not
833 elif warnonly == 1: # Is "not yet" and line is warn only.
833 elif warnonly == 1: # Is "not yet" and line is warn only.
834 warnonly = 2 # Yes do warn.
834 warnonly = 2 # Yes do warn.
835
835
836 if lcmd:
836 if lcmd:
837 # Add on last return code.
837 # Add on last return code.
838 ret = int(lcmd.split()[1])
838 ret = int(lcmd.split()[1])
839 if ret != 0:
839 if ret != 0:
840 postout.append(' [%s]\n' % ret)
840 postout.append(' [%s]\n' % ret)
841 if pos in after:
841 if pos in after:
842 # Merge in non-active test bits.
842 # Merge in non-active test bits.
843 postout += after.pop(pos)
843 postout += after.pop(pos)
844 pos = int(lcmd.split()[0])
844 pos = int(lcmd.split()[0])
845
845
846 if pos in after:
846 if pos in after:
847 postout += after.pop(pos)
847 postout += after.pop(pos)
848
848
849 if warnonly == 2:
849 if warnonly == 2:
850 exitcode = False # Set exitcode to warned.
850 exitcode = False # Set exitcode to warned.
851
851
852 return exitcode, postout
852 return exitcode, postout
853
853
854 @staticmethod
854 @staticmethod
855 def rematch(el, l):
855 def rematch(el, l):
856 try:
856 try:
857 # use \Z to ensure that the regex matches to the end of the string
857 # use \Z to ensure that the regex matches to the end of the string
858 if os.name == 'nt':
858 if os.name == 'nt':
859 return re.match(el + r'\r?\n\Z', l)
859 return re.match(el + r'\r?\n\Z', l)
860 return re.match(el + r'\n\Z', l)
860 return re.match(el + r'\n\Z', l)
861 except re.error:
861 except re.error:
862 # el is an invalid regex
862 # el is an invalid regex
863 return False
863 return False
864
864
865 @staticmethod
865 @staticmethod
866 def globmatch(el, l):
866 def globmatch(el, l):
867 # The only supported special characters are * and ? plus / which also
867 # The only supported special characters are * and ? plus / which also
868 # matches \ on windows. Escaping of these characters is supported.
868 # matches \ on windows. Escaping of these characters is supported.
869 if el + '\n' == l:
869 if el + '\n' == l:
870 if os.altsep:
870 if os.altsep:
871 # matching on "/" is not needed for this line
871 # matching on "/" is not needed for this line
872 return '-glob'
872 return '-glob'
873 return True
873 return True
874 i, n = 0, len(el)
874 i, n = 0, len(el)
875 res = ''
875 res = ''
876 while i < n:
876 while i < n:
877 c = el[i]
877 c = el[i]
878 i += 1
878 i += 1
879 if c == '\\' and el[i] in '*?\\/':
879 if c == '\\' and el[i] in '*?\\/':
880 res += el[i - 1:i + 1]
880 res += el[i - 1:i + 1]
881 i += 1
881 i += 1
882 elif c == '*':
882 elif c == '*':
883 res += '.*'
883 res += '.*'
884 elif c == '?':
884 elif c == '?':
885 res += '.'
885 res += '.'
886 elif c == '/' and os.altsep:
886 elif c == '/' and os.altsep:
887 res += '[/\\\\]'
887 res += '[/\\\\]'
888 else:
888 else:
889 res += re.escape(c)
889 res += re.escape(c)
890 return TTest.rematch(res, l)
890 return TTest.rematch(res, l)
891
891
892 @staticmethod
892 @staticmethod
893 def linematch(el, l):
893 def linematch(el, l):
894 if el == l: # perfect match (fast)
894 if el == l: # perfect match (fast)
895 return True
895 return True
896 if el:
896 if el:
897 if el.endswith(" (esc)\n"):
897 if el.endswith(" (esc)\n"):
898 el = el[:-7].decode('string-escape') + '\n'
898 el = el[:-7].decode('string-escape') + '\n'
899 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
899 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
900 return True
900 return True
901 if el.endswith(" (re)\n"):
901 if el.endswith(" (re)\n"):
902 return TTest.rematch(el[:-6], l)
902 return TTest.rematch(el[:-6], l)
903 if el.endswith(" (glob)\n"):
903 if el.endswith(" (glob)\n"):
904 return TTest.globmatch(el[:-8], l)
904 return TTest.globmatch(el[:-8], l)
905 if os.altsep and l.replace('\\', '/') == el:
905 if os.altsep and l.replace('\\', '/') == el:
906 return '+glob'
906 return '+glob'
907 return False
907 return False
908
908
909 @staticmethod
909 @staticmethod
910 def parsehghaveoutput(lines):
910 def parsehghaveoutput(lines):
911 '''Parse hghave log lines.
911 '''Parse hghave log lines.
912
912
913 Return tuple of lists (missing, failed):
913 Return tuple of lists (missing, failed):
914 * the missing/unknown features
914 * the missing/unknown features
915 * the features for which existence check failed'''
915 * the features for which existence check failed'''
916 missing = []
916 missing = []
917 failed = []
917 failed = []
918 for line in lines:
918 for line in lines:
919 if line.startswith(TTest.SKIPPED_PREFIX):
919 if line.startswith(TTest.SKIPPED_PREFIX):
920 line = line.splitlines()[0]
920 line = line.splitlines()[0]
921 missing.append(line[len(TTest.SKIPPED_PREFIX):])
921 missing.append(line[len(TTest.SKIPPED_PREFIX):])
922 elif line.startswith(TTest.FAILED_PREFIX):
922 elif line.startswith(TTest.FAILED_PREFIX):
923 line = line.splitlines()[0]
923 line = line.splitlines()[0]
924 failed.append(line[len(TTest.FAILED_PREFIX):])
924 failed.append(line[len(TTest.FAILED_PREFIX):])
925
925
926 return missing, failed
926 return missing, failed
927
927
928 @staticmethod
928 @staticmethod
929 def _escapef(m):
929 def _escapef(m):
930 return TTest.ESCAPEMAP[m.group(0)]
930 return TTest.ESCAPEMAP[m.group(0)]
931
931
932 @staticmethod
932 @staticmethod
933 def _stringescape(s):
933 def _stringescape(s):
934 return TTest.ESCAPESUB(TTest._escapef, s)
934 return TTest.ESCAPESUB(TTest._escapef, s)
935
935
936
936
937 wifexited = getattr(os, "WIFEXITED", lambda x: False)
937 wifexited = getattr(os, "WIFEXITED", lambda x: False)
938 def run(cmd, wd, options, replacements, env, abort):
938 def run(cmd, wd, options, replacements, env, abort):
939 """Run command in a sub-process, capturing the output (stdout and stderr).
939 """Run command in a sub-process, capturing the output (stdout and stderr).
940 Return a tuple (exitcode, output). output is None in debug mode."""
940 Return a tuple (exitcode, output). output is None in debug mode."""
941 # TODO: Use subprocess.Popen if we're running on Python 2.4
941 # TODO: Use subprocess.Popen if we're running on Python 2.4
942 if options.debug:
942 if options.debug:
943 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
943 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
944 ret = proc.wait()
944 ret = proc.wait()
945 return (ret, None)
945 return (ret, None)
946
946
947 proc = Popen4(cmd, wd, options.timeout, env)
947 proc = Popen4(cmd, wd, options.timeout, env)
948 def cleanup():
948 def cleanup():
949 terminate(proc)
949 terminate(proc)
950 ret = proc.wait()
950 ret = proc.wait()
951 if ret == 0:
951 if ret == 0:
952 ret = signal.SIGTERM << 8
952 ret = signal.SIGTERM << 8
953 killdaemons(env['DAEMON_PIDS'])
953 killdaemons(env['DAEMON_PIDS'])
954 return ret
954 return ret
955
955
956 output = ''
956 output = ''
957 proc.tochild.close()
957 proc.tochild.close()
958
958
959 try:
959 try:
960 output = proc.fromchild.read()
960 output = proc.fromchild.read()
961 except KeyboardInterrupt:
961 except KeyboardInterrupt:
962 vlog('# Handling keyboard interrupt')
962 vlog('# Handling keyboard interrupt')
963 cleanup()
963 cleanup()
964 raise
964 raise
965
965
966 ret = proc.wait()
966 ret = proc.wait()
967 if wifexited(ret):
967 if wifexited(ret):
968 ret = os.WEXITSTATUS(ret)
968 ret = os.WEXITSTATUS(ret)
969
969
970 if proc.timeout:
970 if proc.timeout:
971 ret = 'timeout'
971 ret = 'timeout'
972
972
973 if ret:
973 if ret:
974 killdaemons(env['DAEMON_PIDS'])
974 killdaemons(env['DAEMON_PIDS'])
975
975
976 if abort[0]:
976 if abort[0]:
977 raise KeyboardInterrupt()
977 raise KeyboardInterrupt()
978
978
979 for s, r in replacements:
979 for s, r in replacements:
980 output = re.sub(s, r, output)
980 output = re.sub(s, r, output)
981 return ret, output.splitlines(True)
981 return ret, output.splitlines(True)
982
982
983 iolock = threading.Lock()
983 iolock = threading.Lock()
984
984
985 class TestResult(unittest._TextTestResult):
985 class TestResult(unittest._TextTestResult):
986 """Holds results when executing via unittest."""
986 """Holds results when executing via unittest."""
987 # Don't worry too much about accessing the non-public _TextTestResult.
987 # Don't worry too much about accessing the non-public _TextTestResult.
988 # It is relatively common in Python testing tools.
988 # It is relatively common in Python testing tools.
989 def __init__(self, *args, **kwargs):
989 def __init__(self, *args, **kwargs):
990 super(TestResult, self).__init__(*args, **kwargs)
990 super(TestResult, self).__init__(*args, **kwargs)
991
991
992 # unittest.TestResult didn't have skipped until 2.7. We need to
992 # unittest.TestResult didn't have skipped until 2.7. We need to
993 # polyfill it.
993 # polyfill it.
994 self.skipped = []
994 self.skipped = []
995
995
996 # We have a custom "ignored" result that isn't present in any Python
996 # We have a custom "ignored" result that isn't present in any Python
997 # unittest implementation. It is very similar to skipped. It may make
997 # unittest implementation. It is very similar to skipped. It may make
998 # sense to map it into skip some day.
998 # sense to map it into skip some day.
999 self.ignored = []
999 self.ignored = []
1000
1000
1001 # We have a custom "warned" result that isn't present in any Python
1002 # unittest implementation. It is very similar to failed. It may make
1003 # sense to map it into fail some day.
1004 self.warned = []
1005
1001 # Polyfill.
1006 # Polyfill.
1002 def addSkip(self, test, reason):
1007 def addSkip(self, test, reason):
1003 self.skipped.append((test, reason))
1008 self.skipped.append((test, reason))
1004
1009
1005 if self.showAll:
1010 if self.showAll:
1006 self.stream.writeln('skipped %s' % reason)
1011 self.stream.writeln('skipped %s' % reason)
1007 else:
1012 else:
1008 self.stream.write('s')
1013 self.stream.write('s')
1009 self.stream.flush()
1014 self.stream.flush()
1010
1015
1011 def addIgnore(self, test, reason):
1016 def addIgnore(self, test, reason):
1012 self.ignored.append((test, reason))
1017 self.ignored.append((test, reason))
1013
1018
1014 if self.showAll:
1019 if self.showAll:
1015 self.stream.writeln('ignored %s' % reason)
1020 self.stream.writeln('ignored %s' % reason)
1016 else:
1021 else:
1017 self.stream.write('i')
1022 self.stream.write('i')
1018 self.stream.flush()
1023 self.stream.flush()
1019
1024
1025 def addWarn(self, test, reason):
1026 self.warned.append((test, reason))
1027
1028 if self.showAll:
1029 self.stream.writeln('warned %s' % reason)
1030 else:
1031 self.stream.write('~')
1032 self.stream.flush()
1033
1020 class TextTestRunner(unittest.TextTestRunner):
1034 class TextTestRunner(unittest.TextTestRunner):
1021 """Custom unittest test runner that uses appropriate settings."""
1035 """Custom unittest test runner that uses appropriate settings."""
1022
1036
1023 def _makeResult(self):
1037 def _makeResult(self):
1024 return TestResult(self.stream, self.descriptions, self.verbosity)
1038 return TestResult(self.stream, self.descriptions, self.verbosity)
1025
1039
1026 class TestRunner(object):
1040 class TestRunner(object):
1027 """Holds context for executing tests.
1041 """Holds context for executing tests.
1028
1042
1029 Tests rely on a lot of state. This object holds it for them.
1043 Tests rely on a lot of state. This object holds it for them.
1030 """
1044 """
1031
1045
1032 REQUIREDTOOLS = [
1046 REQUIREDTOOLS = [
1033 os.path.basename(sys.executable),
1047 os.path.basename(sys.executable),
1034 'diff',
1048 'diff',
1035 'grep',
1049 'grep',
1036 'unzip',
1050 'unzip',
1037 'gunzip',
1051 'gunzip',
1038 'bunzip2',
1052 'bunzip2',
1039 'sed',
1053 'sed',
1040 ]
1054 ]
1041
1055
1042 TESTTYPES = [
1056 TESTTYPES = [
1043 ('.py', PythonTest, '.out'),
1057 ('.py', PythonTest, '.out'),
1044 ('.t', TTest, ''),
1058 ('.t', TTest, ''),
1045 ]
1059 ]
1046
1060
1047 def __init__(self):
1061 def __init__(self):
1048 self.options = None
1062 self.options = None
1049 self.testdir = None
1063 self.testdir = None
1050 self.hgtmp = None
1064 self.hgtmp = None
1051 self.inst = None
1065 self.inst = None
1052 self.bindir = None
1066 self.bindir = None
1053 self.tmpbinddir = None
1067 self.tmpbinddir = None
1054 self.pythondir = None
1068 self.pythondir = None
1055 self.coveragefile = None
1069 self.coveragefile = None
1056 self.times = [] # Holds execution times of tests.
1070 self.times = [] # Holds execution times of tests.
1057 self.results = {
1071 self.results = {
1058 '.': [],
1072 '.': [],
1059 '!': [],
1073 '!': [],
1060 '~': [],
1074 '~': [],
1061 's': [],
1075 's': [],
1062 'i': [],
1076 'i': [],
1063 }
1077 }
1064 self.abort = [False]
1078 self.abort = [False]
1065 self._createdfiles = []
1079 self._createdfiles = []
1066 self._hgpath = None
1080 self._hgpath = None
1067
1081
1068 def run(self, args, parser=None):
1082 def run(self, args, parser=None):
1069 """Run the test suite."""
1083 """Run the test suite."""
1070 oldmask = os.umask(022)
1084 oldmask = os.umask(022)
1071 try:
1085 try:
1072 parser = parser or getparser()
1086 parser = parser or getparser()
1073 options, args = parseargs(args, parser)
1087 options, args = parseargs(args, parser)
1074 self.options = options
1088 self.options = options
1075
1089
1076 self._checktools()
1090 self._checktools()
1077 tests = self.findtests(args)
1091 tests = self.findtests(args)
1078 return self._run(tests)
1092 return self._run(tests)
1079 finally:
1093 finally:
1080 os.umask(oldmask)
1094 os.umask(oldmask)
1081
1095
1082 def _run(self, tests):
1096 def _run(self, tests):
1083 if self.options.random:
1097 if self.options.random:
1084 random.shuffle(tests)
1098 random.shuffle(tests)
1085 else:
1099 else:
1086 # keywords for slow tests
1100 # keywords for slow tests
1087 slow = 'svn gendoc check-code-hg'.split()
1101 slow = 'svn gendoc check-code-hg'.split()
1088 def sortkey(f):
1102 def sortkey(f):
1089 # run largest tests first, as they tend to take the longest
1103 # run largest tests first, as they tend to take the longest
1090 try:
1104 try:
1091 val = -os.stat(f).st_size
1105 val = -os.stat(f).st_size
1092 except OSError, e:
1106 except OSError, e:
1093 if e.errno != errno.ENOENT:
1107 if e.errno != errno.ENOENT:
1094 raise
1108 raise
1095 return -1e9 # file does not exist, tell early
1109 return -1e9 # file does not exist, tell early
1096 for kw in slow:
1110 for kw in slow:
1097 if kw in f:
1111 if kw in f:
1098 val *= 10
1112 val *= 10
1099 return val
1113 return val
1100 tests.sort(key=sortkey)
1114 tests.sort(key=sortkey)
1101
1115
1102 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1116 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1103
1117
1104 if 'PYTHONHASHSEED' not in os.environ:
1118 if 'PYTHONHASHSEED' not in os.environ:
1105 # use a random python hash seed all the time
1119 # use a random python hash seed all the time
1106 # we do the randomness ourself to know what seed is used
1120 # we do the randomness ourself to know what seed is used
1107 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1121 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1108
1122
1109 if self.options.tmpdir:
1123 if self.options.tmpdir:
1110 self.options.keep_tmpdir = True
1124 self.options.keep_tmpdir = True
1111 tmpdir = self.options.tmpdir
1125 tmpdir = self.options.tmpdir
1112 if os.path.exists(tmpdir):
1126 if os.path.exists(tmpdir):
1113 # Meaning of tmpdir has changed since 1.3: we used to create
1127 # Meaning of tmpdir has changed since 1.3: we used to create
1114 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1128 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1115 # tmpdir already exists.
1129 # tmpdir already exists.
1116 print "error: temp dir %r already exists" % tmpdir
1130 print "error: temp dir %r already exists" % tmpdir
1117 return 1
1131 return 1
1118
1132
1119 # Automatically removing tmpdir sounds convenient, but could
1133 # Automatically removing tmpdir sounds convenient, but could
1120 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1134 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1121 # or "--tmpdir=$HOME".
1135 # or "--tmpdir=$HOME".
1122 #vlog("# Removing temp dir", tmpdir)
1136 #vlog("# Removing temp dir", tmpdir)
1123 #shutil.rmtree(tmpdir)
1137 #shutil.rmtree(tmpdir)
1124 os.makedirs(tmpdir)
1138 os.makedirs(tmpdir)
1125 else:
1139 else:
1126 d = None
1140 d = None
1127 if os.name == 'nt':
1141 if os.name == 'nt':
1128 # without this, we get the default temp dir location, but
1142 # without this, we get the default temp dir location, but
1129 # in all lowercase, which causes troubles with paths (issue3490)
1143 # in all lowercase, which causes troubles with paths (issue3490)
1130 d = os.getenv('TMP')
1144 d = os.getenv('TMP')
1131 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1145 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1132 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1146 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1133
1147
1134 if self.options.with_hg:
1148 if self.options.with_hg:
1135 self.inst = None
1149 self.inst = None
1136 self.bindir = os.path.dirname(os.path.realpath(
1150 self.bindir = os.path.dirname(os.path.realpath(
1137 self.options.with_hg))
1151 self.options.with_hg))
1138 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1152 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1139 os.makedirs(self.tmpbindir)
1153 os.makedirs(self.tmpbindir)
1140
1154
1141 # This looks redundant with how Python initializes sys.path from
1155 # This looks redundant with how Python initializes sys.path from
1142 # the location of the script being executed. Needed because the
1156 # the location of the script being executed. Needed because the
1143 # "hg" specified by --with-hg is not the only Python script
1157 # "hg" specified by --with-hg is not the only Python script
1144 # executed in the test suite that needs to import 'mercurial'
1158 # executed in the test suite that needs to import 'mercurial'
1145 # ... which means it's not really redundant at all.
1159 # ... which means it's not really redundant at all.
1146 self.pythondir = self.bindir
1160 self.pythondir = self.bindir
1147 else:
1161 else:
1148 self.inst = os.path.join(self.hgtmp, "install")
1162 self.inst = os.path.join(self.hgtmp, "install")
1149 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1163 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1150 "bin")
1164 "bin")
1151 self.tmpbindir = self.bindir
1165 self.tmpbindir = self.bindir
1152 self.pythondir = os.path.join(self.inst, "lib", "python")
1166 self.pythondir = os.path.join(self.inst, "lib", "python")
1153
1167
1154 os.environ["BINDIR"] = self.bindir
1168 os.environ["BINDIR"] = self.bindir
1155 os.environ["PYTHON"] = PYTHON
1169 os.environ["PYTHON"] = PYTHON
1156
1170
1157 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1171 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1158 if self.tmpbindir != self.bindir:
1172 if self.tmpbindir != self.bindir:
1159 path = [self.tmpbindir] + path
1173 path = [self.tmpbindir] + path
1160 os.environ["PATH"] = os.pathsep.join(path)
1174 os.environ["PATH"] = os.pathsep.join(path)
1161
1175
1162 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1176 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1163 # can run .../tests/run-tests.py test-foo where test-foo
1177 # can run .../tests/run-tests.py test-foo where test-foo
1164 # adds an extension to HGRC. Also include run-test.py directory to
1178 # adds an extension to HGRC. Also include run-test.py directory to
1165 # import modules like heredoctest.
1179 # import modules like heredoctest.
1166 pypath = [self.pythondir, self.testdir,
1180 pypath = [self.pythondir, self.testdir,
1167 os.path.abspath(os.path.dirname(__file__))]
1181 os.path.abspath(os.path.dirname(__file__))]
1168 # We have to augment PYTHONPATH, rather than simply replacing
1182 # We have to augment PYTHONPATH, rather than simply replacing
1169 # it, in case external libraries are only available via current
1183 # it, in case external libraries are only available via current
1170 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1184 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1171 # are in /opt/subversion.)
1185 # are in /opt/subversion.)
1172 oldpypath = os.environ.get(IMPL_PATH)
1186 oldpypath = os.environ.get(IMPL_PATH)
1173 if oldpypath:
1187 if oldpypath:
1174 pypath.append(oldpypath)
1188 pypath.append(oldpypath)
1175 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1189 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1176
1190
1177 self.coveragefile = os.path.join(self.testdir, '.coverage')
1191 self.coveragefile = os.path.join(self.testdir, '.coverage')
1178
1192
1179 vlog("# Using TESTDIR", self.testdir)
1193 vlog("# Using TESTDIR", self.testdir)
1180 vlog("# Using HGTMP", self.hgtmp)
1194 vlog("# Using HGTMP", self.hgtmp)
1181 vlog("# Using PATH", os.environ["PATH"])
1195 vlog("# Using PATH", os.environ["PATH"])
1182 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1196 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1183
1197
1184 try:
1198 try:
1185 return self._runtests(tests) or 0
1199 return self._runtests(tests) or 0
1186 finally:
1200 finally:
1187 time.sleep(.1)
1201 time.sleep(.1)
1188 self._cleanup()
1202 self._cleanup()
1189
1203
1190 def findtests(self, args):
1204 def findtests(self, args):
1191 """Finds possible test files from arguments.
1205 """Finds possible test files from arguments.
1192
1206
1193 If you wish to inject custom tests into the test harness, this would
1207 If you wish to inject custom tests into the test harness, this would
1194 be a good function to monkeypatch or override in a derived class.
1208 be a good function to monkeypatch or override in a derived class.
1195 """
1209 """
1196 if not args:
1210 if not args:
1197 if self.options.changed:
1211 if self.options.changed:
1198 proc = Popen4('hg st --rev "%s" -man0 .' %
1212 proc = Popen4('hg st --rev "%s" -man0 .' %
1199 self.options.changed, None, 0)
1213 self.options.changed, None, 0)
1200 stdout, stderr = proc.communicate()
1214 stdout, stderr = proc.communicate()
1201 args = stdout.strip('\0').split('\0')
1215 args = stdout.strip('\0').split('\0')
1202 else:
1216 else:
1203 args = os.listdir('.')
1217 args = os.listdir('.')
1204
1218
1205 return [t for t in args
1219 return [t for t in args
1206 if os.path.basename(t).startswith('test-')
1220 if os.path.basename(t).startswith('test-')
1207 and (t.endswith('.py') or t.endswith('.t'))]
1221 and (t.endswith('.py') or t.endswith('.t'))]
1208
1222
1209 def _runtests(self, tests):
1223 def _runtests(self, tests):
1210 try:
1224 try:
1211 if self.inst:
1225 if self.inst:
1212 self._installhg()
1226 self._installhg()
1213 self._checkhglib("Testing")
1227 self._checkhglib("Testing")
1214 else:
1228 else:
1215 self._usecorrectpython()
1229 self._usecorrectpython()
1216
1230
1217 if self.options.restart:
1231 if self.options.restart:
1218 orig = list(tests)
1232 orig = list(tests)
1219 while tests:
1233 while tests:
1220 if os.path.exists(tests[0] + ".err"):
1234 if os.path.exists(tests[0] + ".err"):
1221 break
1235 break
1222 tests.pop(0)
1236 tests.pop(0)
1223 if not tests:
1237 if not tests:
1224 print "running all tests"
1238 print "running all tests"
1225 tests = orig
1239 tests = orig
1226
1240
1227 if self.options.unittest:
1241 if self.options.unittest:
1228 suite = unittest.TestSuite()
1242 suite = unittest.TestSuite()
1229 for count, testpath in enumerate(tests):
1243 for count, testpath in enumerate(tests):
1230 suite.addTest(self._gettest(testpath, count, asunit=True))
1244 suite.addTest(self._gettest(testpath, count, asunit=True))
1231
1245
1232 verbosity = 1
1246 verbosity = 1
1233 if self.options.verbose:
1247 if self.options.verbose:
1234 verbosity = 2
1248 verbosity = 2
1235 runner = TextTestRunner(verbosity=verbosity)
1249 runner = TextTestRunner(verbosity=verbosity)
1236 runner.run(suite)
1250 runner.run(suite)
1237 else:
1251 else:
1238 self._executetests(tests)
1252 self._executetests(tests)
1239
1253
1240 failed = len(self.results['!'])
1254 failed = len(self.results['!'])
1241 warned = len(self.results['~'])
1255 warned = len(self.results['~'])
1242 tested = len(self.results['.']) + failed + warned
1256 tested = len(self.results['.']) + failed + warned
1243 skipped = len(self.results['s'])
1257 skipped = len(self.results['s'])
1244 ignored = len(self.results['i'])
1258 ignored = len(self.results['i'])
1245
1259
1246 print
1260 print
1247 if not self.options.noskips:
1261 if not self.options.noskips:
1248 for s in self.results['s']:
1262 for s in self.results['s']:
1249 print "Skipped %s: %s" % s
1263 print "Skipped %s: %s" % s
1250 for s in self.results['~']:
1264 for s in self.results['~']:
1251 print "Warned %s: %s" % s
1265 print "Warned %s: %s" % s
1252 for s in self.results['!']:
1266 for s in self.results['!']:
1253 print "Failed %s: %s" % s
1267 print "Failed %s: %s" % s
1254 self._checkhglib("Tested")
1268 self._checkhglib("Tested")
1255 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1269 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1256 tested, skipped + ignored, warned, failed)
1270 tested, skipped + ignored, warned, failed)
1257 if self.results['!']:
1271 if self.results['!']:
1258 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1272 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1259 if self.options.time:
1273 if self.options.time:
1260 self._outputtimes()
1274 self._outputtimes()
1261
1275
1262 if self.options.anycoverage:
1276 if self.options.anycoverage:
1263 self._outputcoverage()
1277 self._outputcoverage()
1264 except KeyboardInterrupt:
1278 except KeyboardInterrupt:
1265 failed = True
1279 failed = True
1266 print "\ninterrupted!"
1280 print "\ninterrupted!"
1267
1281
1268 if failed:
1282 if failed:
1269 return 1
1283 return 1
1270 if warned:
1284 if warned:
1271 return 80
1285 return 80
1272
1286
1273 def _gettest(self, test, count, asunit=False):
1287 def _gettest(self, test, count, asunit=False):
1274 """Obtain a Test by looking at its filename.
1288 """Obtain a Test by looking at its filename.
1275
1289
1276 Returns a Test instance. The Test may not be runnable if it doesn't
1290 Returns a Test instance. The Test may not be runnable if it doesn't
1277 map to a known type.
1291 map to a known type.
1278 """
1292 """
1279 lctest = test.lower()
1293 lctest = test.lower()
1280 refpath = os.path.join(self.testdir, test)
1294 refpath = os.path.join(self.testdir, test)
1281
1295
1282 testcls = Test
1296 testcls = Test
1283
1297
1284 for ext, cls, out in self.TESTTYPES:
1298 for ext, cls, out in self.TESTTYPES:
1285 if lctest.endswith(ext):
1299 if lctest.endswith(ext):
1286 testcls = cls
1300 testcls = cls
1287 refpath = os.path.join(self.testdir, test + out)
1301 refpath = os.path.join(self.testdir, test + out)
1288 break
1302 break
1289
1303
1290 t = testcls(self, test, count, refpath)
1304 t = testcls(self, test, count, refpath)
1291
1305
1292 if not asunit:
1306 if not asunit:
1293 return t
1307 return t
1294
1308
1295 class MercurialTest(unittest.TestCase):
1309 class MercurialTest(unittest.TestCase):
1296 def __init__(self, name, *args, **kwargs):
1310 def __init__(self, name, *args, **kwargs):
1297 super(MercurialTest, self).__init__(*args, **kwargs)
1311 super(MercurialTest, self).__init__(*args, **kwargs)
1298 self.name = name
1312 self.name = name
1299
1313
1300 def shortDescription(self):
1314 def shortDescription(self):
1301 return self.name
1315 return self.name
1302
1316
1303 # Need to stash away the TestResult since we do custom things
1317 # Need to stash away the TestResult since we do custom things
1304 # with it.
1318 # with it.
1305 def run(self, result):
1319 def run(self, result):
1306 self._result = result
1320 self._result = result
1307
1321
1308 return super(MercurialTest, self).run(result)
1322 return super(MercurialTest, self).run(result)
1309
1323
1310 def runTest(self):
1324 def runTest(self):
1311 code, tname, msg = t.run()
1325 code, tname, msg = t.run()
1312
1326
1313 if code == '!':
1327 if code == '!':
1314 self._result.failures.append((self, msg))
1328 self._result.failures.append((self, msg))
1315 elif code == '~':
1329 elif code == '~':
1316 pass
1330 self._result.addWarn(self, msg)
1317 elif code == '.':
1331 elif code == '.':
1318 # Success is handled automatically by the built-in run().
1332 # Success is handled automatically by the built-in run().
1319 pass
1333 pass
1320 elif code == 's':
1334 elif code == 's':
1321 self._result.addSkip(self, msg)
1335 self._result.addSkip(self, msg)
1322 elif code == 'i':
1336 elif code == 'i':
1323 self._result.addIgnore(self, msg)
1337 self._result.addIgnore(self, msg)
1324 else:
1338 else:
1325 self.fail('Unknown test result code: %s' % code)
1339 self.fail('Unknown test result code: %s' % code)
1326
1340
1327 return MercurialTest(test)
1341 return MercurialTest(test)
1328
1342
1329 def _cleanup(self):
1343 def _cleanup(self):
1330 """Clean up state from this test invocation."""
1344 """Clean up state from this test invocation."""
1331
1345
1332 if self.options.keep_tmpdir:
1346 if self.options.keep_tmpdir:
1333 return
1347 return
1334
1348
1335 vlog("# Cleaning up HGTMP", self.hgtmp)
1349 vlog("# Cleaning up HGTMP", self.hgtmp)
1336 shutil.rmtree(self.hgtmp, True)
1350 shutil.rmtree(self.hgtmp, True)
1337 for f in self._createdfiles:
1351 for f in self._createdfiles:
1338 try:
1352 try:
1339 os.remove(f)
1353 os.remove(f)
1340 except OSError:
1354 except OSError:
1341 pass
1355 pass
1342
1356
1343 def _usecorrectpython(self):
1357 def _usecorrectpython(self):
1344 # Some tests run the Python interpreter. They must use the
1358 # Some tests run the Python interpreter. They must use the
1345 # same interpreter or bad things will happen.
1359 # same interpreter or bad things will happen.
1346 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1360 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1347 if getattr(os, 'symlink', None):
1361 if getattr(os, 'symlink', None):
1348 vlog("# Making python executable in test path a symlink to '%s'" %
1362 vlog("# Making python executable in test path a symlink to '%s'" %
1349 sys.executable)
1363 sys.executable)
1350 mypython = os.path.join(self.tmpbindir, pyexename)
1364 mypython = os.path.join(self.tmpbindir, pyexename)
1351 try:
1365 try:
1352 if os.readlink(mypython) == sys.executable:
1366 if os.readlink(mypython) == sys.executable:
1353 return
1367 return
1354 os.unlink(mypython)
1368 os.unlink(mypython)
1355 except OSError, err:
1369 except OSError, err:
1356 if err.errno != errno.ENOENT:
1370 if err.errno != errno.ENOENT:
1357 raise
1371 raise
1358 if self._findprogram(pyexename) != sys.executable:
1372 if self._findprogram(pyexename) != sys.executable:
1359 try:
1373 try:
1360 os.symlink(sys.executable, mypython)
1374 os.symlink(sys.executable, mypython)
1361 self._createdfiles.append(mypython)
1375 self._createdfiles.append(mypython)
1362 except OSError, err:
1376 except OSError, err:
1363 # child processes may race, which is harmless
1377 # child processes may race, which is harmless
1364 if err.errno != errno.EEXIST:
1378 if err.errno != errno.EEXIST:
1365 raise
1379 raise
1366 else:
1380 else:
1367 exedir, exename = os.path.split(sys.executable)
1381 exedir, exename = os.path.split(sys.executable)
1368 vlog("# Modifying search path to find %s as %s in '%s'" %
1382 vlog("# Modifying search path to find %s as %s in '%s'" %
1369 (exename, pyexename, exedir))
1383 (exename, pyexename, exedir))
1370 path = os.environ['PATH'].split(os.pathsep)
1384 path = os.environ['PATH'].split(os.pathsep)
1371 while exedir in path:
1385 while exedir in path:
1372 path.remove(exedir)
1386 path.remove(exedir)
1373 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1387 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1374 if not self._findprogram(pyexename):
1388 if not self._findprogram(pyexename):
1375 print "WARNING: Cannot find %s in search path" % pyexename
1389 print "WARNING: Cannot find %s in search path" % pyexename
1376
1390
1377 def _installhg(self):
1391 def _installhg(self):
1378 vlog("# Performing temporary installation of HG")
1392 vlog("# Performing temporary installation of HG")
1379 installerrs = os.path.join("tests", "install.err")
1393 installerrs = os.path.join("tests", "install.err")
1380 compiler = ''
1394 compiler = ''
1381 if self.options.compiler:
1395 if self.options.compiler:
1382 compiler = '--compiler ' + self.options.compiler
1396 compiler = '--compiler ' + self.options.compiler
1383 pure = self.options.pure and "--pure" or ""
1397 pure = self.options.pure and "--pure" or ""
1384 py3 = ''
1398 py3 = ''
1385 if sys.version_info[0] == 3:
1399 if sys.version_info[0] == 3:
1386 py3 = '--c2to3'
1400 py3 = '--c2to3'
1387
1401
1388 # Run installer in hg root
1402 # Run installer in hg root
1389 script = os.path.realpath(sys.argv[0])
1403 script = os.path.realpath(sys.argv[0])
1390 hgroot = os.path.dirname(os.path.dirname(script))
1404 hgroot = os.path.dirname(os.path.dirname(script))
1391 os.chdir(hgroot)
1405 os.chdir(hgroot)
1392 nohome = '--home=""'
1406 nohome = '--home=""'
1393 if os.name == 'nt':
1407 if os.name == 'nt':
1394 # The --home="" trick works only on OS where os.sep == '/'
1408 # The --home="" trick works only on OS where os.sep == '/'
1395 # because of a distutils convert_path() fast-path. Avoid it at
1409 # because of a distutils convert_path() fast-path. Avoid it at
1396 # least on Windows for now, deal with .pydistutils.cfg bugs
1410 # least on Windows for now, deal with .pydistutils.cfg bugs
1397 # when they happen.
1411 # when they happen.
1398 nohome = ''
1412 nohome = ''
1399 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1413 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1400 ' build %(compiler)s --build-base="%(base)s"'
1414 ' build %(compiler)s --build-base="%(base)s"'
1401 ' install --force --prefix="%(prefix)s"'
1415 ' install --force --prefix="%(prefix)s"'
1402 ' --install-lib="%(libdir)s"'
1416 ' --install-lib="%(libdir)s"'
1403 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1417 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1404 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1418 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1405 'compiler': compiler,
1419 'compiler': compiler,
1406 'base': os.path.join(self.hgtmp, "build"),
1420 'base': os.path.join(self.hgtmp, "build"),
1407 'prefix': self.inst, 'libdir': self.pythondir,
1421 'prefix': self.inst, 'libdir': self.pythondir,
1408 'bindir': self.bindir,
1422 'bindir': self.bindir,
1409 'nohome': nohome, 'logfile': installerrs})
1423 'nohome': nohome, 'logfile': installerrs})
1410 vlog("# Running", cmd)
1424 vlog("# Running", cmd)
1411 if os.system(cmd) == 0:
1425 if os.system(cmd) == 0:
1412 if not self.options.verbose:
1426 if not self.options.verbose:
1413 os.remove(installerrs)
1427 os.remove(installerrs)
1414 else:
1428 else:
1415 f = open(installerrs)
1429 f = open(installerrs)
1416 for line in f:
1430 for line in f:
1417 print line,
1431 print line,
1418 f.close()
1432 f.close()
1419 sys.exit(1)
1433 sys.exit(1)
1420 os.chdir(self.testdir)
1434 os.chdir(self.testdir)
1421
1435
1422 self._usecorrectpython()
1436 self._usecorrectpython()
1423
1437
1424 if self.options.py3k_warnings and not self.options.anycoverage:
1438 if self.options.py3k_warnings and not self.options.anycoverage:
1425 vlog("# Updating hg command to enable Py3k Warnings switch")
1439 vlog("# Updating hg command to enable Py3k Warnings switch")
1426 f = open(os.path.join(self.bindir, 'hg'), 'r')
1440 f = open(os.path.join(self.bindir, 'hg'), 'r')
1427 lines = [line.rstrip() for line in f]
1441 lines = [line.rstrip() for line in f]
1428 lines[0] += ' -3'
1442 lines[0] += ' -3'
1429 f.close()
1443 f.close()
1430 f = open(os.path.join(self.bindir, 'hg'), 'w')
1444 f = open(os.path.join(self.bindir, 'hg'), 'w')
1431 for line in lines:
1445 for line in lines:
1432 f.write(line + '\n')
1446 f.write(line + '\n')
1433 f.close()
1447 f.close()
1434
1448
1435 hgbat = os.path.join(self.bindir, 'hg.bat')
1449 hgbat = os.path.join(self.bindir, 'hg.bat')
1436 if os.path.isfile(hgbat):
1450 if os.path.isfile(hgbat):
1437 # hg.bat expects to be put in bin/scripts while run-tests.py
1451 # hg.bat expects to be put in bin/scripts while run-tests.py
1438 # installation layout put it in bin/ directly. Fix it
1452 # installation layout put it in bin/ directly. Fix it
1439 f = open(hgbat, 'rb')
1453 f = open(hgbat, 'rb')
1440 data = f.read()
1454 data = f.read()
1441 f.close()
1455 f.close()
1442 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1456 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1443 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1457 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1444 '"%~dp0python" "%~dp0hg" %*')
1458 '"%~dp0python" "%~dp0hg" %*')
1445 f = open(hgbat, 'wb')
1459 f = open(hgbat, 'wb')
1446 f.write(data)
1460 f.write(data)
1447 f.close()
1461 f.close()
1448 else:
1462 else:
1449 print 'WARNING: cannot fix hg.bat reference to python.exe'
1463 print 'WARNING: cannot fix hg.bat reference to python.exe'
1450
1464
1451 if self.options.anycoverage:
1465 if self.options.anycoverage:
1452 custom = os.path.join(self.testdir, 'sitecustomize.py')
1466 custom = os.path.join(self.testdir, 'sitecustomize.py')
1453 target = os.path.join(self.pythondir, 'sitecustomize.py')
1467 target = os.path.join(self.pythondir, 'sitecustomize.py')
1454 vlog('# Installing coverage trigger to %s' % target)
1468 vlog('# Installing coverage trigger to %s' % target)
1455 shutil.copyfile(custom, target)
1469 shutil.copyfile(custom, target)
1456 rc = os.path.join(self.testdir, '.coveragerc')
1470 rc = os.path.join(self.testdir, '.coveragerc')
1457 vlog('# Installing coverage rc to %s' % rc)
1471 vlog('# Installing coverage rc to %s' % rc)
1458 os.environ['COVERAGE_PROCESS_START'] = rc
1472 os.environ['COVERAGE_PROCESS_START'] = rc
1459 fn = os.path.join(self.inst, '..', '.coverage')
1473 fn = os.path.join(self.inst, '..', '.coverage')
1460 os.environ['COVERAGE_FILE'] = fn
1474 os.environ['COVERAGE_FILE'] = fn
1461
1475
1462 def _checkhglib(self, verb):
1476 def _checkhglib(self, verb):
1463 """Ensure that the 'mercurial' package imported by python is
1477 """Ensure that the 'mercurial' package imported by python is
1464 the one we expect it to be. If not, print a warning to stderr."""
1478 the one we expect it to be. If not, print a warning to stderr."""
1465 expecthg = os.path.join(self.pythondir, 'mercurial')
1479 expecthg = os.path.join(self.pythondir, 'mercurial')
1466 actualhg = self._gethgpath()
1480 actualhg = self._gethgpath()
1467 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1481 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1468 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1482 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1469 ' (expected %s)\n'
1483 ' (expected %s)\n'
1470 % (verb, actualhg, expecthg))
1484 % (verb, actualhg, expecthg))
1471 def _gethgpath(self):
1485 def _gethgpath(self):
1472 """Return the path to the mercurial package that is actually found by
1486 """Return the path to the mercurial package that is actually found by
1473 the current Python interpreter."""
1487 the current Python interpreter."""
1474 if self._hgpath is not None:
1488 if self._hgpath is not None:
1475 return self._hgpath
1489 return self._hgpath
1476
1490
1477 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1491 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1478 pipe = os.popen(cmd % PYTHON)
1492 pipe = os.popen(cmd % PYTHON)
1479 try:
1493 try:
1480 self._hgpath = pipe.read().strip()
1494 self._hgpath = pipe.read().strip()
1481 finally:
1495 finally:
1482 pipe.close()
1496 pipe.close()
1483
1497
1484 return self._hgpath
1498 return self._hgpath
1485
1499
1486 def _outputtimes(self):
1500 def _outputtimes(self):
1487 vlog('# Producing time report')
1501 vlog('# Producing time report')
1488 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1502 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1489 cols = '%7.3f %s'
1503 cols = '%7.3f %s'
1490 print '\n%-7s %s' % ('Time', 'Test')
1504 print '\n%-7s %s' % ('Time', 'Test')
1491 for test, timetaken in self.times:
1505 for test, timetaken in self.times:
1492 print cols % (timetaken, test)
1506 print cols % (timetaken, test)
1493
1507
1494 def _outputcoverage(self):
1508 def _outputcoverage(self):
1495 vlog('# Producing coverage report')
1509 vlog('# Producing coverage report')
1496 os.chdir(self.pythondir)
1510 os.chdir(self.pythondir)
1497
1511
1498 def covrun(*args):
1512 def covrun(*args):
1499 cmd = 'coverage %s' % ' '.join(args)
1513 cmd = 'coverage %s' % ' '.join(args)
1500 vlog('# Running: %s' % cmd)
1514 vlog('# Running: %s' % cmd)
1501 os.system(cmd)
1515 os.system(cmd)
1502
1516
1503 covrun('-c')
1517 covrun('-c')
1504 omit = ','.join(os.path.join(x, '*') for x in
1518 omit = ','.join(os.path.join(x, '*') for x in
1505 [self.bindir, self.testdir])
1519 [self.bindir, self.testdir])
1506 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1520 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1507 if self.options.htmlcov:
1521 if self.options.htmlcov:
1508 htmldir = os.path.join(self.testdir, 'htmlcov')
1522 htmldir = os.path.join(self.testdir, 'htmlcov')
1509 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1523 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1510 '"--omit=%s"' % omit)
1524 '"--omit=%s"' % omit)
1511 if self.options.annotate:
1525 if self.options.annotate:
1512 adir = os.path.join(self.testdir, 'annotated')
1526 adir = os.path.join(self.testdir, 'annotated')
1513 if not os.path.isdir(adir):
1527 if not os.path.isdir(adir):
1514 os.mkdir(adir)
1528 os.mkdir(adir)
1515 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1529 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1516
1530
1517 def _executetests(self, tests):
1531 def _executetests(self, tests):
1518 jobs = self.options.jobs
1532 jobs = self.options.jobs
1519 done = queue.Queue()
1533 done = queue.Queue()
1520 running = 0
1534 running = 0
1521 count = 0
1535 count = 0
1522
1536
1523 def job(test, count):
1537 def job(test, count):
1524 try:
1538 try:
1525 t = self._gettest(test, count)
1539 t = self._gettest(test, count)
1526 done.put(t.run())
1540 done.put(t.run())
1527 t.cleanup()
1541 t.cleanup()
1528 except KeyboardInterrupt:
1542 except KeyboardInterrupt:
1529 pass
1543 pass
1530 except: # re-raises
1544 except: # re-raises
1531 done.put(('!', test, 'run-test raised an error, see traceback'))
1545 done.put(('!', test, 'run-test raised an error, see traceback'))
1532 raise
1546 raise
1533
1547
1534 try:
1548 try:
1535 while tests or running:
1549 while tests or running:
1536 if not done.empty() or running == jobs or not tests:
1550 if not done.empty() or running == jobs or not tests:
1537 try:
1551 try:
1538 code, test, msg = done.get(True, 1)
1552 code, test, msg = done.get(True, 1)
1539 self.results[code].append((test, msg))
1553 self.results[code].append((test, msg))
1540 if self.options.first and code not in '.si':
1554 if self.options.first and code not in '.si':
1541 break
1555 break
1542 except queue.Empty:
1556 except queue.Empty:
1543 continue
1557 continue
1544 running -= 1
1558 running -= 1
1545 if tests and not running == jobs:
1559 if tests and not running == jobs:
1546 test = tests.pop(0)
1560 test = tests.pop(0)
1547 if self.options.loop:
1561 if self.options.loop:
1548 tests.append(test)
1562 tests.append(test)
1549 t = threading.Thread(target=job, name=test,
1563 t = threading.Thread(target=job, name=test,
1550 args=(test, count))
1564 args=(test, count))
1551 t.start()
1565 t.start()
1552 running += 1
1566 running += 1
1553 count += 1
1567 count += 1
1554 except KeyboardInterrupt:
1568 except KeyboardInterrupt:
1555 self.abort[0] = True
1569 self.abort[0] = True
1556
1570
1557 def _findprogram(self, program):
1571 def _findprogram(self, program):
1558 """Search PATH for a executable program"""
1572 """Search PATH for a executable program"""
1559 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1573 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1560 name = os.path.join(p, program)
1574 name = os.path.join(p, program)
1561 if os.name == 'nt' or os.access(name, os.X_OK):
1575 if os.name == 'nt' or os.access(name, os.X_OK):
1562 return name
1576 return name
1563 return None
1577 return None
1564
1578
1565 def _checktools(self):
1579 def _checktools(self):
1566 # Before we go any further, check for pre-requisite tools
1580 # Before we go any further, check for pre-requisite tools
1567 # stuff from coreutils (cat, rm, etc) are not tested
1581 # stuff from coreutils (cat, rm, etc) are not tested
1568 for p in self.REQUIREDTOOLS:
1582 for p in self.REQUIREDTOOLS:
1569 if os.name == 'nt' and not p.endswith('.exe'):
1583 if os.name == 'nt' and not p.endswith('.exe'):
1570 p += '.exe'
1584 p += '.exe'
1571 found = self._findprogram(p)
1585 found = self._findprogram(p)
1572 if found:
1586 if found:
1573 vlog("# Found prerequisite", p, "at", found)
1587 vlog("# Found prerequisite", p, "at", found)
1574 else:
1588 else:
1575 print "WARNING: Did not find prerequisite tool: %s " % p
1589 print "WARNING: Did not find prerequisite tool: %s " % p
1576
1590
1577 if __name__ == '__main__':
1591 if __name__ == '__main__':
1578 runner = TestRunner()
1592 runner = TestRunner()
1579 sys.exit(runner.run(sys.argv[1:]))
1593 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now