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