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