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