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