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