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