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