##// END OF EJS Templates
run-tests: move _gethgpath() into TestRunner
Gregory Szorc -
r21385:28414e5a default
parent child Browse files
Show More
@@ -1,1481 +1,1479 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 class TTest(Test):
621 class TTest(Test):
622 """A "t test" is a test backed by a .t file."""
622 """A "t test" is a test backed by a .t file."""
623
623
624 SKIPPED_PREFIX = 'skipped: '
624 SKIPPED_PREFIX = 'skipped: '
625 FAILED_PREFIX = 'hghave check failed: '
625 FAILED_PREFIX = 'hghave check failed: '
626 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
626 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
627
627
628 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
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(
629 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
630 {'\\': '\\\\', '\r': r'\r'})
630 {'\\': '\\\\', '\r': r'\r'})
631
631
632 def _run(self, testtmp, replacements, env):
632 def _run(self, testtmp, replacements, env):
633 f = open(self._path)
633 f = open(self._path)
634 lines = f.readlines()
634 lines = f.readlines()
635 f.close()
635 f.close()
636
636
637 salt, script, after, expected = self._parsetest(lines, testtmp)
637 salt, script, after, expected = self._parsetest(lines, testtmp)
638
638
639 # Write out the generated script.
639 # Write out the generated script.
640 fname = '%s.sh' % testtmp
640 fname = '%s.sh' % testtmp
641 f = open(fname, 'w')
641 f = open(fname, 'w')
642 for l in script:
642 for l in script:
643 f.write(l)
643 f.write(l)
644 f.close()
644 f.close()
645
645
646 cmd = '%s "%s"' % (self._options.shell, fname)
646 cmd = '%s "%s"' % (self._options.shell, fname)
647 vlog("# Running", cmd)
647 vlog("# Running", cmd)
648
648
649 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
649 exitcode, output = run(cmd, testtmp, self._options, replacements, env,
650 self._runner.abort)
650 self._runner.abort)
651 # Do not merge output if skipped. Return hghave message instead.
651 # Do not merge output if skipped. Return hghave message instead.
652 # Similarly, with --debug, output is None.
652 # Similarly, with --debug, output is None.
653 if exitcode == self.SKIPPED_STATUS or output is None:
653 if exitcode == self.SKIPPED_STATUS or output is None:
654 return exitcode, output
654 return exitcode, output
655
655
656 return self._processoutput(exitcode, output, salt, after, expected)
656 return self._processoutput(exitcode, output, salt, after, expected)
657
657
658 def _hghave(self, reqs, testtmp):
658 def _hghave(self, reqs, testtmp):
659 # 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.
660 tdir = self._testdir.replace('\\', '/')
660 tdir = self._testdir.replace('\\', '/')
661 proc = Popen4('%s -c "%s/hghave %s"' %
661 proc = Popen4('%s -c "%s/hghave %s"' %
662 (self._options.shell, tdir, ' '.join(reqs)),
662 (self._options.shell, tdir, ' '.join(reqs)),
663 testtmp, 0)
663 testtmp, 0)
664 stdout, stderr = proc.communicate()
664 stdout, stderr = proc.communicate()
665 ret = proc.wait()
665 ret = proc.wait()
666 if wifexited(ret):
666 if wifexited(ret):
667 ret = os.WEXITSTATUS(ret)
667 ret = os.WEXITSTATUS(ret)
668 if ret == 2:
668 if ret == 2:
669 print stdout
669 print stdout
670 sys.exit(1)
670 sys.exit(1)
671
671
672 return ret == 0
672 return ret == 0
673
673
674 def _parsetest(self, lines, testtmp):
674 def _parsetest(self, lines, testtmp):
675 # We generate a shell script which outputs unique markers to line
675 # We generate a shell script which outputs unique markers to line
676 # up script results with our source. These markers include input
676 # up script results with our source. These markers include input
677 # line number and the last return code.
677 # line number and the last return code.
678 salt = "SALT" + str(time.time())
678 salt = "SALT" + str(time.time())
679 def addsalt(line, inpython):
679 def addsalt(line, inpython):
680 if inpython:
680 if inpython:
681 script.append('%s %d 0\n' % (salt, line))
681 script.append('%s %d 0\n' % (salt, line))
682 else:
682 else:
683 script.append('echo %s %s $?\n' % (salt, line))
683 script.append('echo %s %s $?\n' % (salt, line))
684
684
685 script = []
685 script = []
686
686
687 # 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
688 # with non-active parts of the source, with synchronization by our
688 # with non-active parts of the source, with synchronization by our
689 # SALT line number markers. The after table contains the non-active
689 # SALT line number markers. The after table contains the non-active
690 # components, ordered by line number.
690 # components, ordered by line number.
691 after = {}
691 after = {}
692
692
693 # Expected shell script output.
693 # Expected shell script output.
694 expected = {}
694 expected = {}
695
695
696 pos = prepos = -1
696 pos = prepos = -1
697
697
698 # True or False when in a true or false conditional section
698 # True or False when in a true or false conditional section
699 skipping = None
699 skipping = None
700
700
701 # 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
702 # can generate the surrounding doctest magic.
702 # can generate the surrounding doctest magic.
703 inpython = False
703 inpython = False
704
704
705 if self._options.debug:
705 if self._options.debug:
706 script.append('set -x\n')
706 script.append('set -x\n')
707 if os.getenv('MSYSTEM'):
707 if os.getenv('MSYSTEM'):
708 script.append('alias pwd="pwd -W"\n')
708 script.append('alias pwd="pwd -W"\n')
709
709
710 for n, l in enumerate(lines):
710 for n, l in enumerate(lines):
711 if not l.endswith('\n'):
711 if not l.endswith('\n'):
712 l += '\n'
712 l += '\n'
713 if l.startswith('#if'):
713 if l.startswith('#if'):
714 lsplit = l.split()
714 lsplit = l.split()
715 if len(lsplit) < 2 or lsplit[0] != '#if':
715 if len(lsplit) < 2 or lsplit[0] != '#if':
716 after.setdefault(pos, []).append(' !!! invalid #if\n')
716 after.setdefault(pos, []).append(' !!! invalid #if\n')
717 if skipping is not None:
717 if skipping is not None:
718 after.setdefault(pos, []).append(' !!! nested #if\n')
718 after.setdefault(pos, []).append(' !!! nested #if\n')
719 skipping = not self._hghave(lsplit[1:], testtmp)
719 skipping = not self._hghave(lsplit[1:], testtmp)
720 after.setdefault(pos, []).append(l)
720 after.setdefault(pos, []).append(l)
721 elif l.startswith('#else'):
721 elif l.startswith('#else'):
722 if skipping is None:
722 if skipping is None:
723 after.setdefault(pos, []).append(' !!! missing #if\n')
723 after.setdefault(pos, []).append(' !!! missing #if\n')
724 skipping = not skipping
724 skipping = not skipping
725 after.setdefault(pos, []).append(l)
725 after.setdefault(pos, []).append(l)
726 elif l.startswith('#endif'):
726 elif l.startswith('#endif'):
727 if skipping is None:
727 if skipping is None:
728 after.setdefault(pos, []).append(' !!! missing #if\n')
728 after.setdefault(pos, []).append(' !!! missing #if\n')
729 skipping = None
729 skipping = None
730 after.setdefault(pos, []).append(l)
730 after.setdefault(pos, []).append(l)
731 elif skipping:
731 elif skipping:
732 after.setdefault(pos, []).append(l)
732 after.setdefault(pos, []).append(l)
733 elif l.startswith(' >>> '): # python inlines
733 elif l.startswith(' >>> '): # python inlines
734 after.setdefault(pos, []).append(l)
734 after.setdefault(pos, []).append(l)
735 prepos = pos
735 prepos = pos
736 pos = n
736 pos = n
737 if not inpython:
737 if not inpython:
738 # We've just entered a Python block. Add the header.
738 # We've just entered a Python block. Add the header.
739 inpython = True
739 inpython = True
740 addsalt(prepos, False) # Make sure we report the exit code.
740 addsalt(prepos, False) # Make sure we report the exit code.
741 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
741 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
742 addsalt(n, True)
742 addsalt(n, True)
743 script.append(l[2:])
743 script.append(l[2:])
744 elif l.startswith(' ... '): # python inlines
744 elif l.startswith(' ... '): # python inlines
745 after.setdefault(prepos, []).append(l)
745 after.setdefault(prepos, []).append(l)
746 script.append(l[2:])
746 script.append(l[2:])
747 elif l.startswith(' $ '): # commands
747 elif l.startswith(' $ '): # commands
748 if inpython:
748 if inpython:
749 script.append('EOF\n')
749 script.append('EOF\n')
750 inpython = False
750 inpython = False
751 after.setdefault(pos, []).append(l)
751 after.setdefault(pos, []).append(l)
752 prepos = pos
752 prepos = pos
753 pos = n
753 pos = n
754 addsalt(n, False)
754 addsalt(n, False)
755 cmd = l[4:].split()
755 cmd = l[4:].split()
756 if len(cmd) == 2 and cmd[0] == 'cd':
756 if len(cmd) == 2 and cmd[0] == 'cd':
757 l = ' $ cd %s || exit 1\n' % cmd[1]
757 l = ' $ cd %s || exit 1\n' % cmd[1]
758 script.append(l[4:])
758 script.append(l[4:])
759 elif l.startswith(' > '): # continuations
759 elif l.startswith(' > '): # continuations
760 after.setdefault(prepos, []).append(l)
760 after.setdefault(prepos, []).append(l)
761 script.append(l[4:])
761 script.append(l[4:])
762 elif l.startswith(' '): # results
762 elif l.startswith(' '): # results
763 # Queue up a list of expected results.
763 # Queue up a list of expected results.
764 expected.setdefault(pos, []).append(l[2:])
764 expected.setdefault(pos, []).append(l[2:])
765 else:
765 else:
766 if inpython:
766 if inpython:
767 script.append('EOF\n')
767 script.append('EOF\n')
768 inpython = False
768 inpython = False
769 # Non-command/result. Queue up for merged output.
769 # Non-command/result. Queue up for merged output.
770 after.setdefault(pos, []).append(l)
770 after.setdefault(pos, []).append(l)
771
771
772 if inpython:
772 if inpython:
773 script.append('EOF\n')
773 script.append('EOF\n')
774 if skipping is not None:
774 if skipping is not None:
775 after.setdefault(pos, []).append(' !!! missing #endif\n')
775 after.setdefault(pos, []).append(' !!! missing #endif\n')
776 addsalt(n + 1, False)
776 addsalt(n + 1, False)
777
777
778 return salt, script, after, expected
778 return salt, script, after, expected
779
779
780 def _processoutput(self, exitcode, output, salt, after, expected):
780 def _processoutput(self, exitcode, output, salt, after, expected):
781 # Merge the script output back into a unified test.
781 # Merge the script output back into a unified test.
782 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
782 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
783 if exitcode != 0:
783 if exitcode != 0:
784 warnonly = 3
784 warnonly = 3
785
785
786 pos = -1
786 pos = -1
787 postout = []
787 postout = []
788 for l in output:
788 for l in output:
789 lout, lcmd = l, None
789 lout, lcmd = l, None
790 if salt in l:
790 if salt in l:
791 lout, lcmd = l.split(salt, 1)
791 lout, lcmd = l.split(salt, 1)
792
792
793 if lout:
793 if lout:
794 if not lout.endswith('\n'):
794 if not lout.endswith('\n'):
795 lout += ' (no-eol)\n'
795 lout += ' (no-eol)\n'
796
796
797 # Find the expected output at the current position.
797 # Find the expected output at the current position.
798 el = None
798 el = None
799 if expected.get(pos, None):
799 if expected.get(pos, None):
800 el = expected[pos].pop(0)
800 el = expected[pos].pop(0)
801
801
802 r = TTest.linematch(el, lout)
802 r = TTest.linematch(el, lout)
803 if isinstance(r, str):
803 if isinstance(r, str):
804 if r == '+glob':
804 if r == '+glob':
805 lout = el[:-1] + ' (glob)\n'
805 lout = el[:-1] + ' (glob)\n'
806 r = '' # Warn only this line.
806 r = '' # Warn only this line.
807 elif r == '-glob':
807 elif r == '-glob':
808 lout = ''.join(el.rsplit(' (glob)', 1))
808 lout = ''.join(el.rsplit(' (glob)', 1))
809 r = '' # Warn only this line.
809 r = '' # Warn only this line.
810 else:
810 else:
811 log('\ninfo, unknown linematch result: %r\n' % r)
811 log('\ninfo, unknown linematch result: %r\n' % r)
812 r = False
812 r = False
813 if r:
813 if r:
814 postout.append(' ' + el)
814 postout.append(' ' + el)
815 else:
815 else:
816 if self.NEEDESCAPE(lout):
816 if self.NEEDESCAPE(lout):
817 lout = TTest.stringescape('%s (esc)\n' %
817 lout = TTest.stringescape('%s (esc)\n' %
818 lout.rstrip('\n'))
818 lout.rstrip('\n'))
819 postout.append(' ' + lout) # Let diff deal with it.
819 postout.append(' ' + lout) # Let diff deal with it.
820 if r != '': # If line failed.
820 if r != '': # If line failed.
821 warnonly = 3 # for sure not
821 warnonly = 3 # for sure not
822 elif warnonly == 1: # Is "not yet" and line is warn only.
822 elif warnonly == 1: # Is "not yet" and line is warn only.
823 warnonly = 2 # Yes do warn.
823 warnonly = 2 # Yes do warn.
824
824
825 if lcmd:
825 if lcmd:
826 # Add on last return code.
826 # Add on last return code.
827 ret = int(lcmd.split()[1])
827 ret = int(lcmd.split()[1])
828 if ret != 0:
828 if ret != 0:
829 postout.append(' [%s]\n' % ret)
829 postout.append(' [%s]\n' % ret)
830 if pos in after:
830 if pos in after:
831 # Merge in non-active test bits.
831 # Merge in non-active test bits.
832 postout += after.pop(pos)
832 postout += after.pop(pos)
833 pos = int(lcmd.split()[0])
833 pos = int(lcmd.split()[0])
834
834
835 if pos in after:
835 if pos in after:
836 postout += after.pop(pos)
836 postout += after.pop(pos)
837
837
838 if warnonly == 2:
838 if warnonly == 2:
839 exitcode = False # Set exitcode to warned.
839 exitcode = False # Set exitcode to warned.
840
840
841 return exitcode, postout
841 return exitcode, postout
842
842
843 @staticmethod
843 @staticmethod
844 def rematch(el, l):
844 def rematch(el, l):
845 try:
845 try:
846 # 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
847 if os.name == 'nt':
847 if os.name == 'nt':
848 return re.match(el + r'\r?\n\Z', l)
848 return re.match(el + r'\r?\n\Z', l)
849 return re.match(el + r'\n\Z', l)
849 return re.match(el + r'\n\Z', l)
850 except re.error:
850 except re.error:
851 # el is an invalid regex
851 # el is an invalid regex
852 return False
852 return False
853
853
854 @staticmethod
854 @staticmethod
855 def globmatch(el, l):
855 def globmatch(el, l):
856 # The only supported special characters are * and ? plus / which also
856 # The only supported special characters are * and ? plus / which also
857 # matches \ on windows. Escaping of these characters is supported.
857 # matches \ on windows. Escaping of these characters is supported.
858 if el + '\n' == l:
858 if el + '\n' == l:
859 if os.altsep:
859 if os.altsep:
860 # matching on "/" is not needed for this line
860 # matching on "/" is not needed for this line
861 return '-glob'
861 return '-glob'
862 return True
862 return True
863 i, n = 0, len(el)
863 i, n = 0, len(el)
864 res = ''
864 res = ''
865 while i < n:
865 while i < n:
866 c = el[i]
866 c = el[i]
867 i += 1
867 i += 1
868 if c == '\\' and el[i] in '*?\\/':
868 if c == '\\' and el[i] in '*?\\/':
869 res += el[i - 1:i + 1]
869 res += el[i - 1:i + 1]
870 i += 1
870 i += 1
871 elif c == '*':
871 elif c == '*':
872 res += '.*'
872 res += '.*'
873 elif c == '?':
873 elif c == '?':
874 res += '.'
874 res += '.'
875 elif c == '/' and os.altsep:
875 elif c == '/' and os.altsep:
876 res += '[/\\\\]'
876 res += '[/\\\\]'
877 else:
877 else:
878 res += re.escape(c)
878 res += re.escape(c)
879 return TTest.rematch(res, l)
879 return TTest.rematch(res, l)
880
880
881 @staticmethod
881 @staticmethod
882 def linematch(el, l):
882 def linematch(el, l):
883 if el == l: # perfect match (fast)
883 if el == l: # perfect match (fast)
884 return True
884 return True
885 if el:
885 if el:
886 if el.endswith(" (esc)\n"):
886 if el.endswith(" (esc)\n"):
887 el = el[:-7].decode('string-escape') + '\n'
887 el = el[:-7].decode('string-escape') + '\n'
888 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:
889 return True
889 return True
890 if el.endswith(" (re)\n"):
890 if el.endswith(" (re)\n"):
891 return TTest.rematch(el[:-6], l)
891 return TTest.rematch(el[:-6], l)
892 if el.endswith(" (glob)\n"):
892 if el.endswith(" (glob)\n"):
893 return TTest.globmatch(el[:-8], l)
893 return TTest.globmatch(el[:-8], l)
894 if os.altsep and l.replace('\\', '/') == el:
894 if os.altsep and l.replace('\\', '/') == el:
895 return '+glob'
895 return '+glob'
896 return False
896 return False
897
897
898 @staticmethod
898 @staticmethod
899 def parsehghaveoutput(lines):
899 def parsehghaveoutput(lines):
900 '''Parse hghave log lines.
900 '''Parse hghave log lines.
901
901
902 Return tuple of lists (missing, failed):
902 Return tuple of lists (missing, failed):
903 * the missing/unknown features
903 * the missing/unknown features
904 * the features for which existence check failed'''
904 * the features for which existence check failed'''
905 missing = []
905 missing = []
906 failed = []
906 failed = []
907 for line in lines:
907 for line in lines:
908 if line.startswith(TTest.SKIPPED_PREFIX):
908 if line.startswith(TTest.SKIPPED_PREFIX):
909 line = line.splitlines()[0]
909 line = line.splitlines()[0]
910 missing.append(line[len(TTest.SKIPPED_PREFIX):])
910 missing.append(line[len(TTest.SKIPPED_PREFIX):])
911 elif line.startswith(TTest.FAILED_PREFIX):
911 elif line.startswith(TTest.FAILED_PREFIX):
912 line = line.splitlines()[0]
912 line = line.splitlines()[0]
913 failed.append(line[len(TTest.FAILED_PREFIX):])
913 failed.append(line[len(TTest.FAILED_PREFIX):])
914
914
915 return missing, failed
915 return missing, failed
916
916
917 @staticmethod
917 @staticmethod
918 def _escapef(m):
918 def _escapef(m):
919 return TTest.ESCAPEMAP[m.group(0)]
919 return TTest.ESCAPEMAP[m.group(0)]
920
920
921 @staticmethod
921 @staticmethod
922 def _stringescape(s):
922 def _stringescape(s):
923 return TTest.ESCAPESUB(TTest._escapef, s)
923 return TTest.ESCAPESUB(TTest._escapef, s)
924
924
925
925
926 wifexited = getattr(os, "WIFEXITED", lambda x: False)
926 wifexited = getattr(os, "WIFEXITED", lambda x: False)
927 def run(cmd, wd, options, replacements, env, abort):
927 def run(cmd, wd, options, replacements, env, abort):
928 """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).
929 Return a tuple (exitcode, output). output is None in debug mode."""
929 Return a tuple (exitcode, output). output is None in debug mode."""
930 # 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
931 if options.debug:
931 if options.debug:
932 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
932 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
933 ret = proc.wait()
933 ret = proc.wait()
934 return (ret, None)
934 return (ret, None)
935
935
936 proc = Popen4(cmd, wd, options.timeout, env)
936 proc = Popen4(cmd, wd, options.timeout, env)
937 def cleanup():
937 def cleanup():
938 terminate(proc)
938 terminate(proc)
939 ret = proc.wait()
939 ret = proc.wait()
940 if ret == 0:
940 if ret == 0:
941 ret = signal.SIGTERM << 8
941 ret = signal.SIGTERM << 8
942 killdaemons(env['DAEMON_PIDS'])
942 killdaemons(env['DAEMON_PIDS'])
943 return ret
943 return ret
944
944
945 output = ''
945 output = ''
946 proc.tochild.close()
946 proc.tochild.close()
947
947
948 try:
948 try:
949 output = proc.fromchild.read()
949 output = proc.fromchild.read()
950 except KeyboardInterrupt:
950 except KeyboardInterrupt:
951 vlog('# Handling keyboard interrupt')
951 vlog('# Handling keyboard interrupt')
952 cleanup()
952 cleanup()
953 raise
953 raise
954
954
955 ret = proc.wait()
955 ret = proc.wait()
956 if wifexited(ret):
956 if wifexited(ret):
957 ret = os.WEXITSTATUS(ret)
957 ret = os.WEXITSTATUS(ret)
958
958
959 if proc.timeout:
959 if proc.timeout:
960 ret = 'timeout'
960 ret = 'timeout'
961
961
962 if ret:
962 if ret:
963 killdaemons(env['DAEMON_PIDS'])
963 killdaemons(env['DAEMON_PIDS'])
964
964
965 if abort[0]:
965 if abort[0]:
966 raise KeyboardInterrupt()
966 raise KeyboardInterrupt()
967
967
968 for s, r in replacements:
968 for s, r in replacements:
969 output = re.sub(s, r, output)
969 output = re.sub(s, r, output)
970 return ret, output.splitlines(True)
970 return ret, output.splitlines(True)
971
971
972 _hgpath = None
973
974 def _gethgpath():
975 """Return the path to the mercurial package that is actually found by
976 the current Python interpreter."""
977 global _hgpath
978 if _hgpath is not None:
979 return _hgpath
980
981 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
982 pipe = os.popen(cmd % PYTHON)
983 try:
984 _hgpath = pipe.read().strip()
985 finally:
986 pipe.close()
987 return _hgpath
988
989 iolock = threading.Lock()
972 iolock = threading.Lock()
990
973
991 class TestRunner(object):
974 class TestRunner(object):
992 """Holds context for executing tests.
975 """Holds context for executing tests.
993
976
994 Tests rely on a lot of state. This object holds it for them.
977 Tests rely on a lot of state. This object holds it for them.
995 """
978 """
996
979
997 REQUIREDTOOLS = [
980 REQUIREDTOOLS = [
998 os.path.basename(sys.executable),
981 os.path.basename(sys.executable),
999 'diff',
982 'diff',
1000 'grep',
983 'grep',
1001 'unzip',
984 'unzip',
1002 'gunzip',
985 'gunzip',
1003 'bunzip2',
986 'bunzip2',
1004 'sed',
987 'sed',
1005 ]
988 ]
1006
989
1007 TESTTYPES = [
990 TESTTYPES = [
1008 ('.py', PythonTest, '.out'),
991 ('.py', PythonTest, '.out'),
1009 ('.t', TTest, ''),
992 ('.t', TTest, ''),
1010 ]
993 ]
1011
994
1012 def __init__(self):
995 def __init__(self):
1013 self.options = None
996 self.options = None
1014 self.testdir = None
997 self.testdir = None
1015 self.hgtmp = None
998 self.hgtmp = None
1016 self.inst = None
999 self.inst = None
1017 self.bindir = None
1000 self.bindir = None
1018 self.tmpbinddir = None
1001 self.tmpbinddir = None
1019 self.pythondir = None
1002 self.pythondir = None
1020 self.coveragefile = None
1003 self.coveragefile = None
1021 self.times = [] # Holds execution times of tests.
1004 self.times = [] # Holds execution times of tests.
1022 self.results = {
1005 self.results = {
1023 '.': [],
1006 '.': [],
1024 '!': [],
1007 '!': [],
1025 '~': [],
1008 '~': [],
1026 's': [],
1009 's': [],
1027 'i': [],
1010 'i': [],
1028 }
1011 }
1029 self.abort = [False]
1012 self.abort = [False]
1030 self._createdfiles = []
1013 self._createdfiles = []
1014 self._hgpath = None
1031
1015
1032 def run(self, args, parser=None):
1016 def run(self, args, parser=None):
1033 """Run the test suite."""
1017 """Run the test suite."""
1034 oldmask = os.umask(022)
1018 oldmask = os.umask(022)
1035 try:
1019 try:
1036 parser = parser or getparser()
1020 parser = parser or getparser()
1037 options, args = parseargs(args, parser)
1021 options, args = parseargs(args, parser)
1038 self.options = options
1022 self.options = options
1039
1023
1040 self._checktools()
1024 self._checktools()
1041 tests = self.findtests(args)
1025 tests = self.findtests(args)
1042 return self._run(tests)
1026 return self._run(tests)
1043 finally:
1027 finally:
1044 os.umask(oldmask)
1028 os.umask(oldmask)
1045
1029
1046 def _run(self, tests):
1030 def _run(self, tests):
1047 if self.options.random:
1031 if self.options.random:
1048 random.shuffle(tests)
1032 random.shuffle(tests)
1049 else:
1033 else:
1050 # keywords for slow tests
1034 # keywords for slow tests
1051 slow = 'svn gendoc check-code-hg'.split()
1035 slow = 'svn gendoc check-code-hg'.split()
1052 def sortkey(f):
1036 def sortkey(f):
1053 # run largest tests first, as they tend to take the longest
1037 # run largest tests first, as they tend to take the longest
1054 try:
1038 try:
1055 val = -os.stat(f).st_size
1039 val = -os.stat(f).st_size
1056 except OSError, e:
1040 except OSError, e:
1057 if e.errno != errno.ENOENT:
1041 if e.errno != errno.ENOENT:
1058 raise
1042 raise
1059 return -1e9 # file does not exist, tell early
1043 return -1e9 # file does not exist, tell early
1060 for kw in slow:
1044 for kw in slow:
1061 if kw in f:
1045 if kw in f:
1062 val *= 10
1046 val *= 10
1063 return val
1047 return val
1064 tests.sort(key=sortkey)
1048 tests.sort(key=sortkey)
1065
1049
1066 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1050 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1067
1051
1068 if 'PYTHONHASHSEED' not in os.environ:
1052 if 'PYTHONHASHSEED' not in os.environ:
1069 # use a random python hash seed all the time
1053 # use a random python hash seed all the time
1070 # we do the randomness ourself to know what seed is used
1054 # we do the randomness ourself to know what seed is used
1071 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1055 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1072
1056
1073 if self.options.tmpdir:
1057 if self.options.tmpdir:
1074 self.options.keep_tmpdir = True
1058 self.options.keep_tmpdir = True
1075 tmpdir = self.options.tmpdir
1059 tmpdir = self.options.tmpdir
1076 if os.path.exists(tmpdir):
1060 if os.path.exists(tmpdir):
1077 # Meaning of tmpdir has changed since 1.3: we used to create
1061 # Meaning of tmpdir has changed since 1.3: we used to create
1078 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1062 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1079 # tmpdir already exists.
1063 # tmpdir already exists.
1080 print "error: temp dir %r already exists" % tmpdir
1064 print "error: temp dir %r already exists" % tmpdir
1081 return 1
1065 return 1
1082
1066
1083 # Automatically removing tmpdir sounds convenient, but could
1067 # Automatically removing tmpdir sounds convenient, but could
1084 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1068 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1085 # or "--tmpdir=$HOME".
1069 # or "--tmpdir=$HOME".
1086 #vlog("# Removing temp dir", tmpdir)
1070 #vlog("# Removing temp dir", tmpdir)
1087 #shutil.rmtree(tmpdir)
1071 #shutil.rmtree(tmpdir)
1088 os.makedirs(tmpdir)
1072 os.makedirs(tmpdir)
1089 else:
1073 else:
1090 d = None
1074 d = None
1091 if os.name == 'nt':
1075 if os.name == 'nt':
1092 # without this, we get the default temp dir location, but
1076 # without this, we get the default temp dir location, but
1093 # in all lowercase, which causes troubles with paths (issue3490)
1077 # in all lowercase, which causes troubles with paths (issue3490)
1094 d = os.getenv('TMP')
1078 d = os.getenv('TMP')
1095 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1079 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1096 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1080 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1097
1081
1098 if self.options.with_hg:
1082 if self.options.with_hg:
1099 self.inst = None
1083 self.inst = None
1100 self.bindir = os.path.dirname(os.path.realpath(
1084 self.bindir = os.path.dirname(os.path.realpath(
1101 self.options.with_hg))
1085 self.options.with_hg))
1102 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1086 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1103 os.makedirs(self.tmpbindir)
1087 os.makedirs(self.tmpbindir)
1104
1088
1105 # This looks redundant with how Python initializes sys.path from
1089 # This looks redundant with how Python initializes sys.path from
1106 # the location of the script being executed. Needed because the
1090 # the location of the script being executed. Needed because the
1107 # "hg" specified by --with-hg is not the only Python script
1091 # "hg" specified by --with-hg is not the only Python script
1108 # executed in the test suite that needs to import 'mercurial'
1092 # executed in the test suite that needs to import 'mercurial'
1109 # ... which means it's not really redundant at all.
1093 # ... which means it's not really redundant at all.
1110 self.pythondir = self.bindir
1094 self.pythondir = self.bindir
1111 else:
1095 else:
1112 self.inst = os.path.join(self.hgtmp, "install")
1096 self.inst = os.path.join(self.hgtmp, "install")
1113 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1097 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1114 "bin")
1098 "bin")
1115 self.tmpbindir = self.bindir
1099 self.tmpbindir = self.bindir
1116 self.pythondir = os.path.join(self.inst, "lib", "python")
1100 self.pythondir = os.path.join(self.inst, "lib", "python")
1117
1101
1118 os.environ["BINDIR"] = self.bindir
1102 os.environ["BINDIR"] = self.bindir
1119 os.environ["PYTHON"] = PYTHON
1103 os.environ["PYTHON"] = PYTHON
1120
1104
1121 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1105 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1122 if self.tmpbindir != self.bindir:
1106 if self.tmpbindir != self.bindir:
1123 path = [self.tmpbindir] + path
1107 path = [self.tmpbindir] + path
1124 os.environ["PATH"] = os.pathsep.join(path)
1108 os.environ["PATH"] = os.pathsep.join(path)
1125
1109
1126 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1110 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1127 # can run .../tests/run-tests.py test-foo where test-foo
1111 # can run .../tests/run-tests.py test-foo where test-foo
1128 # adds an extension to HGRC. Also include run-test.py directory to
1112 # adds an extension to HGRC. Also include run-test.py directory to
1129 # import modules like heredoctest.
1113 # import modules like heredoctest.
1130 pypath = [self.pythondir, self.testdir,
1114 pypath = [self.pythondir, self.testdir,
1131 os.path.abspath(os.path.dirname(__file__))]
1115 os.path.abspath(os.path.dirname(__file__))]
1132 # We have to augment PYTHONPATH, rather than simply replacing
1116 # We have to augment PYTHONPATH, rather than simply replacing
1133 # it, in case external libraries are only available via current
1117 # it, in case external libraries are only available via current
1134 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1118 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1135 # are in /opt/subversion.)
1119 # are in /opt/subversion.)
1136 oldpypath = os.environ.get(IMPL_PATH)
1120 oldpypath = os.environ.get(IMPL_PATH)
1137 if oldpypath:
1121 if oldpypath:
1138 pypath.append(oldpypath)
1122 pypath.append(oldpypath)
1139 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1123 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1140
1124
1141 self.coveragefile = os.path.join(self.testdir, '.coverage')
1125 self.coveragefile = os.path.join(self.testdir, '.coverage')
1142
1126
1143 vlog("# Using TESTDIR", self.testdir)
1127 vlog("# Using TESTDIR", self.testdir)
1144 vlog("# Using HGTMP", self.hgtmp)
1128 vlog("# Using HGTMP", self.hgtmp)
1145 vlog("# Using PATH", os.environ["PATH"])
1129 vlog("# Using PATH", os.environ["PATH"])
1146 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1130 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1147
1131
1148 try:
1132 try:
1149 return self._runtests(tests) or 0
1133 return self._runtests(tests) or 0
1150 finally:
1134 finally:
1151 time.sleep(.1)
1135 time.sleep(.1)
1152 self._cleanup()
1136 self._cleanup()
1153
1137
1154 def findtests(self, args):
1138 def findtests(self, args):
1155 """Finds possible test files from arguments.
1139 """Finds possible test files from arguments.
1156
1140
1157 If you wish to inject custom tests into the test harness, this would
1141 If you wish to inject custom tests into the test harness, this would
1158 be a good function to monkeypatch or override in a derived class.
1142 be a good function to monkeypatch or override in a derived class.
1159 """
1143 """
1160 if not args:
1144 if not args:
1161 if self.options.changed:
1145 if self.options.changed:
1162 proc = Popen4('hg st --rev "%s" -man0 .' %
1146 proc = Popen4('hg st --rev "%s" -man0 .' %
1163 self.options.changed, None, 0)
1147 self.options.changed, None, 0)
1164 stdout, stderr = proc.communicate()
1148 stdout, stderr = proc.communicate()
1165 args = stdout.strip('\0').split('\0')
1149 args = stdout.strip('\0').split('\0')
1166 else:
1150 else:
1167 args = os.listdir('.')
1151 args = os.listdir('.')
1168
1152
1169 return [t for t in args
1153 return [t for t in args
1170 if os.path.basename(t).startswith('test-')
1154 if os.path.basename(t).startswith('test-')
1171 and (t.endswith('.py') or t.endswith('.t'))]
1155 and (t.endswith('.py') or t.endswith('.t'))]
1172
1156
1173 def _runtests(self, tests):
1157 def _runtests(self, tests):
1174 try:
1158 try:
1175 if self.inst:
1159 if self.inst:
1176 self._installhg()
1160 self._installhg()
1177 self._checkhglib("Testing")
1161 self._checkhglib("Testing")
1178 else:
1162 else:
1179 self._usecorrectpython()
1163 self._usecorrectpython()
1180
1164
1181 if self.options.restart:
1165 if self.options.restart:
1182 orig = list(tests)
1166 orig = list(tests)
1183 while tests:
1167 while tests:
1184 if os.path.exists(tests[0] + ".err"):
1168 if os.path.exists(tests[0] + ".err"):
1185 break
1169 break
1186 tests.pop(0)
1170 tests.pop(0)
1187 if not tests:
1171 if not tests:
1188 print "running all tests"
1172 print "running all tests"
1189 tests = orig
1173 tests = orig
1190
1174
1191 self._executetests(tests)
1175 self._executetests(tests)
1192
1176
1193 failed = len(self.results['!'])
1177 failed = len(self.results['!'])
1194 warned = len(self.results['~'])
1178 warned = len(self.results['~'])
1195 tested = len(self.results['.']) + failed + warned
1179 tested = len(self.results['.']) + failed + warned
1196 skipped = len(self.results['s'])
1180 skipped = len(self.results['s'])
1197 ignored = len(self.results['i'])
1181 ignored = len(self.results['i'])
1198
1182
1199 print
1183 print
1200 if not self.options.noskips:
1184 if not self.options.noskips:
1201 for s in self.results['s']:
1185 for s in self.results['s']:
1202 print "Skipped %s: %s" % s
1186 print "Skipped %s: %s" % s
1203 for s in self.results['~']:
1187 for s in self.results['~']:
1204 print "Warned %s: %s" % s
1188 print "Warned %s: %s" % s
1205 for s in self.results['!']:
1189 for s in self.results['!']:
1206 print "Failed %s: %s" % s
1190 print "Failed %s: %s" % s
1207 self._checkhglib("Tested")
1191 self._checkhglib("Tested")
1208 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1192 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1209 tested, skipped + ignored, warned, failed)
1193 tested, skipped + ignored, warned, failed)
1210 if self.results['!']:
1194 if self.results['!']:
1211 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1195 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1212 if self.options.time:
1196 if self.options.time:
1213 self._outputtimes()
1197 self._outputtimes()
1214
1198
1215 if self.options.anycoverage:
1199 if self.options.anycoverage:
1216 self._outputcoverage()
1200 self._outputcoverage()
1217 except KeyboardInterrupt:
1201 except KeyboardInterrupt:
1218 failed = True
1202 failed = True
1219 print "\ninterrupted!"
1203 print "\ninterrupted!"
1220
1204
1221 if failed:
1205 if failed:
1222 return 1
1206 return 1
1223 if warned:
1207 if warned:
1224 return 80
1208 return 80
1225
1209
1226 def _gettest(self, test, count):
1210 def _gettest(self, test, count):
1227 """Obtain a Test by looking at its filename.
1211 """Obtain a Test by looking at its filename.
1228
1212
1229 Returns a Test instance. The Test may not be runnable if it doesn't
1213 Returns a Test instance. The Test may not be runnable if it doesn't
1230 map to a known type.
1214 map to a known type.
1231 """
1215 """
1232 lctest = test.lower()
1216 lctest = test.lower()
1233 refpath = os.path.join(self.testdir, test)
1217 refpath = os.path.join(self.testdir, test)
1234
1218
1235 testcls = Test
1219 testcls = Test
1236
1220
1237 for ext, cls, out in self.TESTTYPES:
1221 for ext, cls, out in self.TESTTYPES:
1238 if lctest.endswith(ext):
1222 if lctest.endswith(ext):
1239 testcls = cls
1223 testcls = cls
1240 refpath = os.path.join(self.testdir, test + out)
1224 refpath = os.path.join(self.testdir, test + out)
1241 break
1225 break
1242
1226
1243 return testcls(self, test, count, refpath)
1227 return testcls(self, test, count, refpath)
1244
1228
1245 def _cleanup(self):
1229 def _cleanup(self):
1246 """Clean up state from this test invocation."""
1230 """Clean up state from this test invocation."""
1247
1231
1248 if self.options.keep_tmpdir:
1232 if self.options.keep_tmpdir:
1249 return
1233 return
1250
1234
1251 vlog("# Cleaning up HGTMP", self.hgtmp)
1235 vlog("# Cleaning up HGTMP", self.hgtmp)
1252 shutil.rmtree(self.hgtmp, True)
1236 shutil.rmtree(self.hgtmp, True)
1253 for f in self._createdfiles:
1237 for f in self._createdfiles:
1254 try:
1238 try:
1255 os.remove(f)
1239 os.remove(f)
1256 except OSError:
1240 except OSError:
1257 pass
1241 pass
1258
1242
1259 def _usecorrectpython(self):
1243 def _usecorrectpython(self):
1260 # Some tests run the Python interpreter. They must use the
1244 # Some tests run the Python interpreter. They must use the
1261 # same interpreter or bad things will happen.
1245 # same interpreter or bad things will happen.
1262 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1246 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1263 if getattr(os, 'symlink', None):
1247 if getattr(os, 'symlink', None):
1264 vlog("# Making python executable in test path a symlink to '%s'" %
1248 vlog("# Making python executable in test path a symlink to '%s'" %
1265 sys.executable)
1249 sys.executable)
1266 mypython = os.path.join(self.tmpbindir, pyexename)
1250 mypython = os.path.join(self.tmpbindir, pyexename)
1267 try:
1251 try:
1268 if os.readlink(mypython) == sys.executable:
1252 if os.readlink(mypython) == sys.executable:
1269 return
1253 return
1270 os.unlink(mypython)
1254 os.unlink(mypython)
1271 except OSError, err:
1255 except OSError, err:
1272 if err.errno != errno.ENOENT:
1256 if err.errno != errno.ENOENT:
1273 raise
1257 raise
1274 if self._findprogram(pyexename) != sys.executable:
1258 if self._findprogram(pyexename) != sys.executable:
1275 try:
1259 try:
1276 os.symlink(sys.executable, mypython)
1260 os.symlink(sys.executable, mypython)
1277 self._createdfiles.append(mypython)
1261 self._createdfiles.append(mypython)
1278 except OSError, err:
1262 except OSError, err:
1279 # child processes may race, which is harmless
1263 # child processes may race, which is harmless
1280 if err.errno != errno.EEXIST:
1264 if err.errno != errno.EEXIST:
1281 raise
1265 raise
1282 else:
1266 else:
1283 exedir, exename = os.path.split(sys.executable)
1267 exedir, exename = os.path.split(sys.executable)
1284 vlog("# Modifying search path to find %s as %s in '%s'" %
1268 vlog("# Modifying search path to find %s as %s in '%s'" %
1285 (exename, pyexename, exedir))
1269 (exename, pyexename, exedir))
1286 path = os.environ['PATH'].split(os.pathsep)
1270 path = os.environ['PATH'].split(os.pathsep)
1287 while exedir in path:
1271 while exedir in path:
1288 path.remove(exedir)
1272 path.remove(exedir)
1289 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1273 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1290 if not self._findprogram(pyexename):
1274 if not self._findprogram(pyexename):
1291 print "WARNING: Cannot find %s in search path" % pyexename
1275 print "WARNING: Cannot find %s in search path" % pyexename
1292
1276
1293 def _installhg(self):
1277 def _installhg(self):
1294 vlog("# Performing temporary installation of HG")
1278 vlog("# Performing temporary installation of HG")
1295 installerrs = os.path.join("tests", "install.err")
1279 installerrs = os.path.join("tests", "install.err")
1296 compiler = ''
1280 compiler = ''
1297 if self.options.compiler:
1281 if self.options.compiler:
1298 compiler = '--compiler ' + self.options.compiler
1282 compiler = '--compiler ' + self.options.compiler
1299 pure = self.options.pure and "--pure" or ""
1283 pure = self.options.pure and "--pure" or ""
1300 py3 = ''
1284 py3 = ''
1301 if sys.version_info[0] == 3:
1285 if sys.version_info[0] == 3:
1302 py3 = '--c2to3'
1286 py3 = '--c2to3'
1303
1287
1304 # Run installer in hg root
1288 # Run installer in hg root
1305 script = os.path.realpath(sys.argv[0])
1289 script = os.path.realpath(sys.argv[0])
1306 hgroot = os.path.dirname(os.path.dirname(script))
1290 hgroot = os.path.dirname(os.path.dirname(script))
1307 os.chdir(hgroot)
1291 os.chdir(hgroot)
1308 nohome = '--home=""'
1292 nohome = '--home=""'
1309 if os.name == 'nt':
1293 if os.name == 'nt':
1310 # The --home="" trick works only on OS where os.sep == '/'
1294 # The --home="" trick works only on OS where os.sep == '/'
1311 # because of a distutils convert_path() fast-path. Avoid it at
1295 # because of a distutils convert_path() fast-path. Avoid it at
1312 # least on Windows for now, deal with .pydistutils.cfg bugs
1296 # least on Windows for now, deal with .pydistutils.cfg bugs
1313 # when they happen.
1297 # when they happen.
1314 nohome = ''
1298 nohome = ''
1315 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1299 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1316 ' build %(compiler)s --build-base="%(base)s"'
1300 ' build %(compiler)s --build-base="%(base)s"'
1317 ' install --force --prefix="%(prefix)s"'
1301 ' install --force --prefix="%(prefix)s"'
1318 ' --install-lib="%(libdir)s"'
1302 ' --install-lib="%(libdir)s"'
1319 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1303 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1320 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1304 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1321 'compiler': compiler,
1305 'compiler': compiler,
1322 'base': os.path.join(self.hgtmp, "build"),
1306 'base': os.path.join(self.hgtmp, "build"),
1323 'prefix': self.inst, 'libdir': self.pythondir,
1307 'prefix': self.inst, 'libdir': self.pythondir,
1324 'bindir': self.bindir,
1308 'bindir': self.bindir,
1325 'nohome': nohome, 'logfile': installerrs})
1309 'nohome': nohome, 'logfile': installerrs})
1326 vlog("# Running", cmd)
1310 vlog("# Running", cmd)
1327 if os.system(cmd) == 0:
1311 if os.system(cmd) == 0:
1328 if not self.options.verbose:
1312 if not self.options.verbose:
1329 os.remove(installerrs)
1313 os.remove(installerrs)
1330 else:
1314 else:
1331 f = open(installerrs)
1315 f = open(installerrs)
1332 for line in f:
1316 for line in f:
1333 print line,
1317 print line,
1334 f.close()
1318 f.close()
1335 sys.exit(1)
1319 sys.exit(1)
1336 os.chdir(self.testdir)
1320 os.chdir(self.testdir)
1337
1321
1338 self._usecorrectpython()
1322 self._usecorrectpython()
1339
1323
1340 if self.options.py3k_warnings and not self.options.anycoverage:
1324 if self.options.py3k_warnings and not self.options.anycoverage:
1341 vlog("# Updating hg command to enable Py3k Warnings switch")
1325 vlog("# Updating hg command to enable Py3k Warnings switch")
1342 f = open(os.path.join(self.bindir, 'hg'), 'r')
1326 f = open(os.path.join(self.bindir, 'hg'), 'r')
1343 lines = [line.rstrip() for line in f]
1327 lines = [line.rstrip() for line in f]
1344 lines[0] += ' -3'
1328 lines[0] += ' -3'
1345 f.close()
1329 f.close()
1346 f = open(os.path.join(self.bindir, 'hg'), 'w')
1330 f = open(os.path.join(self.bindir, 'hg'), 'w')
1347 for line in lines:
1331 for line in lines:
1348 f.write(line + '\n')
1332 f.write(line + '\n')
1349 f.close()
1333 f.close()
1350
1334
1351 hgbat = os.path.join(self.bindir, 'hg.bat')
1335 hgbat = os.path.join(self.bindir, 'hg.bat')
1352 if os.path.isfile(hgbat):
1336 if os.path.isfile(hgbat):
1353 # hg.bat expects to be put in bin/scripts while run-tests.py
1337 # hg.bat expects to be put in bin/scripts while run-tests.py
1354 # installation layout put it in bin/ directly. Fix it
1338 # installation layout put it in bin/ directly. Fix it
1355 f = open(hgbat, 'rb')
1339 f = open(hgbat, 'rb')
1356 data = f.read()
1340 data = f.read()
1357 f.close()
1341 f.close()
1358 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1342 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1359 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1343 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1360 '"%~dp0python" "%~dp0hg" %*')
1344 '"%~dp0python" "%~dp0hg" %*')
1361 f = open(hgbat, 'wb')
1345 f = open(hgbat, 'wb')
1362 f.write(data)
1346 f.write(data)
1363 f.close()
1347 f.close()
1364 else:
1348 else:
1365 print 'WARNING: cannot fix hg.bat reference to python.exe'
1349 print 'WARNING: cannot fix hg.bat reference to python.exe'
1366
1350
1367 if self.options.anycoverage:
1351 if self.options.anycoverage:
1368 custom = os.path.join(self.testdir, 'sitecustomize.py')
1352 custom = os.path.join(self.testdir, 'sitecustomize.py')
1369 target = os.path.join(self.pythondir, 'sitecustomize.py')
1353 target = os.path.join(self.pythondir, 'sitecustomize.py')
1370 vlog('# Installing coverage trigger to %s' % target)
1354 vlog('# Installing coverage trigger to %s' % target)
1371 shutil.copyfile(custom, target)
1355 shutil.copyfile(custom, target)
1372 rc = os.path.join(self.testdir, '.coveragerc')
1356 rc = os.path.join(self.testdir, '.coveragerc')
1373 vlog('# Installing coverage rc to %s' % rc)
1357 vlog('# Installing coverage rc to %s' % rc)
1374 os.environ['COVERAGE_PROCESS_START'] = rc
1358 os.environ['COVERAGE_PROCESS_START'] = rc
1375 fn = os.path.join(self.inst, '..', '.coverage')
1359 fn = os.path.join(self.inst, '..', '.coverage')
1376 os.environ['COVERAGE_FILE'] = fn
1360 os.environ['COVERAGE_FILE'] = fn
1377
1361
1378 def _checkhglib(self, verb):
1362 def _checkhglib(self, verb):
1379 """Ensure that the 'mercurial' package imported by python is
1363 """Ensure that the 'mercurial' package imported by python is
1380 the one we expect it to be. If not, print a warning to stderr."""
1364 the one we expect it to be. If not, print a warning to stderr."""
1381 expecthg = os.path.join(self.pythondir, 'mercurial')
1365 expecthg = os.path.join(self.pythondir, 'mercurial')
1382 actualhg = _gethgpath()
1366 actualhg = self._gethgpath()
1383 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1367 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1384 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1368 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1385 ' (expected %s)\n'
1369 ' (expected %s)\n'
1386 % (verb, actualhg, expecthg))
1370 % (verb, actualhg, expecthg))
1371 def _gethgpath(self):
1372 """Return the path to the mercurial package that is actually found by
1373 the current Python interpreter."""
1374 if self._hgpath is not None:
1375 return self._hgpath
1376
1377 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1378 pipe = os.popen(cmd % PYTHON)
1379 try:
1380 self._hgpath = pipe.read().strip()
1381 finally:
1382 pipe.close()
1383
1384 return self._hgpath
1387
1385
1388 def _outputtimes(self):
1386 def _outputtimes(self):
1389 vlog('# Producing time report')
1387 vlog('# Producing time report')
1390 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1388 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1391 cols = '%7.3f %s'
1389 cols = '%7.3f %s'
1392 print '\n%-7s %s' % ('Time', 'Test')
1390 print '\n%-7s %s' % ('Time', 'Test')
1393 for test, timetaken in self.times:
1391 for test, timetaken in self.times:
1394 print cols % (timetaken, test)
1392 print cols % (timetaken, test)
1395
1393
1396 def _outputcoverage(self):
1394 def _outputcoverage(self):
1397 vlog('# Producing coverage report')
1395 vlog('# Producing coverage report')
1398 os.chdir(self.pythondir)
1396 os.chdir(self.pythondir)
1399
1397
1400 def covrun(*args):
1398 def covrun(*args):
1401 cmd = 'coverage %s' % ' '.join(args)
1399 cmd = 'coverage %s' % ' '.join(args)
1402 vlog('# Running: %s' % cmd)
1400 vlog('# Running: %s' % cmd)
1403 os.system(cmd)
1401 os.system(cmd)
1404
1402
1405 covrun('-c')
1403 covrun('-c')
1406 omit = ','.join(os.path.join(x, '*') for x in
1404 omit = ','.join(os.path.join(x, '*') for x in
1407 [self.bindir, self.testdir])
1405 [self.bindir, self.testdir])
1408 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1406 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1409 if self.options.htmlcov:
1407 if self.options.htmlcov:
1410 htmldir = os.path.join(self.testdir, 'htmlcov')
1408 htmldir = os.path.join(self.testdir, 'htmlcov')
1411 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1409 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1412 '"--omit=%s"' % omit)
1410 '"--omit=%s"' % omit)
1413 if self.options.annotate:
1411 if self.options.annotate:
1414 adir = os.path.join(self.testdir, 'annotated')
1412 adir = os.path.join(self.testdir, 'annotated')
1415 if not os.path.isdir(adir):
1413 if not os.path.isdir(adir):
1416 os.mkdir(adir)
1414 os.mkdir(adir)
1417 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1415 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1418
1416
1419 def _executetests(self, tests):
1417 def _executetests(self, tests):
1420 jobs = self.options.jobs
1418 jobs = self.options.jobs
1421 done = queue.Queue()
1419 done = queue.Queue()
1422 running = 0
1420 running = 0
1423 count = 0
1421 count = 0
1424
1422
1425 def job(test, count):
1423 def job(test, count):
1426 try:
1424 try:
1427 t = self._gettest(test, count)
1425 t = self._gettest(test, count)
1428 done.put(t.run())
1426 done.put(t.run())
1429 t.cleanup()
1427 t.cleanup()
1430 except KeyboardInterrupt:
1428 except KeyboardInterrupt:
1431 pass
1429 pass
1432 except: # re-raises
1430 except: # re-raises
1433 done.put(('!', test, 'run-test raised an error, see traceback'))
1431 done.put(('!', test, 'run-test raised an error, see traceback'))
1434 raise
1432 raise
1435
1433
1436 try:
1434 try:
1437 while tests or running:
1435 while tests or running:
1438 if not done.empty() or running == jobs or not tests:
1436 if not done.empty() or running == jobs or not tests:
1439 try:
1437 try:
1440 code, test, msg = done.get(True, 1)
1438 code, test, msg = done.get(True, 1)
1441 self.results[code].append((test, msg))
1439 self.results[code].append((test, msg))
1442 if self.options.first and code not in '.si':
1440 if self.options.first and code not in '.si':
1443 break
1441 break
1444 except queue.Empty:
1442 except queue.Empty:
1445 continue
1443 continue
1446 running -= 1
1444 running -= 1
1447 if tests and not running == jobs:
1445 if tests and not running == jobs:
1448 test = tests.pop(0)
1446 test = tests.pop(0)
1449 if self.options.loop:
1447 if self.options.loop:
1450 tests.append(test)
1448 tests.append(test)
1451 t = threading.Thread(target=job, name=test,
1449 t = threading.Thread(target=job, name=test,
1452 args=(test, count))
1450 args=(test, count))
1453 t.start()
1451 t.start()
1454 running += 1
1452 running += 1
1455 count += 1
1453 count += 1
1456 except KeyboardInterrupt:
1454 except KeyboardInterrupt:
1457 self.abort[0] = True
1455 self.abort[0] = True
1458
1456
1459 def _findprogram(self, program):
1457 def _findprogram(self, program):
1460 """Search PATH for a executable program"""
1458 """Search PATH for a executable program"""
1461 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1459 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1462 name = os.path.join(p, program)
1460 name = os.path.join(p, program)
1463 if os.name == 'nt' or os.access(name, os.X_OK):
1461 if os.name == 'nt' or os.access(name, os.X_OK):
1464 return name
1462 return name
1465 return None
1463 return None
1466
1464
1467 def _checktools(self):
1465 def _checktools(self):
1468 # Before we go any further, check for pre-requisite tools
1466 # Before we go any further, check for pre-requisite tools
1469 # stuff from coreutils (cat, rm, etc) are not tested
1467 # stuff from coreutils (cat, rm, etc) are not tested
1470 for p in self.REQUIREDTOOLS:
1468 for p in self.REQUIREDTOOLS:
1471 if os.name == 'nt' and not p.endswith('.exe'):
1469 if os.name == 'nt' and not p.endswith('.exe'):
1472 p += '.exe'
1470 p += '.exe'
1473 found = self._findprogram(p)
1471 found = self._findprogram(p)
1474 if found:
1472 if found:
1475 vlog("# Found prerequisite", p, "at", found)
1473 vlog("# Found prerequisite", p, "at", found)
1476 else:
1474 else:
1477 print "WARNING: Did not find prerequisite tool: %s " % p
1475 print "WARNING: Did not find prerequisite tool: %s " % p
1478
1476
1479 if __name__ == '__main__':
1477 if __name__ == '__main__':
1480 runner = TestRunner()
1478 runner = TestRunner()
1481 sys.exit(runner.run(sys.argv[1:]))
1479 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now