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