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