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