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