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