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