##// END OF EJS Templates
run-tests: add docstrings
Gregory Szorc -
r21536:92a6b16c default
parent child Browse files
Show More
@@ -1,1768 +1,1785 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("-v", "--verbose", action="store_true",
191 parser.add_option("-v", "--verbose", action="store_true",
192 help="output verbose messages")
192 help="output verbose messages")
193 parser.add_option("--view", type="string",
193 parser.add_option("--view", type="string",
194 help="external diff viewer")
194 help="external diff viewer")
195 parser.add_option("--with-hg", type="string",
195 parser.add_option("--with-hg", type="string",
196 metavar="HG",
196 metavar="HG",
197 help="test using specified hg script rather than a "
197 help="test using specified hg script rather than a "
198 "temporary installation")
198 "temporary installation")
199 parser.add_option("-3", "--py3k-warnings", action="store_true",
199 parser.add_option("-3", "--py3k-warnings", action="store_true",
200 help="enable Py3k warnings on Python 2.6+")
200 help="enable Py3k warnings on Python 2.6+")
201 parser.add_option('--extra-config-opt', action="append",
201 parser.add_option('--extra-config-opt', action="append",
202 help='set the given config opt in the test hgrc')
202 help='set the given config opt in the test hgrc')
203 parser.add_option('--random', action="store_true",
203 parser.add_option('--random', action="store_true",
204 help='run tests in random order')
204 help='run tests in random order')
205
205
206 for option, (envvar, default) in defaults.items():
206 for option, (envvar, default) in defaults.items():
207 defaults[option] = type(default)(os.environ.get(envvar, default))
207 defaults[option] = type(default)(os.environ.get(envvar, default))
208 parser.set_defaults(**defaults)
208 parser.set_defaults(**defaults)
209
209
210 return parser
210 return parser
211
211
212 def parseargs(args, parser):
212 def parseargs(args, parser):
213 """Parse arguments with our OptionParser and validate results."""
213 """Parse arguments with our OptionParser and validate results."""
214 (options, args) = parser.parse_args(args)
214 (options, args) = parser.parse_args(args)
215
215
216 # jython is always pure
216 # jython is always pure
217 if 'java' in sys.platform or '__pypy__' in sys.modules:
217 if 'java' in sys.platform or '__pypy__' in sys.modules:
218 options.pure = True
218 options.pure = True
219
219
220 if options.with_hg:
220 if options.with_hg:
221 options.with_hg = os.path.expanduser(options.with_hg)
221 options.with_hg = os.path.expanduser(options.with_hg)
222 if not (os.path.isfile(options.with_hg) and
222 if not (os.path.isfile(options.with_hg) and
223 os.access(options.with_hg, os.X_OK)):
223 os.access(options.with_hg, os.X_OK)):
224 parser.error('--with-hg must specify an executable hg script')
224 parser.error('--with-hg must specify an executable hg script')
225 if not os.path.basename(options.with_hg) == 'hg':
225 if not os.path.basename(options.with_hg) == 'hg':
226 sys.stderr.write('warning: --with-hg should specify an hg script\n')
226 sys.stderr.write('warning: --with-hg should specify an hg script\n')
227 if options.local:
227 if options.local:
228 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
228 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
229 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
229 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
230 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
230 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
231 parser.error('--local specified, but %r not found or not executable'
231 parser.error('--local specified, but %r not found or not executable'
232 % hgbin)
232 % hgbin)
233 options.with_hg = hgbin
233 options.with_hg = hgbin
234
234
235 options.anycoverage = options.cover or options.annotate or options.htmlcov
235 options.anycoverage = options.cover or options.annotate or options.htmlcov
236 if options.anycoverage:
236 if options.anycoverage:
237 try:
237 try:
238 import coverage
238 import coverage
239 covver = version.StrictVersion(coverage.__version__).version
239 covver = version.StrictVersion(coverage.__version__).version
240 if covver < (3, 3):
240 if covver < (3, 3):
241 parser.error('coverage options require coverage 3.3 or later')
241 parser.error('coverage options require coverage 3.3 or later')
242 except ImportError:
242 except ImportError:
243 parser.error('coverage options now require the coverage package')
243 parser.error('coverage options now require the coverage package')
244
244
245 if options.anycoverage and options.local:
245 if options.anycoverage and options.local:
246 # this needs some path mangling somewhere, I guess
246 # this needs some path mangling somewhere, I guess
247 parser.error("sorry, coverage options do not work when --local "
247 parser.error("sorry, coverage options do not work when --local "
248 "is specified")
248 "is specified")
249
249
250 global verbose
250 global verbose
251 if options.verbose:
251 if options.verbose:
252 verbose = ''
252 verbose = ''
253
253
254 if options.tmpdir:
254 if options.tmpdir:
255 options.tmpdir = os.path.expanduser(options.tmpdir)
255 options.tmpdir = os.path.expanduser(options.tmpdir)
256
256
257 if options.jobs < 1:
257 if options.jobs < 1:
258 parser.error('--jobs must be positive')
258 parser.error('--jobs must be positive')
259 if options.interactive and options.debug:
259 if options.interactive and options.debug:
260 parser.error("-i/--interactive and -d/--debug are incompatible")
260 parser.error("-i/--interactive and -d/--debug are incompatible")
261 if options.debug:
261 if options.debug:
262 if options.timeout != defaults['timeout']:
262 if options.timeout != defaults['timeout']:
263 sys.stderr.write(
263 sys.stderr.write(
264 'warning: --timeout option ignored with --debug\n')
264 'warning: --timeout option ignored with --debug\n')
265 options.timeout = 0
265 options.timeout = 0
266 if options.py3k_warnings:
266 if options.py3k_warnings:
267 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
267 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
268 parser.error('--py3k-warnings can only be used on Python 2.6+')
268 parser.error('--py3k-warnings can only be used on Python 2.6+')
269 if options.blacklist:
269 if options.blacklist:
270 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
270 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
271 if options.whitelist:
271 if options.whitelist:
272 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
272 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
273 else:
273 else:
274 options.whitelisted = {}
274 options.whitelisted = {}
275
275
276 return (options, args)
276 return (options, args)
277
277
278 def rename(src, dst):
278 def rename(src, dst):
279 """Like os.rename(), trade atomicity and opened files friendliness
279 """Like os.rename(), trade atomicity and opened files friendliness
280 for existing destination support.
280 for existing destination support.
281 """
281 """
282 shutil.copy(src, dst)
282 shutil.copy(src, dst)
283 os.remove(src)
283 os.remove(src)
284
284
285 def getdiff(expected, output, ref, err):
285 def getdiff(expected, output, ref, err):
286 servefail = False
286 servefail = False
287 lines = []
287 lines = []
288 for line in difflib.unified_diff(expected, output, ref, err):
288 for line in difflib.unified_diff(expected, output, ref, err):
289 lines.append(line)
289 lines.append(line)
290 if not servefail and line.startswith(
290 if not servefail and line.startswith(
291 '+ abort: child process failed to start'):
291 '+ abort: child process failed to start'):
292 servefail = True
292 servefail = True
293
293
294 return servefail, lines
294 return servefail, lines
295
295
296 verbose = False
296 verbose = False
297 def vlog(*msg):
297 def vlog(*msg):
298 """Log only when in verbose mode."""
298 """Log only when in verbose mode."""
299 if verbose is False:
299 if verbose is False:
300 return
300 return
301
301
302 return log(*msg)
302 return log(*msg)
303
303
304 def log(*msg):
304 def log(*msg):
305 """Log something to stdout.
305 """Log something to stdout.
306
306
307 Arguments are strings to print.
307 Arguments are strings to print.
308 """
308 """
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 terminate(proc):
318 def terminate(proc):
319 """Terminate subprocess (with fallback for Python versions < 2.6)"""
319 """Terminate subprocess (with fallback for Python versions < 2.6)"""
320 vlog('# Terminating process %d' % proc.pid)
320 vlog('# Terminating process %d' % proc.pid)
321 try:
321 try:
322 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
322 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
323 except OSError:
323 except OSError:
324 pass
324 pass
325
325
326 def killdaemons(pidfile):
326 def killdaemons(pidfile):
327 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
327 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
328 logfn=vlog)
328 logfn=vlog)
329
329
330 class Test(unittest.TestCase):
330 class Test(unittest.TestCase):
331 """Encapsulates a single, runnable test.
331 """Encapsulates a single, runnable test.
332
332
333 While this class conforms to the unittest.TestCase API, it differs in that
333 While this class conforms to the unittest.TestCase API, it differs in that
334 instances need to be instantiated manually. (Typically, unittest.TestCase
334 instances need to be instantiated manually. (Typically, unittest.TestCase
335 classes are instantiated automatically by scanning modules.)
335 classes are instantiated automatically by scanning modules.)
336 """
336 """
337
337
338 # Status code reserved for skipped tests (used by hghave).
338 # Status code reserved for skipped tests (used by hghave).
339 SKIPPED_STATUS = 80
339 SKIPPED_STATUS = 80
340
340
341 def __init__(self, path, tmpdir, keeptmpdir=False,
341 def __init__(self, path, tmpdir, keeptmpdir=False,
342 debug=False,
342 debug=False,
343 timeout=defaults['timeout'],
343 timeout=defaults['timeout'],
344 startport=defaults['port'], extraconfigopts=None,
344 startport=defaults['port'], extraconfigopts=None,
345 py3kwarnings=False, shell=None):
345 py3kwarnings=False, shell=None):
346 """Create a test from parameters.
346 """Create a test from parameters.
347
347
348 path is the full path to the file defining the test.
348 path is the full path to the file defining the test.
349
349
350 tmpdir is the main temporary directory to use for this test.
350 tmpdir is the main temporary directory to use for this test.
351
351
352 keeptmpdir determines whether to keep the test's temporary directory
352 keeptmpdir determines whether to keep the test's temporary directory
353 after execution. It defaults to removal (False).
353 after execution. It defaults to removal (False).
354
354
355 debug mode will make the test execute verbosely, with unfiltered
355 debug mode will make the test execute verbosely, with unfiltered
356 output.
356 output.
357
357
358 timeout controls the maximum run time of the test. It is ignored when
358 timeout controls the maximum run time of the test. It is ignored when
359 debug is True.
359 debug is True.
360
360
361 startport controls the starting port number to use for this test. Each
361 startport controls the starting port number to use for this test. Each
362 test will reserve 3 port numbers for execution. It is the caller's
362 test will reserve 3 port numbers for execution. It is the caller's
363 responsibility to allocate a non-overlapping port range to Test
363 responsibility to allocate a non-overlapping port range to Test
364 instances.
364 instances.
365
365
366 extraconfigopts is an iterable of extra hgrc config options. Values
366 extraconfigopts is an iterable of extra hgrc config options. Values
367 must have the form "key=value" (something understood by hgrc). Values
367 must have the form "key=value" (something understood by hgrc). Values
368 of the form "foo.key=value" will result in "[foo] key=value".
368 of the form "foo.key=value" will result in "[foo] key=value".
369
369
370 py3kwarnings enables Py3k warnings.
370 py3kwarnings enables Py3k warnings.
371
371
372 shell is the shell to execute tests in.
372 shell is the shell to execute tests in.
373 """
373 """
374
374
375 self.path = path
375 self.path = path
376 self.name = os.path.basename(path)
376 self.name = os.path.basename(path)
377 self._testdir = os.path.dirname(path)
377 self._testdir = os.path.dirname(path)
378 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
378 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
379
379
380 self._threadtmp = tmpdir
380 self._threadtmp = tmpdir
381 self._keeptmpdir = keeptmpdir
381 self._keeptmpdir = keeptmpdir
382 self._debug = debug
382 self._debug = debug
383 self._timeout = timeout
383 self._timeout = timeout
384 self._startport = startport
384 self._startport = startport
385 self._extraconfigopts = extraconfigopts or []
385 self._extraconfigopts = extraconfigopts or []
386 self._py3kwarnings = py3kwarnings
386 self._py3kwarnings = py3kwarnings
387 self._shell = shell
387 self._shell = shell
388
388
389 self._aborted = False
389 self._aborted = False
390 self._daemonpids = []
390 self._daemonpids = []
391 self._finished = None
391 self._finished = None
392 self._ret = None
392 self._ret = None
393 self._out = None
393 self._out = None
394 self._skipped = None
394 self._skipped = None
395 self._testtmp = None
395 self._testtmp = None
396
396
397 # If we're not in --debug mode and reference output file exists,
397 # If we're not in --debug mode and reference output file exists,
398 # check test output against it.
398 # check test output against it.
399 if debug:
399 if debug:
400 self._refout = None # to match "out is None"
400 self._refout = None # to match "out is None"
401 elif os.path.exists(self.refpath):
401 elif os.path.exists(self.refpath):
402 f = open(self.refpath, 'r')
402 f = open(self.refpath, 'r')
403 self._refout = f.read().splitlines(True)
403 self._refout = f.read().splitlines(True)
404 f.close()
404 f.close()
405 else:
405 else:
406 self._refout = []
406 self._refout = []
407
407
408 def __str__(self):
408 def __str__(self):
409 return self.name
409 return self.name
410
410
411 def shortDescription(self):
411 def shortDescription(self):
412 return self.name
412 return self.name
413
413
414 def setUp(self):
414 def setUp(self):
415 """Tasks to perform before run()."""
415 """Tasks to perform before run()."""
416 self._finished = False
416 self._finished = False
417 self._ret = None
417 self._ret = None
418 self._out = None
418 self._out = None
419 self._skipped = None
419 self._skipped = None
420
420
421 try:
421 try:
422 os.mkdir(self._threadtmp)
422 os.mkdir(self._threadtmp)
423 except OSError, e:
423 except OSError, e:
424 if e.errno != errno.EEXIST:
424 if e.errno != errno.EEXIST:
425 raise
425 raise
426
426
427 self._testtmp = os.path.join(self._threadtmp,
427 self._testtmp = os.path.join(self._threadtmp,
428 os.path.basename(self.path))
428 os.path.basename(self.path))
429 os.mkdir(self._testtmp)
429 os.mkdir(self._testtmp)
430
430
431 # Remove any previous output files.
431 # Remove any previous output files.
432 if os.path.exists(self.errpath):
432 if os.path.exists(self.errpath):
433 os.remove(self.errpath)
433 os.remove(self.errpath)
434
434
435 def run(self, result):
435 def run(self, result):
436 """Run this test and report results against a TestResult instance."""
437 # This function is extremely similar to unittest.TestCase.run(). Once
438 # we require Python 2.7 (or at least its version of unittest), this
439 # function can largely go away.
436 self._result = result
440 self._result = result
437 result.startTest(self)
441 result.startTest(self)
438 try:
442 try:
439 try:
443 try:
440 self.setUp()
444 self.setUp()
441 except (KeyboardInterrupt, SystemExit):
445 except (KeyboardInterrupt, SystemExit):
442 self._aborted = True
446 self._aborted = True
443 raise
447 raise
444 except Exception:
448 except Exception:
445 result.addError(self, sys.exc_info())
449 result.addError(self, sys.exc_info())
446 return
450 return
447
451
448 success = False
452 success = False
449 try:
453 try:
450 self.runTest()
454 self.runTest()
451 except KeyboardInterrupt:
455 except KeyboardInterrupt:
452 self._aborted = True
456 self._aborted = True
453 raise
457 raise
454 except SkipTest, e:
458 except SkipTest, e:
455 result.addSkip(self, str(e))
459 result.addSkip(self, str(e))
456 except IgnoreTest, e:
460 except IgnoreTest, e:
457 result.addIgnore(self, str(e))
461 result.addIgnore(self, str(e))
458 except WarnTest, e:
462 except WarnTest, e:
459 result.addWarn(self, str(e))
463 result.addWarn(self, str(e))
460 except self.failureException, e:
464 except self.failureException, e:
461 # This differs from unittest in that we don't capture
465 # This differs from unittest in that we don't capture
462 # the stack trace. This is for historical reasons and
466 # the stack trace. This is for historical reasons and
463 # this decision could be revisted in the future,
467 # this decision could be revisted in the future,
464 # especially for PythonTest instances.
468 # especially for PythonTest instances.
465 result.addFailure(self, str(e))
469 result.addFailure(self, str(e))
466 except Exception:
470 except Exception:
467 result.addError(self, sys.exc_info())
471 result.addError(self, sys.exc_info())
468 else:
472 else:
469 success = True
473 success = True
470
474
471 try:
475 try:
472 self.tearDown()
476 self.tearDown()
473 except (KeyboardInterrupt, SystemExit):
477 except (KeyboardInterrupt, SystemExit):
474 self._aborted = True
478 self._aborted = True
475 raise
479 raise
476 except Exception:
480 except Exception:
477 result.addError(self, sys.exc_info())
481 result.addError(self, sys.exc_info())
478 success = False
482 success = False
479
483
480 if success:
484 if success:
481 result.addSuccess(self)
485 result.addSuccess(self)
482 finally:
486 finally:
483 result.stopTest(self, interrupted=self._aborted)
487 result.stopTest(self, interrupted=self._aborted)
484
488
485 def runTest(self):
489 def runTest(self):
486 """Run this test instance.
490 """Run this test instance.
487
491
488 This will return a tuple describing the result of the test.
492 This will return a tuple describing the result of the test.
489 """
493 """
490 replacements = self._getreplacements()
494 replacements = self._getreplacements()
491 env = self._getenv()
495 env = self._getenv()
492 self._daemonpids.append(env['DAEMON_PIDS'])
496 self._daemonpids.append(env['DAEMON_PIDS'])
493 self._createhgrc(env['HGRCPATH'])
497 self._createhgrc(env['HGRCPATH'])
494
498
495 vlog('# Test', self.name)
499 vlog('# Test', self.name)
496
500
497 ret, out = self._run(replacements, env)
501 ret, out = self._run(replacements, env)
498 self._finished = True
502 self._finished = True
499 self._ret = ret
503 self._ret = ret
500 self._out = out
504 self._out = out
501
505
502 def describe(ret):
506 def describe(ret):
503 if ret < 0:
507 if ret < 0:
504 return 'killed by signal: %d' % -ret
508 return 'killed by signal: %d' % -ret
505 return 'returned error code %d' % ret
509 return 'returned error code %d' % ret
506
510
507 self._skipped = False
511 self._skipped = False
508
512
509 if ret == self.SKIPPED_STATUS:
513 if ret == self.SKIPPED_STATUS:
510 if out is None: # Debug mode, nothing to parse.
514 if out is None: # Debug mode, nothing to parse.
511 missing = ['unknown']
515 missing = ['unknown']
512 failed = None
516 failed = None
513 else:
517 else:
514 missing, failed = TTest.parsehghaveoutput(out)
518 missing, failed = TTest.parsehghaveoutput(out)
515
519
516 if not missing:
520 if not missing:
517 missing = ['irrelevant']
521 missing = ['irrelevant']
518
522
519 if failed:
523 if failed:
520 self.fail('hg have failed checking for %s' % failed[-1])
524 self.fail('hg have failed checking for %s' % failed[-1])
521 else:
525 else:
522 self._skipped = True
526 self._skipped = True
523 raise SkipTest(missing[-1])
527 raise SkipTest(missing[-1])
524 elif ret == 'timeout':
528 elif ret == 'timeout':
525 self.fail('timed out')
529 self.fail('timed out')
526 elif ret is False:
530 elif ret is False:
527 raise WarnTest('no result code from test')
531 raise WarnTest('no result code from test')
528 elif out != self._refout:
532 elif out != self._refout:
529 # The result object handles diff calculation for us.
533 # The result object handles diff calculation for us.
530 self._result.addOutputMismatch(self, ret, out, self._refout)
534 self._result.addOutputMismatch(self, ret, out, self._refout)
531
535
532 if ret:
536 if ret:
533 msg = 'output changed and ' + describe(ret)
537 msg = 'output changed and ' + describe(ret)
534 else:
538 else:
535 msg = 'output changed'
539 msg = 'output changed'
536
540
537 if (ret != 0 or out != self._refout) and not self._skipped \
541 if (ret != 0 or out != self._refout) and not self._skipped \
538 and not self._debug:
542 and not self._debug:
539 f = open(self.errpath, 'wb')
543 f = open(self.errpath, 'wb')
540 for line in out:
544 for line in out:
541 f.write(line)
545 f.write(line)
542 f.close()
546 f.close()
543
547
544 self.fail(msg)
548 self.fail(msg)
545 elif ret:
549 elif ret:
546 self.fail(describe(ret))
550 self.fail(describe(ret))
547
551
548 def tearDown(self):
552 def tearDown(self):
549 """Tasks to perform after run()."""
553 """Tasks to perform after run()."""
550 for entry in self._daemonpids:
554 for entry in self._daemonpids:
551 killdaemons(entry)
555 killdaemons(entry)
552 self._daemonpids = []
556 self._daemonpids = []
553
557
554 if not self._keeptmpdir:
558 if not self._keeptmpdir:
555 shutil.rmtree(self._testtmp, True)
559 shutil.rmtree(self._testtmp, True)
556 shutil.rmtree(self._threadtmp, True)
560 shutil.rmtree(self._threadtmp, True)
557
561
558 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
562 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
559 and not self._debug and self._out:
563 and not self._debug and self._out:
560 f = open(self.errpath, 'wb')
564 f = open(self.errpath, 'wb')
561 for line in self._out:
565 for line in self._out:
562 f.write(line)
566 f.write(line)
563 f.close()
567 f.close()
564
568
565 vlog("# Ret was:", self._ret)
569 vlog("# Ret was:", self._ret)
566
570
567 def _run(self, replacements, env):
571 def _run(self, replacements, env):
568 # This should be implemented in child classes to run tests.
572 # This should be implemented in child classes to run tests.
569 raise SkipTest('unknown test type')
573 raise SkipTest('unknown test type')
570
574
571 def abort(self):
575 def abort(self):
572 """Terminate execution of this test."""
576 """Terminate execution of this test."""
573 self._aborted = True
577 self._aborted = True
574
578
575 def _getreplacements(self):
579 def _getreplacements(self):
580 """Obtain a mapping of text replacements to apply to test output.
581
582 Test output needs to be normalized so it can be compared to expected
583 output. This function defines how some of that normalization will
584 occur.
585 """
576 r = [
586 r = [
577 (r':%s\b' % self._startport, ':$HGPORT'),
587 (r':%s\b' % self._startport, ':$HGPORT'),
578 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
588 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
579 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
589 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
580 ]
590 ]
581
591
582 if os.name == 'nt':
592 if os.name == 'nt':
583 r.append(
593 r.append(
584 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
594 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
585 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
595 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
586 for c in self._testtmp), '$TESTTMP'))
596 for c in self._testtmp), '$TESTTMP'))
587 else:
597 else:
588 r.append((re.escape(self._testtmp), '$TESTTMP'))
598 r.append((re.escape(self._testtmp), '$TESTTMP'))
589
599
590 return r
600 return r
591
601
592 def _getenv(self):
602 def _getenv(self):
603 """Obtain environment variables to use during test execution."""
593 env = os.environ.copy()
604 env = os.environ.copy()
594 env['TESTTMP'] = self._testtmp
605 env['TESTTMP'] = self._testtmp
595 env['HOME'] = self._testtmp
606 env['HOME'] = self._testtmp
596 env["HGPORT"] = str(self._startport)
607 env["HGPORT"] = str(self._startport)
597 env["HGPORT1"] = str(self._startport + 1)
608 env["HGPORT1"] = str(self._startport + 1)
598 env["HGPORT2"] = str(self._startport + 2)
609 env["HGPORT2"] = str(self._startport + 2)
599 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
610 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
600 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
611 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
601 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
612 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
602 env["HGMERGE"] = "internal:merge"
613 env["HGMERGE"] = "internal:merge"
603 env["HGUSER"] = "test"
614 env["HGUSER"] = "test"
604 env["HGENCODING"] = "ascii"
615 env["HGENCODING"] = "ascii"
605 env["HGENCODINGMODE"] = "strict"
616 env["HGENCODINGMODE"] = "strict"
606
617
607 # Reset some environment variables to well-known values so that
618 # Reset some environment variables to well-known values so that
608 # the tests produce repeatable output.
619 # the tests produce repeatable output.
609 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
620 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
610 env['TZ'] = 'GMT'
621 env['TZ'] = 'GMT'
611 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
622 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
612 env['COLUMNS'] = '80'
623 env['COLUMNS'] = '80'
613 env['TERM'] = 'xterm'
624 env['TERM'] = 'xterm'
614
625
615 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
626 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
616 'NO_PROXY').split():
627 'NO_PROXY').split():
617 if k in env:
628 if k in env:
618 del env[k]
629 del env[k]
619
630
620 # unset env related to hooks
631 # unset env related to hooks
621 for k in env.keys():
632 for k in env.keys():
622 if k.startswith('HG_'):
633 if k.startswith('HG_'):
623 del env[k]
634 del env[k]
624
635
625 return env
636 return env
626
637
627 def _createhgrc(self, path):
638 def _createhgrc(self, path):
628 # create a fresh hgrc
639 """Create an hgrc file for this test."""
629 hgrc = open(path, 'w')
640 hgrc = open(path, 'w')
630 hgrc.write('[ui]\n')
641 hgrc.write('[ui]\n')
631 hgrc.write('slash = True\n')
642 hgrc.write('slash = True\n')
632 hgrc.write('interactive = False\n')
643 hgrc.write('interactive = False\n')
633 hgrc.write('[defaults]\n')
644 hgrc.write('[defaults]\n')
634 hgrc.write('backout = -d "0 0"\n')
645 hgrc.write('backout = -d "0 0"\n')
635 hgrc.write('commit = -d "0 0"\n')
646 hgrc.write('commit = -d "0 0"\n')
636 hgrc.write('shelve = --date "0 0"\n')
647 hgrc.write('shelve = --date "0 0"\n')
637 hgrc.write('tag = -d "0 0"\n')
648 hgrc.write('tag = -d "0 0"\n')
638 for opt in self._extraconfigopts:
649 for opt in self._extraconfigopts:
639 section, key = opt.split('.', 1)
650 section, key = opt.split('.', 1)
640 assert '=' in key, ('extra config opt %s must '
651 assert '=' in key, ('extra config opt %s must '
641 'have an = for assignment' % opt)
652 'have an = for assignment' % opt)
642 hgrc.write('[%s]\n%s\n' % (section, key))
653 hgrc.write('[%s]\n%s\n' % (section, key))
643 hgrc.close()
654 hgrc.close()
644
655
645 def fail(self, msg):
656 def fail(self, msg):
646 # unittest differentiates between errored and failed.
657 # unittest differentiates between errored and failed.
647 # Failed is denoted by AssertionError (by default at least).
658 # Failed is denoted by AssertionError (by default at least).
648 raise AssertionError(msg)
659 raise AssertionError(msg)
649
660
650 class PythonTest(Test):
661 class PythonTest(Test):
651 """A Python-based test."""
662 """A Python-based test."""
652
663
653 @property
664 @property
654 def refpath(self):
665 def refpath(self):
655 return os.path.join(self._testdir, '%s.out' % self.name)
666 return os.path.join(self._testdir, '%s.out' % self.name)
656
667
657 def _run(self, replacements, env):
668 def _run(self, replacements, env):
658 py3kswitch = self._py3kwarnings and ' -3' or ''
669 py3kswitch = self._py3kwarnings and ' -3' or ''
659 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
670 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
660 vlog("# Running", cmd)
671 vlog("# Running", cmd)
661 if os.name == 'nt':
672 if os.name == 'nt':
662 replacements.append((r'\r\n', '\n'))
673 replacements.append((r'\r\n', '\n'))
663 result = run(cmd, self._testtmp, replacements, env,
674 result = run(cmd, self._testtmp, replacements, env,
664 debug=self._debug, timeout=self._timeout)
675 debug=self._debug, timeout=self._timeout)
665 if self._aborted:
676 if self._aborted:
666 raise KeyboardInterrupt()
677 raise KeyboardInterrupt()
667
678
668 return result
679 return result
669
680
670 class TTest(Test):
681 class TTest(Test):
671 """A "t test" is a test backed by a .t file."""
682 """A "t test" is a test backed by a .t file."""
672
683
673 SKIPPED_PREFIX = 'skipped: '
684 SKIPPED_PREFIX = 'skipped: '
674 FAILED_PREFIX = 'hghave check failed: '
685 FAILED_PREFIX = 'hghave check failed: '
675 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
686 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
676
687
677 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
688 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
678 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
689 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
679 {'\\': '\\\\', '\r': r'\r'})
690 {'\\': '\\\\', '\r': r'\r'})
680
691
681 @property
692 @property
682 def refpath(self):
693 def refpath(self):
683 return os.path.join(self._testdir, self.name)
694 return os.path.join(self._testdir, self.name)
684
695
685 def _run(self, replacements, env):
696 def _run(self, replacements, env):
686 f = open(self.path)
697 f = open(self.path)
687 lines = f.readlines()
698 lines = f.readlines()
688 f.close()
699 f.close()
689
700
690 salt, script, after, expected = self._parsetest(lines)
701 salt, script, after, expected = self._parsetest(lines)
691
702
692 # Write out the generated script.
703 # Write out the generated script.
693 fname = '%s.sh' % self._testtmp
704 fname = '%s.sh' % self._testtmp
694 f = open(fname, 'w')
705 f = open(fname, 'w')
695 for l in script:
706 for l in script:
696 f.write(l)
707 f.write(l)
697 f.close()
708 f.close()
698
709
699 cmd = '%s "%s"' % (self._shell, fname)
710 cmd = '%s "%s"' % (self._shell, fname)
700 vlog("# Running", cmd)
711 vlog("# Running", cmd)
701
712
702 exitcode, output = run(cmd, self._testtmp, replacements, env,
713 exitcode, output = run(cmd, self._testtmp, replacements, env,
703 debug=self._debug, timeout=self._timeout)
714 debug=self._debug, timeout=self._timeout)
704
715
705 if self._aborted:
716 if self._aborted:
706 raise KeyboardInterrupt()
717 raise KeyboardInterrupt()
707
718
708 # Do not merge output if skipped. Return hghave message instead.
719 # Do not merge output if skipped. Return hghave message instead.
709 # Similarly, with --debug, output is None.
720 # Similarly, with --debug, output is None.
710 if exitcode == self.SKIPPED_STATUS or output is None:
721 if exitcode == self.SKIPPED_STATUS or output is None:
711 return exitcode, output
722 return exitcode, output
712
723
713 return self._processoutput(exitcode, output, salt, after, expected)
724 return self._processoutput(exitcode, output, salt, after, expected)
714
725
715 def _hghave(self, reqs):
726 def _hghave(self, reqs):
716 # TODO do something smarter when all other uses of hghave are gone.
727 # TODO do something smarter when all other uses of hghave are gone.
717 tdir = self._testdir.replace('\\', '/')
728 tdir = self._testdir.replace('\\', '/')
718 proc = Popen4('%s -c "%s/hghave %s"' %
729 proc = Popen4('%s -c "%s/hghave %s"' %
719 (self._shell, tdir, ' '.join(reqs)),
730 (self._shell, tdir, ' '.join(reqs)),
720 self._testtmp, 0)
731 self._testtmp, 0)
721 stdout, stderr = proc.communicate()
732 stdout, stderr = proc.communicate()
722 ret = proc.wait()
733 ret = proc.wait()
723 if wifexited(ret):
734 if wifexited(ret):
724 ret = os.WEXITSTATUS(ret)
735 ret = os.WEXITSTATUS(ret)
725 if ret == 2:
736 if ret == 2:
726 print stdout
737 print stdout
727 sys.exit(1)
738 sys.exit(1)
728
739
729 return ret == 0
740 return ret == 0
730
741
731 def _parsetest(self, lines):
742 def _parsetest(self, lines):
732 # We generate a shell script which outputs unique markers to line
743 # We generate a shell script which outputs unique markers to line
733 # up script results with our source. These markers include input
744 # up script results with our source. These markers include input
734 # line number and the last return code.
745 # line number and the last return code.
735 salt = "SALT" + str(time.time())
746 salt = "SALT" + str(time.time())
736 def addsalt(line, inpython):
747 def addsalt(line, inpython):
737 if inpython:
748 if inpython:
738 script.append('%s %d 0\n' % (salt, line))
749 script.append('%s %d 0\n' % (salt, line))
739 else:
750 else:
740 script.append('echo %s %s $?\n' % (salt, line))
751 script.append('echo %s %s $?\n' % (salt, line))
741
752
742 script = []
753 script = []
743
754
744 # After we run the shell script, we re-unify the script output
755 # After we run the shell script, we re-unify the script output
745 # with non-active parts of the source, with synchronization by our
756 # with non-active parts of the source, with synchronization by our
746 # SALT line number markers. The after table contains the non-active
757 # SALT line number markers. The after table contains the non-active
747 # components, ordered by line number.
758 # components, ordered by line number.
748 after = {}
759 after = {}
749
760
750 # Expected shell script output.
761 # Expected shell script output.
751 expected = {}
762 expected = {}
752
763
753 pos = prepos = -1
764 pos = prepos = -1
754
765
755 # True or False when in a true or false conditional section
766 # True or False when in a true or false conditional section
756 skipping = None
767 skipping = None
757
768
758 # We keep track of whether or not we're in a Python block so we
769 # We keep track of whether or not we're in a Python block so we
759 # can generate the surrounding doctest magic.
770 # can generate the surrounding doctest magic.
760 inpython = False
771 inpython = False
761
772
762 if self._debug:
773 if self._debug:
763 script.append('set -x\n')
774 script.append('set -x\n')
764 if os.getenv('MSYSTEM'):
775 if os.getenv('MSYSTEM'):
765 script.append('alias pwd="pwd -W"\n')
776 script.append('alias pwd="pwd -W"\n')
766
777
767 for n, l in enumerate(lines):
778 for n, l in enumerate(lines):
768 if not l.endswith('\n'):
779 if not l.endswith('\n'):
769 l += '\n'
780 l += '\n'
770 if l.startswith('#if'):
781 if l.startswith('#if'):
771 lsplit = l.split()
782 lsplit = l.split()
772 if len(lsplit) < 2 or lsplit[0] != '#if':
783 if len(lsplit) < 2 or lsplit[0] != '#if':
773 after.setdefault(pos, []).append(' !!! invalid #if\n')
784 after.setdefault(pos, []).append(' !!! invalid #if\n')
774 if skipping is not None:
785 if skipping is not None:
775 after.setdefault(pos, []).append(' !!! nested #if\n')
786 after.setdefault(pos, []).append(' !!! nested #if\n')
776 skipping = not self._hghave(lsplit[1:])
787 skipping = not self._hghave(lsplit[1:])
777 after.setdefault(pos, []).append(l)
788 after.setdefault(pos, []).append(l)
778 elif l.startswith('#else'):
789 elif l.startswith('#else'):
779 if skipping is None:
790 if skipping is None:
780 after.setdefault(pos, []).append(' !!! missing #if\n')
791 after.setdefault(pos, []).append(' !!! missing #if\n')
781 skipping = not skipping
792 skipping = not skipping
782 after.setdefault(pos, []).append(l)
793 after.setdefault(pos, []).append(l)
783 elif l.startswith('#endif'):
794 elif l.startswith('#endif'):
784 if skipping is None:
795 if skipping is None:
785 after.setdefault(pos, []).append(' !!! missing #if\n')
796 after.setdefault(pos, []).append(' !!! missing #if\n')
786 skipping = None
797 skipping = None
787 after.setdefault(pos, []).append(l)
798 after.setdefault(pos, []).append(l)
788 elif skipping:
799 elif skipping:
789 after.setdefault(pos, []).append(l)
800 after.setdefault(pos, []).append(l)
790 elif l.startswith(' >>> '): # python inlines
801 elif l.startswith(' >>> '): # python inlines
791 after.setdefault(pos, []).append(l)
802 after.setdefault(pos, []).append(l)
792 prepos = pos
803 prepos = pos
793 pos = n
804 pos = n
794 if not inpython:
805 if not inpython:
795 # We've just entered a Python block. Add the header.
806 # We've just entered a Python block. Add the header.
796 inpython = True
807 inpython = True
797 addsalt(prepos, False) # Make sure we report the exit code.
808 addsalt(prepos, False) # Make sure we report the exit code.
798 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
809 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
799 addsalt(n, True)
810 addsalt(n, True)
800 script.append(l[2:])
811 script.append(l[2:])
801 elif l.startswith(' ... '): # python inlines
812 elif l.startswith(' ... '): # python inlines
802 after.setdefault(prepos, []).append(l)
813 after.setdefault(prepos, []).append(l)
803 script.append(l[2:])
814 script.append(l[2:])
804 elif l.startswith(' $ '): # commands
815 elif l.startswith(' $ '): # commands
805 if inpython:
816 if inpython:
806 script.append('EOF\n')
817 script.append('EOF\n')
807 inpython = False
818 inpython = False
808 after.setdefault(pos, []).append(l)
819 after.setdefault(pos, []).append(l)
809 prepos = pos
820 prepos = pos
810 pos = n
821 pos = n
811 addsalt(n, False)
822 addsalt(n, False)
812 cmd = l[4:].split()
823 cmd = l[4:].split()
813 if len(cmd) == 2 and cmd[0] == 'cd':
824 if len(cmd) == 2 and cmd[0] == 'cd':
814 l = ' $ cd %s || exit 1\n' % cmd[1]
825 l = ' $ cd %s || exit 1\n' % cmd[1]
815 script.append(l[4:])
826 script.append(l[4:])
816 elif l.startswith(' > '): # continuations
827 elif l.startswith(' > '): # continuations
817 after.setdefault(prepos, []).append(l)
828 after.setdefault(prepos, []).append(l)
818 script.append(l[4:])
829 script.append(l[4:])
819 elif l.startswith(' '): # results
830 elif l.startswith(' '): # results
820 # Queue up a list of expected results.
831 # Queue up a list of expected results.
821 expected.setdefault(pos, []).append(l[2:])
832 expected.setdefault(pos, []).append(l[2:])
822 else:
833 else:
823 if inpython:
834 if inpython:
824 script.append('EOF\n')
835 script.append('EOF\n')
825 inpython = False
836 inpython = False
826 # Non-command/result. Queue up for merged output.
837 # Non-command/result. Queue up for merged output.
827 after.setdefault(pos, []).append(l)
838 after.setdefault(pos, []).append(l)
828
839
829 if inpython:
840 if inpython:
830 script.append('EOF\n')
841 script.append('EOF\n')
831 if skipping is not None:
842 if skipping is not None:
832 after.setdefault(pos, []).append(' !!! missing #endif\n')
843 after.setdefault(pos, []).append(' !!! missing #endif\n')
833 addsalt(n + 1, False)
844 addsalt(n + 1, False)
834
845
835 return salt, script, after, expected
846 return salt, script, after, expected
836
847
837 def _processoutput(self, exitcode, output, salt, after, expected):
848 def _processoutput(self, exitcode, output, salt, after, expected):
838 # Merge the script output back into a unified test.
849 # Merge the script output back into a unified test.
839 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
850 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
840 if exitcode != 0:
851 if exitcode != 0:
841 warnonly = 3
852 warnonly = 3
842
853
843 pos = -1
854 pos = -1
844 postout = []
855 postout = []
845 for l in output:
856 for l in output:
846 lout, lcmd = l, None
857 lout, lcmd = l, None
847 if salt in l:
858 if salt in l:
848 lout, lcmd = l.split(salt, 1)
859 lout, lcmd = l.split(salt, 1)
849
860
850 if lout:
861 if lout:
851 if not lout.endswith('\n'):
862 if not lout.endswith('\n'):
852 lout += ' (no-eol)\n'
863 lout += ' (no-eol)\n'
853
864
854 # Find the expected output at the current position.
865 # Find the expected output at the current position.
855 el = None
866 el = None
856 if expected.get(pos, None):
867 if expected.get(pos, None):
857 el = expected[pos].pop(0)
868 el = expected[pos].pop(0)
858
869
859 r = TTest.linematch(el, lout)
870 r = TTest.linematch(el, lout)
860 if isinstance(r, str):
871 if isinstance(r, str):
861 if r == '+glob':
872 if r == '+glob':
862 lout = el[:-1] + ' (glob)\n'
873 lout = el[:-1] + ' (glob)\n'
863 r = '' # Warn only this line.
874 r = '' # Warn only this line.
864 elif r == '-glob':
875 elif r == '-glob':
865 lout = ''.join(el.rsplit(' (glob)', 1))
876 lout = ''.join(el.rsplit(' (glob)', 1))
866 r = '' # Warn only this line.
877 r = '' # Warn only this line.
867 else:
878 else:
868 log('\ninfo, unknown linematch result: %r\n' % r)
879 log('\ninfo, unknown linematch result: %r\n' % r)
869 r = False
880 r = False
870 if r:
881 if r:
871 postout.append(' ' + el)
882 postout.append(' ' + el)
872 else:
883 else:
873 if self.NEEDESCAPE(lout):
884 if self.NEEDESCAPE(lout):
874 lout = TTest.stringescape('%s (esc)\n' %
885 lout = TTest.stringescape('%s (esc)\n' %
875 lout.rstrip('\n'))
886 lout.rstrip('\n'))
876 postout.append(' ' + lout) # Let diff deal with it.
887 postout.append(' ' + lout) # Let diff deal with it.
877 if r != '': # If line failed.
888 if r != '': # If line failed.
878 warnonly = 3 # for sure not
889 warnonly = 3 # for sure not
879 elif warnonly == 1: # Is "not yet" and line is warn only.
890 elif warnonly == 1: # Is "not yet" and line is warn only.
880 warnonly = 2 # Yes do warn.
891 warnonly = 2 # Yes do warn.
881
892
882 if lcmd:
893 if lcmd:
883 # Add on last return code.
894 # Add on last return code.
884 ret = int(lcmd.split()[1])
895 ret = int(lcmd.split()[1])
885 if ret != 0:
896 if ret != 0:
886 postout.append(' [%s]\n' % ret)
897 postout.append(' [%s]\n' % ret)
887 if pos in after:
898 if pos in after:
888 # Merge in non-active test bits.
899 # Merge in non-active test bits.
889 postout += after.pop(pos)
900 postout += after.pop(pos)
890 pos = int(lcmd.split()[0])
901 pos = int(lcmd.split()[0])
891
902
892 if pos in after:
903 if pos in after:
893 postout += after.pop(pos)
904 postout += after.pop(pos)
894
905
895 if warnonly == 2:
906 if warnonly == 2:
896 exitcode = False # Set exitcode to warned.
907 exitcode = False # Set exitcode to warned.
897
908
898 return exitcode, postout
909 return exitcode, postout
899
910
900 @staticmethod
911 @staticmethod
901 def rematch(el, l):
912 def rematch(el, l):
902 try:
913 try:
903 # use \Z to ensure that the regex matches to the end of the string
914 # use \Z to ensure that the regex matches to the end of the string
904 if os.name == 'nt':
915 if os.name == 'nt':
905 return re.match(el + r'\r?\n\Z', l)
916 return re.match(el + r'\r?\n\Z', l)
906 return re.match(el + r'\n\Z', l)
917 return re.match(el + r'\n\Z', l)
907 except re.error:
918 except re.error:
908 # el is an invalid regex
919 # el is an invalid regex
909 return False
920 return False
910
921
911 @staticmethod
922 @staticmethod
912 def globmatch(el, l):
923 def globmatch(el, l):
913 # The only supported special characters are * and ? plus / which also
924 # The only supported special characters are * and ? plus / which also
914 # matches \ on windows. Escaping of these characters is supported.
925 # matches \ on windows. Escaping of these characters is supported.
915 if el + '\n' == l:
926 if el + '\n' == l:
916 if os.altsep:
927 if os.altsep:
917 # matching on "/" is not needed for this line
928 # matching on "/" is not needed for this line
918 return '-glob'
929 return '-glob'
919 return True
930 return True
920 i, n = 0, len(el)
931 i, n = 0, len(el)
921 res = ''
932 res = ''
922 while i < n:
933 while i < n:
923 c = el[i]
934 c = el[i]
924 i += 1
935 i += 1
925 if c == '\\' and el[i] in '*?\\/':
936 if c == '\\' and el[i] in '*?\\/':
926 res += el[i - 1:i + 1]
937 res += el[i - 1:i + 1]
927 i += 1
938 i += 1
928 elif c == '*':
939 elif c == '*':
929 res += '.*'
940 res += '.*'
930 elif c == '?':
941 elif c == '?':
931 res += '.'
942 res += '.'
932 elif c == '/' and os.altsep:
943 elif c == '/' and os.altsep:
933 res += '[/\\\\]'
944 res += '[/\\\\]'
934 else:
945 else:
935 res += re.escape(c)
946 res += re.escape(c)
936 return TTest.rematch(res, l)
947 return TTest.rematch(res, l)
937
948
938 @staticmethod
949 @staticmethod
939 def linematch(el, l):
950 def linematch(el, l):
940 if el == l: # perfect match (fast)
951 if el == l: # perfect match (fast)
941 return True
952 return True
942 if el:
953 if el:
943 if el.endswith(" (esc)\n"):
954 if el.endswith(" (esc)\n"):
944 el = el[:-7].decode('string-escape') + '\n'
955 el = el[:-7].decode('string-escape') + '\n'
945 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
956 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
946 return True
957 return True
947 if el.endswith(" (re)\n"):
958 if el.endswith(" (re)\n"):
948 return TTest.rematch(el[:-6], l)
959 return TTest.rematch(el[:-6], l)
949 if el.endswith(" (glob)\n"):
960 if el.endswith(" (glob)\n"):
950 return TTest.globmatch(el[:-8], l)
961 return TTest.globmatch(el[:-8], l)
951 if os.altsep and l.replace('\\', '/') == el:
962 if os.altsep and l.replace('\\', '/') == el:
952 return '+glob'
963 return '+glob'
953 return False
964 return False
954
965
955 @staticmethod
966 @staticmethod
956 def parsehghaveoutput(lines):
967 def parsehghaveoutput(lines):
957 '''Parse hghave log lines.
968 '''Parse hghave log lines.
958
969
959 Return tuple of lists (missing, failed):
970 Return tuple of lists (missing, failed):
960 * the missing/unknown features
971 * the missing/unknown features
961 * the features for which existence check failed'''
972 * the features for which existence check failed'''
962 missing = []
973 missing = []
963 failed = []
974 failed = []
964 for line in lines:
975 for line in lines:
965 if line.startswith(TTest.SKIPPED_PREFIX):
976 if line.startswith(TTest.SKIPPED_PREFIX):
966 line = line.splitlines()[0]
977 line = line.splitlines()[0]
967 missing.append(line[len(TTest.SKIPPED_PREFIX):])
978 missing.append(line[len(TTest.SKIPPED_PREFIX):])
968 elif line.startswith(TTest.FAILED_PREFIX):
979 elif line.startswith(TTest.FAILED_PREFIX):
969 line = line.splitlines()[0]
980 line = line.splitlines()[0]
970 failed.append(line[len(TTest.FAILED_PREFIX):])
981 failed.append(line[len(TTest.FAILED_PREFIX):])
971
982
972 return missing, failed
983 return missing, failed
973
984
974 @staticmethod
985 @staticmethod
975 def _escapef(m):
986 def _escapef(m):
976 return TTest.ESCAPEMAP[m.group(0)]
987 return TTest.ESCAPEMAP[m.group(0)]
977
988
978 @staticmethod
989 @staticmethod
979 def _stringescape(s):
990 def _stringescape(s):
980 return TTest.ESCAPESUB(TTest._escapef, s)
991 return TTest.ESCAPESUB(TTest._escapef, s)
981
992
982
993
983 wifexited = getattr(os, "WIFEXITED", lambda x: False)
994 wifexited = getattr(os, "WIFEXITED", lambda x: False)
984 def run(cmd, wd, replacements, env, debug=False, timeout=None):
995 def run(cmd, wd, replacements, env, debug=False, timeout=None):
985 """Run command in a sub-process, capturing the output (stdout and stderr).
996 """Run command in a sub-process, capturing the output (stdout and stderr).
986 Return a tuple (exitcode, output). output is None in debug mode."""
997 Return a tuple (exitcode, output). output is None in debug mode."""
987 if debug:
998 if debug:
988 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
999 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
989 ret = proc.wait()
1000 ret = proc.wait()
990 return (ret, None)
1001 return (ret, None)
991
1002
992 proc = Popen4(cmd, wd, timeout, env)
1003 proc = Popen4(cmd, wd, timeout, env)
993 def cleanup():
1004 def cleanup():
994 terminate(proc)
1005 terminate(proc)
995 ret = proc.wait()
1006 ret = proc.wait()
996 if ret == 0:
1007 if ret == 0:
997 ret = signal.SIGTERM << 8
1008 ret = signal.SIGTERM << 8
998 killdaemons(env['DAEMON_PIDS'])
1009 killdaemons(env['DAEMON_PIDS'])
999 return ret
1010 return ret
1000
1011
1001 output = ''
1012 output = ''
1002 proc.tochild.close()
1013 proc.tochild.close()
1003
1014
1004 try:
1015 try:
1005 output = proc.fromchild.read()
1016 output = proc.fromchild.read()
1006 except KeyboardInterrupt:
1017 except KeyboardInterrupt:
1007 vlog('# Handling keyboard interrupt')
1018 vlog('# Handling keyboard interrupt')
1008 cleanup()
1019 cleanup()
1009 raise
1020 raise
1010
1021
1011 ret = proc.wait()
1022 ret = proc.wait()
1012 if wifexited(ret):
1023 if wifexited(ret):
1013 ret = os.WEXITSTATUS(ret)
1024 ret = os.WEXITSTATUS(ret)
1014
1025
1015 if proc.timeout:
1026 if proc.timeout:
1016 ret = 'timeout'
1027 ret = 'timeout'
1017
1028
1018 if ret:
1029 if ret:
1019 killdaemons(env['DAEMON_PIDS'])
1030 killdaemons(env['DAEMON_PIDS'])
1020
1031
1021 for s, r in replacements:
1032 for s, r in replacements:
1022 output = re.sub(s, r, output)
1033 output = re.sub(s, r, output)
1023 return ret, output.splitlines(True)
1034 return ret, output.splitlines(True)
1024
1035
1025 iolock = threading.Lock()
1036 iolock = threading.Lock()
1026
1037
1027 class SkipTest(Exception):
1038 class SkipTest(Exception):
1028 """Raised to indicate that a test is to be skipped."""
1039 """Raised to indicate that a test is to be skipped."""
1029
1040
1030 class IgnoreTest(Exception):
1041 class IgnoreTest(Exception):
1031 """Raised to indicate that a test is to be ignored."""
1042 """Raised to indicate that a test is to be ignored."""
1032
1043
1033 class WarnTest(Exception):
1044 class WarnTest(Exception):
1034 """Raised to indicate that a test warned."""
1045 """Raised to indicate that a test warned."""
1035
1046
1036 class TestResult(unittest._TextTestResult):
1047 class TestResult(unittest._TextTestResult):
1037 """Holds results when executing via unittest."""
1048 """Holds results when executing via unittest."""
1038 # Don't worry too much about accessing the non-public _TextTestResult.
1049 # Don't worry too much about accessing the non-public _TextTestResult.
1039 # It is relatively common in Python testing tools.
1050 # It is relatively common in Python testing tools.
1040 def __init__(self, options, *args, **kwargs):
1051 def __init__(self, options, *args, **kwargs):
1041 super(TestResult, self).__init__(*args, **kwargs)
1052 super(TestResult, self).__init__(*args, **kwargs)
1042
1053
1043 self._options = options
1054 self._options = options
1044
1055
1045 # unittest.TestResult didn't have skipped until 2.7. We need to
1056 # unittest.TestResult didn't have skipped until 2.7. We need to
1046 # polyfill it.
1057 # polyfill it.
1047 self.skipped = []
1058 self.skipped = []
1048
1059
1049 # We have a custom "ignored" result that isn't present in any Python
1060 # We have a custom "ignored" result that isn't present in any Python
1050 # unittest implementation. It is very similar to skipped. It may make
1061 # unittest implementation. It is very similar to skipped. It may make
1051 # sense to map it into skip some day.
1062 # sense to map it into skip some day.
1052 self.ignored = []
1063 self.ignored = []
1053
1064
1054 # We have a custom "warned" result that isn't present in any Python
1065 # We have a custom "warned" result that isn't present in any Python
1055 # unittest implementation. It is very similar to failed. It may make
1066 # unittest implementation. It is very similar to failed. It may make
1056 # sense to map it into fail some day.
1067 # sense to map it into fail some day.
1057 self.warned = []
1068 self.warned = []
1058
1069
1059 self.times = []
1070 self.times = []
1060 self._started = {}
1071 self._started = {}
1061
1072
1062 def addFailure(self, test, reason):
1073 def addFailure(self, test, reason):
1063 self.failures.append((test, reason))
1074 self.failures.append((test, reason))
1064
1075
1065 if self._options.first:
1076 if self._options.first:
1066 self.stop()
1077 self.stop()
1067
1078
1068 def addError(self, *args, **kwargs):
1079 def addError(self, *args, **kwargs):
1069 super(TestResult, self).addError(*args, **kwargs)
1080 super(TestResult, self).addError(*args, **kwargs)
1070
1081
1071 if self._options.first:
1082 if self._options.first:
1072 self.stop()
1083 self.stop()
1073
1084
1074 # Polyfill.
1085 # Polyfill.
1075 def addSkip(self, test, reason):
1086 def addSkip(self, test, reason):
1076 self.skipped.append((test, reason))
1087 self.skipped.append((test, reason))
1077
1088
1078 if self.showAll:
1089 if self.showAll:
1079 self.stream.writeln('skipped %s' % reason)
1090 self.stream.writeln('skipped %s' % reason)
1080 else:
1091 else:
1081 self.stream.write('s')
1092 self.stream.write('s')
1082 self.stream.flush()
1093 self.stream.flush()
1083
1094
1084 def addIgnore(self, test, reason):
1095 def addIgnore(self, test, reason):
1085 self.ignored.append((test, reason))
1096 self.ignored.append((test, reason))
1086
1097
1087 if self.showAll:
1098 if self.showAll:
1088 self.stream.writeln('ignored %s' % reason)
1099 self.stream.writeln('ignored %s' % reason)
1089 else:
1100 else:
1090 self.stream.write('i')
1101 self.stream.write('i')
1091 self.stream.flush()
1102 self.stream.flush()
1092
1103
1093 def addWarn(self, test, reason):
1104 def addWarn(self, test, reason):
1094 self.warned.append((test, reason))
1105 self.warned.append((test, reason))
1095
1106
1096 if self._options.first:
1107 if self._options.first:
1097 self.stop()
1108 self.stop()
1098
1109
1099 if self.showAll:
1110 if self.showAll:
1100 self.stream.writeln('warned %s' % reason)
1111 self.stream.writeln('warned %s' % reason)
1101 else:
1112 else:
1102 self.stream.write('~')
1113 self.stream.write('~')
1103 self.stream.flush()
1114 self.stream.flush()
1104
1115
1105 def addOutputMismatch(self, test, ret, got, expected):
1116 def addOutputMismatch(self, test, ret, got, expected):
1106 """Record a mismatch in test output for a particular test."""
1117 """Record a mismatch in test output for a particular test."""
1107
1118
1108 if self._options.nodiff:
1119 if self._options.nodiff:
1109 return
1120 return
1110
1121
1111 if self._options.view:
1122 if self._options.view:
1112 os.system("%s %s %s" % (self._view, test.refpath, test.errpath))
1123 os.system("%s %s %s" % (self._view, test.refpath, test.errpath))
1113 else:
1124 else:
1114 failed, lines = getdiff(expected, got,
1125 failed, lines = getdiff(expected, got,
1115 test.refpath, test.errpath)
1126 test.refpath, test.errpath)
1116 if failed:
1127 if failed:
1117 self.addFailure(test, 'diff generation failed')
1128 self.addFailure(test, 'diff generation failed')
1118 else:
1129 else:
1119 self.stream.write('\n')
1130 self.stream.write('\n')
1120 for line in lines:
1131 for line in lines:
1121 self.stream.write(line)
1132 self.stream.write(line)
1122 self.stream.flush()
1133 self.stream.flush()
1123
1134
1124 if ret or not self._options.interactive or \
1135 if ret or not self._options.interactive or \
1125 not os.path.exists(test.errpath):
1136 not os.path.exists(test.errpath):
1126 return
1137 return
1127
1138
1128 iolock.acquire()
1139 iolock.acquire()
1129 print 'Accept this change? [n] ',
1140 print 'Accept this change? [n] ',
1130 answer = sys.stdin.readline().strip()
1141 answer = sys.stdin.readline().strip()
1131 iolock.release()
1142 iolock.release()
1132 if answer.lower() in ('y', 'yes'):
1143 if answer.lower() in ('y', 'yes'):
1133 if test.name.endswith('.t'):
1144 if test.name.endswith('.t'):
1134 rename(test.errpath, test.path)
1145 rename(test.errpath, test.path)
1135 else:
1146 else:
1136 rename(test.errpath, '%s.out' % test.path)
1147 rename(test.errpath, '%s.out' % test.path)
1137
1148
1138 def startTest(self, test):
1149 def startTest(self, test):
1139 super(TestResult, self).startTest(test)
1150 super(TestResult, self).startTest(test)
1140
1151
1141 self._started[test.name] = time.time()
1152 self._started[test.name] = time.time()
1142
1153
1143 def stopTest(self, test, interrupted=False):
1154 def stopTest(self, test, interrupted=False):
1144 super(TestResult, self).stopTest(test)
1155 super(TestResult, self).stopTest(test)
1145
1156
1146 self.times.append((test.name, time.time() - self._started[test.name]))
1157 self.times.append((test.name, time.time() - self._started[test.name]))
1147 del self._started[test.name]
1158 del self._started[test.name]
1148
1159
1149 if interrupted:
1160 if interrupted:
1150 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1161 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1151 test.name, self.times[-1][1]))
1162 test.name, self.times[-1][1]))
1152
1163
1153 class TestSuite(unittest.TestSuite):
1164 class TestSuite(unittest.TestSuite):
1154 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1165 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1155
1166
1156 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1167 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1157 retest=False, keywords=None, loop=False,
1168 retest=False, keywords=None, loop=False,
1158 *args, **kwargs):
1169 *args, **kwargs):
1159 """Create a new instance that can run tests with a configuration.
1170 """Create a new instance that can run tests with a configuration.
1160
1171
1161 testdir specifies the directory where tests are executed from. This
1172 testdir specifies the directory where tests are executed from. This
1162 is typically the ``tests`` directory from Mercurial's source
1173 is typically the ``tests`` directory from Mercurial's source
1163 repository.
1174 repository.
1164
1175
1165 jobs specifies the number of jobs to run concurrently. Each test
1176 jobs specifies the number of jobs to run concurrently. Each test
1166 executes on its own thread. Tests actually spawn new processes, so
1177 executes on its own thread. Tests actually spawn new processes, so
1167 state mutation should not be an issue.
1178 state mutation should not be an issue.
1168
1179
1169 whitelist and blacklist denote tests that have been whitelisted and
1180 whitelist and blacklist denote tests that have been whitelisted and
1170 blacklisted, respectively. These arguments don't belong in TestSuite.
1181 blacklisted, respectively. These arguments don't belong in TestSuite.
1171 Instead, whitelist and blacklist should be handled by the thing that
1182 Instead, whitelist and blacklist should be handled by the thing that
1172 populates the TestSuite with tests. They are present to preserve
1183 populates the TestSuite with tests. They are present to preserve
1173 backwards compatible behavior which reports skipped tests as part
1184 backwards compatible behavior which reports skipped tests as part
1174 of the results.
1185 of the results.
1175
1186
1176 retest denotes whether to retest failed tests. This arguably belongs
1187 retest denotes whether to retest failed tests. This arguably belongs
1177 outside of TestSuite.
1188 outside of TestSuite.
1178
1189
1179 keywords denotes key words that will be used to filter which tests
1190 keywords denotes key words that will be used to filter which tests
1180 to execute. This arguably belongs outside of TestSuite.
1191 to execute. This arguably belongs outside of TestSuite.
1181
1192
1182 loop denotes whether to loop over tests forever.
1193 loop denotes whether to loop over tests forever.
1183 """
1194 """
1184 super(TestSuite, self).__init__(*args, **kwargs)
1195 super(TestSuite, self).__init__(*args, **kwargs)
1185
1196
1186 self._jobs = jobs
1197 self._jobs = jobs
1187 self._whitelist = whitelist
1198 self._whitelist = whitelist
1188 self._blacklist = blacklist
1199 self._blacklist = blacklist
1189 self._retest = retest
1200 self._retest = retest
1190 self._keywords = keywords
1201 self._keywords = keywords
1191 self._loop = loop
1202 self._loop = loop
1192
1203
1193 def run(self, result):
1204 def run(self, result):
1194 # We have a number of filters that need to be applied. We do this
1205 # We have a number of filters that need to be applied. We do this
1195 # here instead of inside Test because it makes the running logic for
1206 # here instead of inside Test because it makes the running logic for
1196 # Test simpler.
1207 # Test simpler.
1197 tests = []
1208 tests = []
1198 for test in self._tests:
1209 for test in self._tests:
1199 if not os.path.exists(test.path):
1210 if not os.path.exists(test.path):
1200 result.addSkip(test, "Doesn't exist")
1211 result.addSkip(test, "Doesn't exist")
1201 continue
1212 continue
1202
1213
1203 if not (self._whitelist and test.name in self._whitelist):
1214 if not (self._whitelist and test.name in self._whitelist):
1204 if self._blacklist and test.name in self._blacklist:
1215 if self._blacklist and test.name in self._blacklist:
1205 result.addSkip(test, 'blacklisted')
1216 result.addSkip(test, 'blacklisted')
1206 continue
1217 continue
1207
1218
1208 if self._retest and not os.path.exists(test.errpath):
1219 if self._retest and not os.path.exists(test.errpath):
1209 result.addIgnore(test, 'not retesting')
1220 result.addIgnore(test, 'not retesting')
1210 continue
1221 continue
1211
1222
1212 if self._keywords:
1223 if self._keywords:
1213 f = open(test.path)
1224 f = open(test.path)
1214 t = f.read().lower() + test.name.lower()
1225 t = f.read().lower() + test.name.lower()
1215 f.close()
1226 f.close()
1216 ignored = False
1227 ignored = False
1217 for k in self._keywords.lower().split():
1228 for k in self._keywords.lower().split():
1218 if k not in t:
1229 if k not in t:
1219 result.addIgnore(test, "doesn't match keyword")
1230 result.addIgnore(test, "doesn't match keyword")
1220 ignored = True
1231 ignored = True
1221 break
1232 break
1222
1233
1223 if ignored:
1234 if ignored:
1224 continue
1235 continue
1225
1236
1226 tests.append(test)
1237 tests.append(test)
1227
1238
1228 runtests = list(tests)
1239 runtests = list(tests)
1229 done = queue.Queue()
1240 done = queue.Queue()
1230 running = 0
1241 running = 0
1231
1242
1232 def job(test, result):
1243 def job(test, result):
1233 try:
1244 try:
1234 test(result)
1245 test(result)
1235 done.put(None)
1246 done.put(None)
1236 except KeyboardInterrupt:
1247 except KeyboardInterrupt:
1237 pass
1248 pass
1238 except: # re-raises
1249 except: # re-raises
1239 done.put(('!', test, 'run-test raised an error, see traceback'))
1250 done.put(('!', test, 'run-test raised an error, see traceback'))
1240 raise
1251 raise
1241
1252
1242 try:
1253 try:
1243 while tests or running:
1254 while tests or running:
1244 if not done.empty() or running == self._jobs or not tests:
1255 if not done.empty() or running == self._jobs or not tests:
1245 try:
1256 try:
1246 done.get(True, 1)
1257 done.get(True, 1)
1247 if result and result.shouldStop:
1258 if result and result.shouldStop:
1248 break
1259 break
1249 except queue.Empty:
1260 except queue.Empty:
1250 continue
1261 continue
1251 running -= 1
1262 running -= 1
1252 if tests and not running == self._jobs:
1263 if tests and not running == self._jobs:
1253 test = tests.pop(0)
1264 test = tests.pop(0)
1254 if self._loop:
1265 if self._loop:
1255 tests.append(test)
1266 tests.append(test)
1256 t = threading.Thread(target=job, name=test.name,
1267 t = threading.Thread(target=job, name=test.name,
1257 args=(test, result))
1268 args=(test, result))
1258 t.start()
1269 t.start()
1259 running += 1
1270 running += 1
1260 except KeyboardInterrupt:
1271 except KeyboardInterrupt:
1261 for test in runtests:
1272 for test in runtests:
1262 test.abort()
1273 test.abort()
1263
1274
1264 return result
1275 return result
1265
1276
1266 class TextTestRunner(unittest.TextTestRunner):
1277 class TextTestRunner(unittest.TextTestRunner):
1267 """Custom unittest test runner that uses appropriate settings."""
1278 """Custom unittest test runner that uses appropriate settings."""
1268
1279
1269 def __init__(self, runner, *args, **kwargs):
1280 def __init__(self, runner, *args, **kwargs):
1270 super(TextTestRunner, self).__init__(*args, **kwargs)
1281 super(TextTestRunner, self).__init__(*args, **kwargs)
1271
1282
1272 self._runner = runner
1283 self._runner = runner
1273
1284
1274 def run(self, test):
1285 def run(self, test):
1275 result = TestResult(self._runner.options, self.stream,
1286 result = TestResult(self._runner.options, self.stream,
1276 self.descriptions, self.verbosity)
1287 self.descriptions, self.verbosity)
1277
1288
1278 test(result)
1289 test(result)
1279
1290
1280 failed = len(result.failures)
1291 failed = len(result.failures)
1281 warned = len(result.warned)
1292 warned = len(result.warned)
1282 skipped = len(result.skipped)
1293 skipped = len(result.skipped)
1283 ignored = len(result.ignored)
1294 ignored = len(result.ignored)
1284
1295
1285 self.stream.writeln('')
1296 self.stream.writeln('')
1286
1297
1287 if not self._runner.options.noskips:
1298 if not self._runner.options.noskips:
1288 for test, msg in result.skipped:
1299 for test, msg in result.skipped:
1289 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1300 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1290 for test, msg in result.warned:
1301 for test, msg in result.warned:
1291 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1302 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1292 for test, msg in result.failures:
1303 for test, msg in result.failures:
1293 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1304 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1294 for test, msg in result.errors:
1305 for test, msg in result.errors:
1295 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1306 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1296
1307
1297 self._runner._checkhglib('Tested')
1308 self._runner._checkhglib('Tested')
1298
1309
1299 # This differs from unittest's default output in that we don't count
1310 # This differs from unittest's default output in that we don't count
1300 # skipped and ignored tests as part of the total test count.
1311 # skipped and ignored tests as part of the total test count.
1301 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1312 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1302 % (result.testsRun - skipped - ignored,
1313 % (result.testsRun - skipped - ignored,
1303 skipped + ignored, warned, failed))
1314 skipped + ignored, warned, failed))
1304 if failed:
1315 if failed:
1305 self.stream.writeln('python hash seed: %s' %
1316 self.stream.writeln('python hash seed: %s' %
1306 os.environ['PYTHONHASHSEED'])
1317 os.environ['PYTHONHASHSEED'])
1307 if self._runner.options.time:
1318 if self._runner.options.time:
1308 self.printtimes(result.times)
1319 self.printtimes(result.times)
1309
1320
1310 def printtimes(self, times):
1321 def printtimes(self, times):
1311 self.stream.writeln('# Producing time report')
1322 self.stream.writeln('# Producing time report')
1312 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1323 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1313 cols = '%7.3f %s'
1324 cols = '%7.3f %s'
1314 self.stream.writeln('%-7s %s' % ('Time', 'Test'))
1325 self.stream.writeln('%-7s %s' % ('Time', 'Test'))
1315 for test, timetaken in times:
1326 for test, timetaken in times:
1316 self.stream.writeln(cols % (timetaken, test))
1327 self.stream.writeln(cols % (timetaken, test))
1317
1328
1318 class TestRunner(object):
1329 class TestRunner(object):
1319 """Holds context for executing tests.
1330 """Holds context for executing tests.
1320
1331
1321 Tests rely on a lot of state. This object holds it for them.
1332 Tests rely on a lot of state. This object holds it for them.
1322 """
1333 """
1323
1334
1335 # Programs required to run tests.
1324 REQUIREDTOOLS = [
1336 REQUIREDTOOLS = [
1325 os.path.basename(sys.executable),
1337 os.path.basename(sys.executable),
1326 'diff',
1338 'diff',
1327 'grep',
1339 'grep',
1328 'unzip',
1340 'unzip',
1329 'gunzip',
1341 'gunzip',
1330 'bunzip2',
1342 'bunzip2',
1331 'sed',
1343 'sed',
1332 ]
1344 ]
1333
1345
1346 # Maps file extensions to test class.
1334 TESTTYPES = [
1347 TESTTYPES = [
1335 ('.py', PythonTest),
1348 ('.py', PythonTest),
1336 ('.t', TTest),
1349 ('.t', TTest),
1337 ]
1350 ]
1338
1351
1339 def __init__(self):
1352 def __init__(self):
1340 self.options = None
1353 self.options = None
1341 self._testdir = None
1354 self._testdir = None
1342 self._hgtmp = None
1355 self._hgtmp = None
1343 self._installdir = None
1356 self._installdir = None
1344 self._bindir = None
1357 self._bindir = None
1345 self._tmpbinddir = None
1358 self._tmpbinddir = None
1346 self._pythondir = None
1359 self._pythondir = None
1347 self._coveragefile = None
1360 self._coveragefile = None
1348 self._createdfiles = []
1361 self._createdfiles = []
1349 self._hgpath = None
1362 self._hgpath = None
1350
1363
1351 def run(self, args, parser=None):
1364 def run(self, args, parser=None):
1352 """Run the test suite."""
1365 """Run the test suite."""
1353 oldmask = os.umask(022)
1366 oldmask = os.umask(022)
1354 try:
1367 try:
1355 parser = parser or getparser()
1368 parser = parser or getparser()
1356 options, args = parseargs(args, parser)
1369 options, args = parseargs(args, parser)
1357 self.options = options
1370 self.options = options
1358
1371
1359 self._checktools()
1372 self._checktools()
1360 tests = self.findtests(args)
1373 tests = self.findtests(args)
1361 return self._run(tests)
1374 return self._run(tests)
1362 finally:
1375 finally:
1363 os.umask(oldmask)
1376 os.umask(oldmask)
1364
1377
1365 def _run(self, tests):
1378 def _run(self, tests):
1366 if self.options.random:
1379 if self.options.random:
1367 random.shuffle(tests)
1380 random.shuffle(tests)
1368 else:
1381 else:
1369 # keywords for slow tests
1382 # keywords for slow tests
1370 slow = 'svn gendoc check-code-hg'.split()
1383 slow = 'svn gendoc check-code-hg'.split()
1371 def sortkey(f):
1384 def sortkey(f):
1372 # run largest tests first, as they tend to take the longest
1385 # run largest tests first, as they tend to take the longest
1373 try:
1386 try:
1374 val = -os.stat(f).st_size
1387 val = -os.stat(f).st_size
1375 except OSError, e:
1388 except OSError, e:
1376 if e.errno != errno.ENOENT:
1389 if e.errno != errno.ENOENT:
1377 raise
1390 raise
1378 return -1e9 # file does not exist, tell early
1391 return -1e9 # file does not exist, tell early
1379 for kw in slow:
1392 for kw in slow:
1380 if kw in f:
1393 if kw in f:
1381 val *= 10
1394 val *= 10
1382 return val
1395 return val
1383 tests.sort(key=sortkey)
1396 tests.sort(key=sortkey)
1384
1397
1385 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1398 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1386
1399
1387 if 'PYTHONHASHSEED' not in os.environ:
1400 if 'PYTHONHASHSEED' not in os.environ:
1388 # use a random python hash seed all the time
1401 # use a random python hash seed all the time
1389 # we do the randomness ourself to know what seed is used
1402 # we do the randomness ourself to know what seed is used
1390 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1403 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1391
1404
1392 if self.options.tmpdir:
1405 if self.options.tmpdir:
1393 self.options.keep_tmpdir = True
1406 self.options.keep_tmpdir = True
1394 tmpdir = self.options.tmpdir
1407 tmpdir = self.options.tmpdir
1395 if os.path.exists(tmpdir):
1408 if os.path.exists(tmpdir):
1396 # Meaning of tmpdir has changed since 1.3: we used to create
1409 # Meaning of tmpdir has changed since 1.3: we used to create
1397 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1410 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1398 # tmpdir already exists.
1411 # tmpdir already exists.
1399 print "error: temp dir %r already exists" % tmpdir
1412 print "error: temp dir %r already exists" % tmpdir
1400 return 1
1413 return 1
1401
1414
1402 # Automatically removing tmpdir sounds convenient, but could
1415 # Automatically removing tmpdir sounds convenient, but could
1403 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1416 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1404 # or "--tmpdir=$HOME".
1417 # or "--tmpdir=$HOME".
1405 #vlog("# Removing temp dir", tmpdir)
1418 #vlog("# Removing temp dir", tmpdir)
1406 #shutil.rmtree(tmpdir)
1419 #shutil.rmtree(tmpdir)
1407 os.makedirs(tmpdir)
1420 os.makedirs(tmpdir)
1408 else:
1421 else:
1409 d = None
1422 d = None
1410 if os.name == 'nt':
1423 if os.name == 'nt':
1411 # without this, we get the default temp dir location, but
1424 # without this, we get the default temp dir location, but
1412 # in all lowercase, which causes troubles with paths (issue3490)
1425 # in all lowercase, which causes troubles with paths (issue3490)
1413 d = os.getenv('TMP')
1426 d = os.getenv('TMP')
1414 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1427 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1415 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1428 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1416
1429
1417 if self.options.with_hg:
1430 if self.options.with_hg:
1418 self._installdir = None
1431 self._installdir = None
1419 self._bindir = os.path.dirname(os.path.realpath(
1432 self._bindir = os.path.dirname(os.path.realpath(
1420 self.options.with_hg))
1433 self.options.with_hg))
1421 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1434 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1422 os.makedirs(self._tmpbindir)
1435 os.makedirs(self._tmpbindir)
1423
1436
1424 # This looks redundant with how Python initializes sys.path from
1437 # This looks redundant with how Python initializes sys.path from
1425 # the location of the script being executed. Needed because the
1438 # the location of the script being executed. Needed because the
1426 # "hg" specified by --with-hg is not the only Python script
1439 # "hg" specified by --with-hg is not the only Python script
1427 # executed in the test suite that needs to import 'mercurial'
1440 # executed in the test suite that needs to import 'mercurial'
1428 # ... which means it's not really redundant at all.
1441 # ... which means it's not really redundant at all.
1429 self._pythondir = self._bindir
1442 self._pythondir = self._bindir
1430 else:
1443 else:
1431 self._installdir = os.path.join(self._hgtmp, "install")
1444 self._installdir = os.path.join(self._hgtmp, "install")
1432 self._bindir = os.environ["BINDIR"] = \
1445 self._bindir = os.environ["BINDIR"] = \
1433 os.path.join(self._installdir, "bin")
1446 os.path.join(self._installdir, "bin")
1434 self._tmpbindir = self._bindir
1447 self._tmpbindir = self._bindir
1435 self._pythondir = os.path.join(self._installdir, "lib", "python")
1448 self._pythondir = os.path.join(self._installdir, "lib", "python")
1436
1449
1437 os.environ["BINDIR"] = self._bindir
1450 os.environ["BINDIR"] = self._bindir
1438 os.environ["PYTHON"] = PYTHON
1451 os.environ["PYTHON"] = PYTHON
1439
1452
1440 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1453 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1441 if self._tmpbindir != self._bindir:
1454 if self._tmpbindir != self._bindir:
1442 path = [self._tmpbindir] + path
1455 path = [self._tmpbindir] + path
1443 os.environ["PATH"] = os.pathsep.join(path)
1456 os.environ["PATH"] = os.pathsep.join(path)
1444
1457
1445 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1458 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1446 # can run .../tests/run-tests.py test-foo where test-foo
1459 # can run .../tests/run-tests.py test-foo where test-foo
1447 # adds an extension to HGRC. Also include run-test.py directory to
1460 # adds an extension to HGRC. Also include run-test.py directory to
1448 # import modules like heredoctest.
1461 # import modules like heredoctest.
1449 pypath = [self._pythondir, self._testdir,
1462 pypath = [self._pythondir, self._testdir,
1450 os.path.abspath(os.path.dirname(__file__))]
1463 os.path.abspath(os.path.dirname(__file__))]
1451 # We have to augment PYTHONPATH, rather than simply replacing
1464 # We have to augment PYTHONPATH, rather than simply replacing
1452 # it, in case external libraries are only available via current
1465 # it, in case external libraries are only available via current
1453 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1466 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1454 # are in /opt/subversion.)
1467 # are in /opt/subversion.)
1455 oldpypath = os.environ.get(IMPL_PATH)
1468 oldpypath = os.environ.get(IMPL_PATH)
1456 if oldpypath:
1469 if oldpypath:
1457 pypath.append(oldpypath)
1470 pypath.append(oldpypath)
1458 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1471 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1459
1472
1460 self._coveragefile = os.path.join(self._testdir, '.coverage')
1473 self._coveragefile = os.path.join(self._testdir, '.coverage')
1461
1474
1462 vlog("# Using TESTDIR", self._testdir)
1475 vlog("# Using TESTDIR", self._testdir)
1463 vlog("# Using HGTMP", self._hgtmp)
1476 vlog("# Using HGTMP", self._hgtmp)
1464 vlog("# Using PATH", os.environ["PATH"])
1477 vlog("# Using PATH", os.environ["PATH"])
1465 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1478 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1466
1479
1467 try:
1480 try:
1468 return self._runtests(tests) or 0
1481 return self._runtests(tests) or 0
1469 finally:
1482 finally:
1470 time.sleep(.1)
1483 time.sleep(.1)
1471 self._cleanup()
1484 self._cleanup()
1472
1485
1473 def findtests(self, args):
1486 def findtests(self, args):
1474 """Finds possible test files from arguments.
1487 """Finds possible test files from arguments.
1475
1488
1476 If you wish to inject custom tests into the test harness, this would
1489 If you wish to inject custom tests into the test harness, this would
1477 be a good function to monkeypatch or override in a derived class.
1490 be a good function to monkeypatch or override in a derived class.
1478 """
1491 """
1479 if not args:
1492 if not args:
1480 if self.options.changed:
1493 if self.options.changed:
1481 proc = Popen4('hg st --rev "%s" -man0 .' %
1494 proc = Popen4('hg st --rev "%s" -man0 .' %
1482 self.options.changed, None, 0)
1495 self.options.changed, None, 0)
1483 stdout, stderr = proc.communicate()
1496 stdout, stderr = proc.communicate()
1484 args = stdout.strip('\0').split('\0')
1497 args = stdout.strip('\0').split('\0')
1485 else:
1498 else:
1486 args = os.listdir('.')
1499 args = os.listdir('.')
1487
1500
1488 return [t for t in args
1501 return [t for t in args
1489 if os.path.basename(t).startswith('test-')
1502 if os.path.basename(t).startswith('test-')
1490 and (t.endswith('.py') or t.endswith('.t'))]
1503 and (t.endswith('.py') or t.endswith('.t'))]
1491
1504
1492 def _runtests(self, tests):
1505 def _runtests(self, tests):
1493 try:
1506 try:
1494 if self._installdir:
1507 if self._installdir:
1495 self._installhg()
1508 self._installhg()
1496 self._checkhglib("Testing")
1509 self._checkhglib("Testing")
1497 else:
1510 else:
1498 self._usecorrectpython()
1511 self._usecorrectpython()
1499
1512
1500 if self.options.restart:
1513 if self.options.restart:
1501 orig = list(tests)
1514 orig = list(tests)
1502 while tests:
1515 while tests:
1503 if os.path.exists(tests[0] + ".err"):
1516 if os.path.exists(tests[0] + ".err"):
1504 break
1517 break
1505 tests.pop(0)
1518 tests.pop(0)
1506 if not tests:
1519 if not tests:
1507 print "running all tests"
1520 print "running all tests"
1508 tests = orig
1521 tests = orig
1509
1522
1510 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1523 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1511
1524
1512 failed = False
1525 failed = False
1513 warned = False
1526 warned = False
1514
1527
1515 suite = TestSuite(self._testdir,
1528 suite = TestSuite(self._testdir,
1516 jobs=self.options.jobs,
1529 jobs=self.options.jobs,
1517 whitelist=self.options.whitelisted,
1530 whitelist=self.options.whitelisted,
1518 blacklist=self.options.blacklist,
1531 blacklist=self.options.blacklist,
1519 retest=self.options.retest,
1532 retest=self.options.retest,
1520 keywords=self.options.keywords,
1533 keywords=self.options.keywords,
1521 loop=self.options.loop,
1534 loop=self.options.loop,
1522 tests=tests)
1535 tests=tests)
1523 verbosity = 1
1536 verbosity = 1
1524 if self.options.verbose:
1537 if self.options.verbose:
1525 verbosity = 2
1538 verbosity = 2
1526 runner = TextTestRunner(self, verbosity=verbosity)
1539 runner = TextTestRunner(self, verbosity=verbosity)
1527 runner.run(suite)
1540 runner.run(suite)
1528
1541
1529 if self.options.anycoverage:
1542 if self.options.anycoverage:
1530 self._outputcoverage()
1543 self._outputcoverage()
1531 except KeyboardInterrupt:
1544 except KeyboardInterrupt:
1532 failed = True
1545 failed = True
1533 print "\ninterrupted!"
1546 print "\ninterrupted!"
1534
1547
1535 if failed:
1548 if failed:
1536 return 1
1549 return 1
1537 if warned:
1550 if warned:
1538 return 80
1551 return 80
1539
1552
1540 def _gettest(self, test, count):
1553 def _gettest(self, test, count):
1541 """Obtain a Test by looking at its filename.
1554 """Obtain a Test by looking at its filename.
1542
1555
1543 Returns a Test instance. The Test may not be runnable if it doesn't
1556 Returns a Test instance. The Test may not be runnable if it doesn't
1544 map to a known type.
1557 map to a known type.
1545 """
1558 """
1546 lctest = test.lower()
1559 lctest = test.lower()
1547 testcls = Test
1560 testcls = Test
1548
1561
1549 for ext, cls in self.TESTTYPES:
1562 for ext, cls in self.TESTTYPES:
1550 if lctest.endswith(ext):
1563 if lctest.endswith(ext):
1551 testcls = cls
1564 testcls = cls
1552 break
1565 break
1553
1566
1554 refpath = os.path.join(self._testdir, test)
1567 refpath = os.path.join(self._testdir, test)
1555 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1568 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1556
1569
1557 return testcls(refpath, tmpdir,
1570 return testcls(refpath, tmpdir,
1558 keeptmpdir=self.options.keep_tmpdir,
1571 keeptmpdir=self.options.keep_tmpdir,
1559 debug=self.options.debug,
1572 debug=self.options.debug,
1560 timeout=self.options.timeout,
1573 timeout=self.options.timeout,
1561 startport=self.options.port + count * 3,
1574 startport=self.options.port + count * 3,
1562 extraconfigopts=self.options.extra_config_opt,
1575 extraconfigopts=self.options.extra_config_opt,
1563 py3kwarnings=self.options.py3k_warnings,
1576 py3kwarnings=self.options.py3k_warnings,
1564 shell=self.options.shell)
1577 shell=self.options.shell)
1565
1578
1566 def _cleanup(self):
1579 def _cleanup(self):
1567 """Clean up state from this test invocation."""
1580 """Clean up state from this test invocation."""
1568
1581
1569 if self.options.keep_tmpdir:
1582 if self.options.keep_tmpdir:
1570 return
1583 return
1571
1584
1572 vlog("# Cleaning up HGTMP", self._hgtmp)
1585 vlog("# Cleaning up HGTMP", self._hgtmp)
1573 shutil.rmtree(self._hgtmp, True)
1586 shutil.rmtree(self._hgtmp, True)
1574 for f in self._createdfiles:
1587 for f in self._createdfiles:
1575 try:
1588 try:
1576 os.remove(f)
1589 os.remove(f)
1577 except OSError:
1590 except OSError:
1578 pass
1591 pass
1579
1592
1580 def _usecorrectpython(self):
1593 def _usecorrectpython(self):
1581 # Some tests run the Python interpreter. They must use the
1594 """Configure the environment to use the appropriate Python in tests."""
1582 # same interpreter or bad things will happen.
1595 # Tests must use the same interpreter as us or bad things will happen.
1583 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1596 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1584 if getattr(os, 'symlink', None):
1597 if getattr(os, 'symlink', None):
1585 vlog("# Making python executable in test path a symlink to '%s'" %
1598 vlog("# Making python executable in test path a symlink to '%s'" %
1586 sys.executable)
1599 sys.executable)
1587 mypython = os.path.join(self._tmpbindir, pyexename)
1600 mypython = os.path.join(self._tmpbindir, pyexename)
1588 try:
1601 try:
1589 if os.readlink(mypython) == sys.executable:
1602 if os.readlink(mypython) == sys.executable:
1590 return
1603 return
1591 os.unlink(mypython)
1604 os.unlink(mypython)
1592 except OSError, err:
1605 except OSError, err:
1593 if err.errno != errno.ENOENT:
1606 if err.errno != errno.ENOENT:
1594 raise
1607 raise
1595 if self._findprogram(pyexename) != sys.executable:
1608 if self._findprogram(pyexename) != sys.executable:
1596 try:
1609 try:
1597 os.symlink(sys.executable, mypython)
1610 os.symlink(sys.executable, mypython)
1598 self._createdfiles.append(mypython)
1611 self._createdfiles.append(mypython)
1599 except OSError, err:
1612 except OSError, err:
1600 # child processes may race, which is harmless
1613 # child processes may race, which is harmless
1601 if err.errno != errno.EEXIST:
1614 if err.errno != errno.EEXIST:
1602 raise
1615 raise
1603 else:
1616 else:
1604 exedir, exename = os.path.split(sys.executable)
1617 exedir, exename = os.path.split(sys.executable)
1605 vlog("# Modifying search path to find %s as %s in '%s'" %
1618 vlog("# Modifying search path to find %s as %s in '%s'" %
1606 (exename, pyexename, exedir))
1619 (exename, pyexename, exedir))
1607 path = os.environ['PATH'].split(os.pathsep)
1620 path = os.environ['PATH'].split(os.pathsep)
1608 while exedir in path:
1621 while exedir in path:
1609 path.remove(exedir)
1622 path.remove(exedir)
1610 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1623 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1611 if not self._findprogram(pyexename):
1624 if not self._findprogram(pyexename):
1612 print "WARNING: Cannot find %s in search path" % pyexename
1625 print "WARNING: Cannot find %s in search path" % pyexename
1613
1626
1614 def _installhg(self):
1627 def _installhg(self):
1628 """Install hg into the test environment.
1629
1630 This will also configure hg with the appropriate testing settings.
1631 """
1615 vlog("# Performing temporary installation of HG")
1632 vlog("# Performing temporary installation of HG")
1616 installerrs = os.path.join("tests", "install.err")
1633 installerrs = os.path.join("tests", "install.err")
1617 compiler = ''
1634 compiler = ''
1618 if self.options.compiler:
1635 if self.options.compiler:
1619 compiler = '--compiler ' + self.options.compiler
1636 compiler = '--compiler ' + self.options.compiler
1620 pure = self.options.pure and "--pure" or ""
1637 pure = self.options.pure and "--pure" or ""
1621 py3 = ''
1638 py3 = ''
1622 if sys.version_info[0] == 3:
1639 if sys.version_info[0] == 3:
1623 py3 = '--c2to3'
1640 py3 = '--c2to3'
1624
1641
1625 # Run installer in hg root
1642 # Run installer in hg root
1626 script = os.path.realpath(sys.argv[0])
1643 script = os.path.realpath(sys.argv[0])
1627 hgroot = os.path.dirname(os.path.dirname(script))
1644 hgroot = os.path.dirname(os.path.dirname(script))
1628 os.chdir(hgroot)
1645 os.chdir(hgroot)
1629 nohome = '--home=""'
1646 nohome = '--home=""'
1630 if os.name == 'nt':
1647 if os.name == 'nt':
1631 # The --home="" trick works only on OS where os.sep == '/'
1648 # The --home="" trick works only on OS where os.sep == '/'
1632 # because of a distutils convert_path() fast-path. Avoid it at
1649 # because of a distutils convert_path() fast-path. Avoid it at
1633 # least on Windows for now, deal with .pydistutils.cfg bugs
1650 # least on Windows for now, deal with .pydistutils.cfg bugs
1634 # when they happen.
1651 # when they happen.
1635 nohome = ''
1652 nohome = ''
1636 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1653 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1637 ' build %(compiler)s --build-base="%(base)s"'
1654 ' build %(compiler)s --build-base="%(base)s"'
1638 ' install --force --prefix="%(prefix)s"'
1655 ' install --force --prefix="%(prefix)s"'
1639 ' --install-lib="%(libdir)s"'
1656 ' --install-lib="%(libdir)s"'
1640 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1657 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1641 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1658 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1642 'compiler': compiler,
1659 'compiler': compiler,
1643 'base': os.path.join(self._hgtmp, "build"),
1660 'base': os.path.join(self._hgtmp, "build"),
1644 'prefix': self._installdir, 'libdir': self._pythondir,
1661 'prefix': self._installdir, 'libdir': self._pythondir,
1645 'bindir': self._bindir,
1662 'bindir': self._bindir,
1646 'nohome': nohome, 'logfile': installerrs})
1663 'nohome': nohome, 'logfile': installerrs})
1647 vlog("# Running", cmd)
1664 vlog("# Running", cmd)
1648 if os.system(cmd) == 0:
1665 if os.system(cmd) == 0:
1649 if not self.options.verbose:
1666 if not self.options.verbose:
1650 os.remove(installerrs)
1667 os.remove(installerrs)
1651 else:
1668 else:
1652 f = open(installerrs)
1669 f = open(installerrs)
1653 for line in f:
1670 for line in f:
1654 print line,
1671 print line,
1655 f.close()
1672 f.close()
1656 sys.exit(1)
1673 sys.exit(1)
1657 os.chdir(self._testdir)
1674 os.chdir(self._testdir)
1658
1675
1659 self._usecorrectpython()
1676 self._usecorrectpython()
1660
1677
1661 if self.options.py3k_warnings and not self.options.anycoverage:
1678 if self.options.py3k_warnings and not self.options.anycoverage:
1662 vlog("# Updating hg command to enable Py3k Warnings switch")
1679 vlog("# Updating hg command to enable Py3k Warnings switch")
1663 f = open(os.path.join(self._bindir, 'hg'), 'r')
1680 f = open(os.path.join(self._bindir, 'hg'), 'r')
1664 lines = [line.rstrip() for line in f]
1681 lines = [line.rstrip() for line in f]
1665 lines[0] += ' -3'
1682 lines[0] += ' -3'
1666 f.close()
1683 f.close()
1667 f = open(os.path.join(self._bindir, 'hg'), 'w')
1684 f = open(os.path.join(self._bindir, 'hg'), 'w')
1668 for line in lines:
1685 for line in lines:
1669 f.write(line + '\n')
1686 f.write(line + '\n')
1670 f.close()
1687 f.close()
1671
1688
1672 hgbat = os.path.join(self._bindir, 'hg.bat')
1689 hgbat = os.path.join(self._bindir, 'hg.bat')
1673 if os.path.isfile(hgbat):
1690 if os.path.isfile(hgbat):
1674 # hg.bat expects to be put in bin/scripts while run-tests.py
1691 # hg.bat expects to be put in bin/scripts while run-tests.py
1675 # installation layout put it in bin/ directly. Fix it
1692 # installation layout put it in bin/ directly. Fix it
1676 f = open(hgbat, 'rb')
1693 f = open(hgbat, 'rb')
1677 data = f.read()
1694 data = f.read()
1678 f.close()
1695 f.close()
1679 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1696 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1680 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1697 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1681 '"%~dp0python" "%~dp0hg" %*')
1698 '"%~dp0python" "%~dp0hg" %*')
1682 f = open(hgbat, 'wb')
1699 f = open(hgbat, 'wb')
1683 f.write(data)
1700 f.write(data)
1684 f.close()
1701 f.close()
1685 else:
1702 else:
1686 print 'WARNING: cannot fix hg.bat reference to python.exe'
1703 print 'WARNING: cannot fix hg.bat reference to python.exe'
1687
1704
1688 if self.options.anycoverage:
1705 if self.options.anycoverage:
1689 custom = os.path.join(self._testdir, 'sitecustomize.py')
1706 custom = os.path.join(self._testdir, 'sitecustomize.py')
1690 target = os.path.join(self._pythondir, 'sitecustomize.py')
1707 target = os.path.join(self._pythondir, 'sitecustomize.py')
1691 vlog('# Installing coverage trigger to %s' % target)
1708 vlog('# Installing coverage trigger to %s' % target)
1692 shutil.copyfile(custom, target)
1709 shutil.copyfile(custom, target)
1693 rc = os.path.join(self._testdir, '.coveragerc')
1710 rc = os.path.join(self._testdir, '.coveragerc')
1694 vlog('# Installing coverage rc to %s' % rc)
1711 vlog('# Installing coverage rc to %s' % rc)
1695 os.environ['COVERAGE_PROCESS_START'] = rc
1712 os.environ['COVERAGE_PROCESS_START'] = rc
1696 fn = os.path.join(self._installdir, '..', '.coverage')
1713 fn = os.path.join(self._installdir, '..', '.coverage')
1697 os.environ['COVERAGE_FILE'] = fn
1714 os.environ['COVERAGE_FILE'] = fn
1698
1715
1699 def _checkhglib(self, verb):
1716 def _checkhglib(self, verb):
1700 """Ensure that the 'mercurial' package imported by python is
1717 """Ensure that the 'mercurial' package imported by python is
1701 the one we expect it to be. If not, print a warning to stderr."""
1718 the one we expect it to be. If not, print a warning to stderr."""
1702 expecthg = os.path.join(self._pythondir, 'mercurial')
1719 expecthg = os.path.join(self._pythondir, 'mercurial')
1703 actualhg = self._gethgpath()
1720 actualhg = self._gethgpath()
1704 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1721 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1705 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1722 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1706 ' (expected %s)\n'
1723 ' (expected %s)\n'
1707 % (verb, actualhg, expecthg))
1724 % (verb, actualhg, expecthg))
1708 def _gethgpath(self):
1725 def _gethgpath(self):
1709 """Return the path to the mercurial package that is actually found by
1726 """Return the path to the mercurial package that is actually found by
1710 the current Python interpreter."""
1727 the current Python interpreter."""
1711 if self._hgpath is not None:
1728 if self._hgpath is not None:
1712 return self._hgpath
1729 return self._hgpath
1713
1730
1714 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1731 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1715 pipe = os.popen(cmd % PYTHON)
1732 pipe = os.popen(cmd % PYTHON)
1716 try:
1733 try:
1717 self._hgpath = pipe.read().strip()
1734 self._hgpath = pipe.read().strip()
1718 finally:
1735 finally:
1719 pipe.close()
1736 pipe.close()
1720
1737
1721 return self._hgpath
1738 return self._hgpath
1722
1739
1723 def _outputcoverage(self):
1740 def _outputcoverage(self):
1741 """Produce code coverage output."""
1724 vlog('# Producing coverage report')
1742 vlog('# Producing coverage report')
1725 os.chdir(self._pythondir)
1743 os.chdir(self._pythondir)
1726
1744
1727 def covrun(*args):
1745 def covrun(*args):
1728 cmd = 'coverage %s' % ' '.join(args)
1746 cmd = 'coverage %s' % ' '.join(args)
1729 vlog('# Running: %s' % cmd)
1747 vlog('# Running: %s' % cmd)
1730 os.system(cmd)
1748 os.system(cmd)
1731
1749
1732 covrun('-c')
1750 covrun('-c')
1733 omit = ','.join(os.path.join(x, '*') for x in
1751 omit = ','.join(os.path.join(x, '*') for x in
1734 [self._bindir, self._testdir])
1752 [self._bindir, self._testdir])
1735 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1753 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1736 if self.options.htmlcov:
1754 if self.options.htmlcov:
1737 htmldir = os.path.join(self._testdir, 'htmlcov')
1755 htmldir = os.path.join(self._testdir, 'htmlcov')
1738 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1756 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1739 '"--omit=%s"' % omit)
1757 '"--omit=%s"' % omit)
1740 if self.options.annotate:
1758 if self.options.annotate:
1741 adir = os.path.join(self._testdir, 'annotated')
1759 adir = os.path.join(self._testdir, 'annotated')
1742 if not os.path.isdir(adir):
1760 if not os.path.isdir(adir):
1743 os.mkdir(adir)
1761 os.mkdir(adir)
1744 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1762 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1745
1763
1746 def _findprogram(self, program):
1764 def _findprogram(self, program):
1747 """Search PATH for a executable program"""
1765 """Search PATH for a executable program"""
1748 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1766 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1749 name = os.path.join(p, program)
1767 name = os.path.join(p, program)
1750 if os.name == 'nt' or os.access(name, os.X_OK):
1768 if os.name == 'nt' or os.access(name, os.X_OK):
1751 return name
1769 return name
1752 return None
1770 return None
1753
1771
1754 def _checktools(self):
1772 def _checktools(self):
1755 # Before we go any further, check for pre-requisite tools
1773 """Ensure tools required to run tests are present."""
1756 # stuff from coreutils (cat, rm, etc) are not tested
1757 for p in self.REQUIREDTOOLS:
1774 for p in self.REQUIREDTOOLS:
1758 if os.name == 'nt' and not p.endswith('.exe'):
1775 if os.name == 'nt' and not p.endswith('.exe'):
1759 p += '.exe'
1776 p += '.exe'
1760 found = self._findprogram(p)
1777 found = self._findprogram(p)
1761 if found:
1778 if found:
1762 vlog("# Found prerequisite", p, "at", found)
1779 vlog("# Found prerequisite", p, "at", found)
1763 else:
1780 else:
1764 print "WARNING: Did not find prerequisite tool: %s " % p
1781 print "WARNING: Did not find prerequisite tool: %s " % p
1765
1782
1766 if __name__ == '__main__':
1783 if __name__ == '__main__':
1767 runner = TestRunner()
1784 runner = TestRunner()
1768 sys.exit(runner.run(sys.argv[1:]))
1785 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now