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