##// END OF EJS Templates
run-tests: teach unittest about skipped tests
Gregory Szorc -
r21430:cf299265 default
parent child Browse files
Show More
@@ -1,1550 +1,1564 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import Queue as queue
59 import Queue as queue
60 import unittest
60 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):
985 class TestResult(unittest._TextTestResult):
986 """Holds results when executing via unittest."""
986 """Holds results when executing via unittest."""
987 # Don't worry too much about accessing the non-public _TextTestResult.
987 # Don't worry too much about accessing the non-public _TextTestResult.
988 # It is relatively common in Python testing tools.
988 # It is relatively common in Python testing tools.
989 def __init__(self, *args, **kwargs):
989 def __init__(self, *args, **kwargs):
990 super(TestResult, self).__init__(*args, **kwargs)
990 super(TestResult, self).__init__(*args, **kwargs)
991
991
992 # unittest.TestResult didn't have skipped until 2.7. We need to
993 # polyfill it.
994 self.skipped = []
995
996 # Polyfill.
997 def addSkip(self, test, reason):
998 self.skipped.append((test, reason))
999
1000 if self.showAll:
1001 self.stream.writeln('skipped %s' % reason)
1002 else:
1003 self.stream.write('s')
1004 self.stream.flush()
1005
992 class TextTestRunner(unittest.TextTestRunner):
1006 class TextTestRunner(unittest.TextTestRunner):
993 """Custom unittest test runner that uses appropriate settings."""
1007 """Custom unittest test runner that uses appropriate settings."""
994
1008
995 def _makeResult(self):
1009 def _makeResult(self):
996 return TestResult(self.stream, self.descriptions, self.verbosity)
1010 return TestResult(self.stream, self.descriptions, self.verbosity)
997
1011
998 class TestRunner(object):
1012 class TestRunner(object):
999 """Holds context for executing tests.
1013 """Holds context for executing tests.
1000
1014
1001 Tests rely on a lot of state. This object holds it for them.
1015 Tests rely on a lot of state. This object holds it for them.
1002 """
1016 """
1003
1017
1004 REQUIREDTOOLS = [
1018 REQUIREDTOOLS = [
1005 os.path.basename(sys.executable),
1019 os.path.basename(sys.executable),
1006 'diff',
1020 'diff',
1007 'grep',
1021 'grep',
1008 'unzip',
1022 'unzip',
1009 'gunzip',
1023 'gunzip',
1010 'bunzip2',
1024 'bunzip2',
1011 'sed',
1025 'sed',
1012 ]
1026 ]
1013
1027
1014 TESTTYPES = [
1028 TESTTYPES = [
1015 ('.py', PythonTest, '.out'),
1029 ('.py', PythonTest, '.out'),
1016 ('.t', TTest, ''),
1030 ('.t', TTest, ''),
1017 ]
1031 ]
1018
1032
1019 def __init__(self):
1033 def __init__(self):
1020 self.options = None
1034 self.options = None
1021 self.testdir = None
1035 self.testdir = None
1022 self.hgtmp = None
1036 self.hgtmp = None
1023 self.inst = None
1037 self.inst = None
1024 self.bindir = None
1038 self.bindir = None
1025 self.tmpbinddir = None
1039 self.tmpbinddir = None
1026 self.pythondir = None
1040 self.pythondir = None
1027 self.coveragefile = None
1041 self.coveragefile = None
1028 self.times = [] # Holds execution times of tests.
1042 self.times = [] # Holds execution times of tests.
1029 self.results = {
1043 self.results = {
1030 '.': [],
1044 '.': [],
1031 '!': [],
1045 '!': [],
1032 '~': [],
1046 '~': [],
1033 's': [],
1047 's': [],
1034 'i': [],
1048 'i': [],
1035 }
1049 }
1036 self.abort = [False]
1050 self.abort = [False]
1037 self._createdfiles = []
1051 self._createdfiles = []
1038 self._hgpath = None
1052 self._hgpath = None
1039
1053
1040 def run(self, args, parser=None):
1054 def run(self, args, parser=None):
1041 """Run the test suite."""
1055 """Run the test suite."""
1042 oldmask = os.umask(022)
1056 oldmask = os.umask(022)
1043 try:
1057 try:
1044 parser = parser or getparser()
1058 parser = parser or getparser()
1045 options, args = parseargs(args, parser)
1059 options, args = parseargs(args, parser)
1046 self.options = options
1060 self.options = options
1047
1061
1048 self._checktools()
1062 self._checktools()
1049 tests = self.findtests(args)
1063 tests = self.findtests(args)
1050 return self._run(tests)
1064 return self._run(tests)
1051 finally:
1065 finally:
1052 os.umask(oldmask)
1066 os.umask(oldmask)
1053
1067
1054 def _run(self, tests):
1068 def _run(self, tests):
1055 if self.options.random:
1069 if self.options.random:
1056 random.shuffle(tests)
1070 random.shuffle(tests)
1057 else:
1071 else:
1058 # keywords for slow tests
1072 # keywords for slow tests
1059 slow = 'svn gendoc check-code-hg'.split()
1073 slow = 'svn gendoc check-code-hg'.split()
1060 def sortkey(f):
1074 def sortkey(f):
1061 # run largest tests first, as they tend to take the longest
1075 # run largest tests first, as they tend to take the longest
1062 try:
1076 try:
1063 val = -os.stat(f).st_size
1077 val = -os.stat(f).st_size
1064 except OSError, e:
1078 except OSError, e:
1065 if e.errno != errno.ENOENT:
1079 if e.errno != errno.ENOENT:
1066 raise
1080 raise
1067 return -1e9 # file does not exist, tell early
1081 return -1e9 # file does not exist, tell early
1068 for kw in slow:
1082 for kw in slow:
1069 if kw in f:
1083 if kw in f:
1070 val *= 10
1084 val *= 10
1071 return val
1085 return val
1072 tests.sort(key=sortkey)
1086 tests.sort(key=sortkey)
1073
1087
1074 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1088 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1075
1089
1076 if 'PYTHONHASHSEED' not in os.environ:
1090 if 'PYTHONHASHSEED' not in os.environ:
1077 # use a random python hash seed all the time
1091 # use a random python hash seed all the time
1078 # we do the randomness ourself to know what seed is used
1092 # we do the randomness ourself to know what seed is used
1079 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1093 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1080
1094
1081 if self.options.tmpdir:
1095 if self.options.tmpdir:
1082 self.options.keep_tmpdir = True
1096 self.options.keep_tmpdir = True
1083 tmpdir = self.options.tmpdir
1097 tmpdir = self.options.tmpdir
1084 if os.path.exists(tmpdir):
1098 if os.path.exists(tmpdir):
1085 # Meaning of tmpdir has changed since 1.3: we used to create
1099 # Meaning of tmpdir has changed since 1.3: we used to create
1086 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1100 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1087 # tmpdir already exists.
1101 # tmpdir already exists.
1088 print "error: temp dir %r already exists" % tmpdir
1102 print "error: temp dir %r already exists" % tmpdir
1089 return 1
1103 return 1
1090
1104
1091 # Automatically removing tmpdir sounds convenient, but could
1105 # Automatically removing tmpdir sounds convenient, but could
1092 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1106 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1093 # or "--tmpdir=$HOME".
1107 # or "--tmpdir=$HOME".
1094 #vlog("# Removing temp dir", tmpdir)
1108 #vlog("# Removing temp dir", tmpdir)
1095 #shutil.rmtree(tmpdir)
1109 #shutil.rmtree(tmpdir)
1096 os.makedirs(tmpdir)
1110 os.makedirs(tmpdir)
1097 else:
1111 else:
1098 d = None
1112 d = None
1099 if os.name == 'nt':
1113 if os.name == 'nt':
1100 # without this, we get the default temp dir location, but
1114 # without this, we get the default temp dir location, but
1101 # in all lowercase, which causes troubles with paths (issue3490)
1115 # in all lowercase, which causes troubles with paths (issue3490)
1102 d = os.getenv('TMP')
1116 d = os.getenv('TMP')
1103 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1117 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1104 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1118 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1105
1119
1106 if self.options.with_hg:
1120 if self.options.with_hg:
1107 self.inst = None
1121 self.inst = None
1108 self.bindir = os.path.dirname(os.path.realpath(
1122 self.bindir = os.path.dirname(os.path.realpath(
1109 self.options.with_hg))
1123 self.options.with_hg))
1110 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1124 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1111 os.makedirs(self.tmpbindir)
1125 os.makedirs(self.tmpbindir)
1112
1126
1113 # This looks redundant with how Python initializes sys.path from
1127 # This looks redundant with how Python initializes sys.path from
1114 # the location of the script being executed. Needed because the
1128 # the location of the script being executed. Needed because the
1115 # "hg" specified by --with-hg is not the only Python script
1129 # "hg" specified by --with-hg is not the only Python script
1116 # executed in the test suite that needs to import 'mercurial'
1130 # executed in the test suite that needs to import 'mercurial'
1117 # ... which means it's not really redundant at all.
1131 # ... which means it's not really redundant at all.
1118 self.pythondir = self.bindir
1132 self.pythondir = self.bindir
1119 else:
1133 else:
1120 self.inst = os.path.join(self.hgtmp, "install")
1134 self.inst = os.path.join(self.hgtmp, "install")
1121 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1135 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1122 "bin")
1136 "bin")
1123 self.tmpbindir = self.bindir
1137 self.tmpbindir = self.bindir
1124 self.pythondir = os.path.join(self.inst, "lib", "python")
1138 self.pythondir = os.path.join(self.inst, "lib", "python")
1125
1139
1126 os.environ["BINDIR"] = self.bindir
1140 os.environ["BINDIR"] = self.bindir
1127 os.environ["PYTHON"] = PYTHON
1141 os.environ["PYTHON"] = PYTHON
1128
1142
1129 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1143 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1130 if self.tmpbindir != self.bindir:
1144 if self.tmpbindir != self.bindir:
1131 path = [self.tmpbindir] + path
1145 path = [self.tmpbindir] + path
1132 os.environ["PATH"] = os.pathsep.join(path)
1146 os.environ["PATH"] = os.pathsep.join(path)
1133
1147
1134 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1148 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1135 # can run .../tests/run-tests.py test-foo where test-foo
1149 # can run .../tests/run-tests.py test-foo where test-foo
1136 # adds an extension to HGRC. Also include run-test.py directory to
1150 # adds an extension to HGRC. Also include run-test.py directory to
1137 # import modules like heredoctest.
1151 # import modules like heredoctest.
1138 pypath = [self.pythondir, self.testdir,
1152 pypath = [self.pythondir, self.testdir,
1139 os.path.abspath(os.path.dirname(__file__))]
1153 os.path.abspath(os.path.dirname(__file__))]
1140 # We have to augment PYTHONPATH, rather than simply replacing
1154 # We have to augment PYTHONPATH, rather than simply replacing
1141 # it, in case external libraries are only available via current
1155 # it, in case external libraries are only available via current
1142 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1156 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1143 # are in /opt/subversion.)
1157 # are in /opt/subversion.)
1144 oldpypath = os.environ.get(IMPL_PATH)
1158 oldpypath = os.environ.get(IMPL_PATH)
1145 if oldpypath:
1159 if oldpypath:
1146 pypath.append(oldpypath)
1160 pypath.append(oldpypath)
1147 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1161 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1148
1162
1149 self.coveragefile = os.path.join(self.testdir, '.coverage')
1163 self.coveragefile = os.path.join(self.testdir, '.coverage')
1150
1164
1151 vlog("# Using TESTDIR", self.testdir)
1165 vlog("# Using TESTDIR", self.testdir)
1152 vlog("# Using HGTMP", self.hgtmp)
1166 vlog("# Using HGTMP", self.hgtmp)
1153 vlog("# Using PATH", os.environ["PATH"])
1167 vlog("# Using PATH", os.environ["PATH"])
1154 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1168 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1155
1169
1156 try:
1170 try:
1157 return self._runtests(tests) or 0
1171 return self._runtests(tests) or 0
1158 finally:
1172 finally:
1159 time.sleep(.1)
1173 time.sleep(.1)
1160 self._cleanup()
1174 self._cleanup()
1161
1175
1162 def findtests(self, args):
1176 def findtests(self, args):
1163 """Finds possible test files from arguments.
1177 """Finds possible test files from arguments.
1164
1178
1165 If you wish to inject custom tests into the test harness, this would
1179 If you wish to inject custom tests into the test harness, this would
1166 be a good function to monkeypatch or override in a derived class.
1180 be a good function to monkeypatch or override in a derived class.
1167 """
1181 """
1168 if not args:
1182 if not args:
1169 if self.options.changed:
1183 if self.options.changed:
1170 proc = Popen4('hg st --rev "%s" -man0 .' %
1184 proc = Popen4('hg st --rev "%s" -man0 .' %
1171 self.options.changed, None, 0)
1185 self.options.changed, None, 0)
1172 stdout, stderr = proc.communicate()
1186 stdout, stderr = proc.communicate()
1173 args = stdout.strip('\0').split('\0')
1187 args = stdout.strip('\0').split('\0')
1174 else:
1188 else:
1175 args = os.listdir('.')
1189 args = os.listdir('.')
1176
1190
1177 return [t for t in args
1191 return [t for t in args
1178 if os.path.basename(t).startswith('test-')
1192 if os.path.basename(t).startswith('test-')
1179 and (t.endswith('.py') or t.endswith('.t'))]
1193 and (t.endswith('.py') or t.endswith('.t'))]
1180
1194
1181 def _runtests(self, tests):
1195 def _runtests(self, tests):
1182 try:
1196 try:
1183 if self.inst:
1197 if self.inst:
1184 self._installhg()
1198 self._installhg()
1185 self._checkhglib("Testing")
1199 self._checkhglib("Testing")
1186 else:
1200 else:
1187 self._usecorrectpython()
1201 self._usecorrectpython()
1188
1202
1189 if self.options.restart:
1203 if self.options.restart:
1190 orig = list(tests)
1204 orig = list(tests)
1191 while tests:
1205 while tests:
1192 if os.path.exists(tests[0] + ".err"):
1206 if os.path.exists(tests[0] + ".err"):
1193 break
1207 break
1194 tests.pop(0)
1208 tests.pop(0)
1195 if not tests:
1209 if not tests:
1196 print "running all tests"
1210 print "running all tests"
1197 tests = orig
1211 tests = orig
1198
1212
1199 if self.options.unittest:
1213 if self.options.unittest:
1200 suite = unittest.TestSuite()
1214 suite = unittest.TestSuite()
1201 for count, testpath in enumerate(tests):
1215 for count, testpath in enumerate(tests):
1202 suite.addTest(self._gettest(testpath, count, asunit=True))
1216 suite.addTest(self._gettest(testpath, count, asunit=True))
1203
1217
1204 verbosity = 1
1218 verbosity = 1
1205 if self.options.verbose:
1219 if self.options.verbose:
1206 verbosity = 2
1220 verbosity = 2
1207 runner = TextTestRunner(verbosity=verbosity)
1221 runner = TextTestRunner(verbosity=verbosity)
1208 runner.run(suite)
1222 runner.run(suite)
1209 else:
1223 else:
1210 self._executetests(tests)
1224 self._executetests(tests)
1211
1225
1212 failed = len(self.results['!'])
1226 failed = len(self.results['!'])
1213 warned = len(self.results['~'])
1227 warned = len(self.results['~'])
1214 tested = len(self.results['.']) + failed + warned
1228 tested = len(self.results['.']) + failed + warned
1215 skipped = len(self.results['s'])
1229 skipped = len(self.results['s'])
1216 ignored = len(self.results['i'])
1230 ignored = len(self.results['i'])
1217
1231
1218 print
1232 print
1219 if not self.options.noskips:
1233 if not self.options.noskips:
1220 for s in self.results['s']:
1234 for s in self.results['s']:
1221 print "Skipped %s: %s" % s
1235 print "Skipped %s: %s" % s
1222 for s in self.results['~']:
1236 for s in self.results['~']:
1223 print "Warned %s: %s" % s
1237 print "Warned %s: %s" % s
1224 for s in self.results['!']:
1238 for s in self.results['!']:
1225 print "Failed %s: %s" % s
1239 print "Failed %s: %s" % s
1226 self._checkhglib("Tested")
1240 self._checkhglib("Tested")
1227 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1241 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1228 tested, skipped + ignored, warned, failed)
1242 tested, skipped + ignored, warned, failed)
1229 if self.results['!']:
1243 if self.results['!']:
1230 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1244 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1231 if self.options.time:
1245 if self.options.time:
1232 self._outputtimes()
1246 self._outputtimes()
1233
1247
1234 if self.options.anycoverage:
1248 if self.options.anycoverage:
1235 self._outputcoverage()
1249 self._outputcoverage()
1236 except KeyboardInterrupt:
1250 except KeyboardInterrupt:
1237 failed = True
1251 failed = True
1238 print "\ninterrupted!"
1252 print "\ninterrupted!"
1239
1253
1240 if failed:
1254 if failed:
1241 return 1
1255 return 1
1242 if warned:
1256 if warned:
1243 return 80
1257 return 80
1244
1258
1245 def _gettest(self, test, count, asunit=False):
1259 def _gettest(self, test, count, asunit=False):
1246 """Obtain a Test by looking at its filename.
1260 """Obtain a Test by looking at its filename.
1247
1261
1248 Returns a Test instance. The Test may not be runnable if it doesn't
1262 Returns a Test instance. The Test may not be runnable if it doesn't
1249 map to a known type.
1263 map to a known type.
1250 """
1264 """
1251 lctest = test.lower()
1265 lctest = test.lower()
1252 refpath = os.path.join(self.testdir, test)
1266 refpath = os.path.join(self.testdir, test)
1253
1267
1254 testcls = Test
1268 testcls = Test
1255
1269
1256 for ext, cls, out in self.TESTTYPES:
1270 for ext, cls, out in self.TESTTYPES:
1257 if lctest.endswith(ext):
1271 if lctest.endswith(ext):
1258 testcls = cls
1272 testcls = cls
1259 refpath = os.path.join(self.testdir, test + out)
1273 refpath = os.path.join(self.testdir, test + out)
1260 break
1274 break
1261
1275
1262 t = testcls(self, test, count, refpath)
1276 t = testcls(self, test, count, refpath)
1263
1277
1264 if not asunit:
1278 if not asunit:
1265 return t
1279 return t
1266
1280
1267 class MercurialTest(unittest.TestCase):
1281 class MercurialTest(unittest.TestCase):
1268 def __init__(self, name, *args, **kwargs):
1282 def __init__(self, name, *args, **kwargs):
1269 super(MercurialTest, self).__init__(*args, **kwargs)
1283 super(MercurialTest, self).__init__(*args, **kwargs)
1270 self.name = name
1284 self.name = name
1271
1285
1272 def shortDescription(self):
1286 def shortDescription(self):
1273 return self.name
1287 return self.name
1274
1288
1275 # Need to stash away the TestResult since we do custom things
1289 # Need to stash away the TestResult since we do custom things
1276 # with it.
1290 # with it.
1277 def run(self, result):
1291 def run(self, result):
1278 self._result = result
1292 self._result = result
1279
1293
1280 return super(MercurialTest, self).run(result)
1294 return super(MercurialTest, self).run(result)
1281
1295
1282 def runTest(self):
1296 def runTest(self):
1283 code, tname, msg = t.run()
1297 code, tname, msg = t.run()
1284
1298
1285 if code == '!':
1299 if code == '!':
1286 self._result.failures.append((self, msg))
1300 self._result.failures.append((self, msg))
1287 elif code == '~':
1301 elif code == '~':
1288 pass
1302 pass
1289 elif code == '.':
1303 elif code == '.':
1290 pass
1304 pass
1291 elif code == 's':
1305 elif code == 's':
1292 pass
1306 self._result.addSkip(self, msg)
1293 elif code == 'i':
1307 elif code == 'i':
1294 pass
1308 pass
1295 else:
1309 else:
1296 self.fail('Unknown test result code: %s' % code)
1310 self.fail('Unknown test result code: %s' % code)
1297
1311
1298 return MercurialTest(test)
1312 return MercurialTest(test)
1299
1313
1300 def _cleanup(self):
1314 def _cleanup(self):
1301 """Clean up state from this test invocation."""
1315 """Clean up state from this test invocation."""
1302
1316
1303 if self.options.keep_tmpdir:
1317 if self.options.keep_tmpdir:
1304 return
1318 return
1305
1319
1306 vlog("# Cleaning up HGTMP", self.hgtmp)
1320 vlog("# Cleaning up HGTMP", self.hgtmp)
1307 shutil.rmtree(self.hgtmp, True)
1321 shutil.rmtree(self.hgtmp, True)
1308 for f in self._createdfiles:
1322 for f in self._createdfiles:
1309 try:
1323 try:
1310 os.remove(f)
1324 os.remove(f)
1311 except OSError:
1325 except OSError:
1312 pass
1326 pass
1313
1327
1314 def _usecorrectpython(self):
1328 def _usecorrectpython(self):
1315 # Some tests run the Python interpreter. They must use the
1329 # Some tests run the Python interpreter. They must use the
1316 # same interpreter or bad things will happen.
1330 # same interpreter or bad things will happen.
1317 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1331 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1318 if getattr(os, 'symlink', None):
1332 if getattr(os, 'symlink', None):
1319 vlog("# Making python executable in test path a symlink to '%s'" %
1333 vlog("# Making python executable in test path a symlink to '%s'" %
1320 sys.executable)
1334 sys.executable)
1321 mypython = os.path.join(self.tmpbindir, pyexename)
1335 mypython = os.path.join(self.tmpbindir, pyexename)
1322 try:
1336 try:
1323 if os.readlink(mypython) == sys.executable:
1337 if os.readlink(mypython) == sys.executable:
1324 return
1338 return
1325 os.unlink(mypython)
1339 os.unlink(mypython)
1326 except OSError, err:
1340 except OSError, err:
1327 if err.errno != errno.ENOENT:
1341 if err.errno != errno.ENOENT:
1328 raise
1342 raise
1329 if self._findprogram(pyexename) != sys.executable:
1343 if self._findprogram(pyexename) != sys.executable:
1330 try:
1344 try:
1331 os.symlink(sys.executable, mypython)
1345 os.symlink(sys.executable, mypython)
1332 self._createdfiles.append(mypython)
1346 self._createdfiles.append(mypython)
1333 except OSError, err:
1347 except OSError, err:
1334 # child processes may race, which is harmless
1348 # child processes may race, which is harmless
1335 if err.errno != errno.EEXIST:
1349 if err.errno != errno.EEXIST:
1336 raise
1350 raise
1337 else:
1351 else:
1338 exedir, exename = os.path.split(sys.executable)
1352 exedir, exename = os.path.split(sys.executable)
1339 vlog("# Modifying search path to find %s as %s in '%s'" %
1353 vlog("# Modifying search path to find %s as %s in '%s'" %
1340 (exename, pyexename, exedir))
1354 (exename, pyexename, exedir))
1341 path = os.environ['PATH'].split(os.pathsep)
1355 path = os.environ['PATH'].split(os.pathsep)
1342 while exedir in path:
1356 while exedir in path:
1343 path.remove(exedir)
1357 path.remove(exedir)
1344 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1358 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1345 if not self._findprogram(pyexename):
1359 if not self._findprogram(pyexename):
1346 print "WARNING: Cannot find %s in search path" % pyexename
1360 print "WARNING: Cannot find %s in search path" % pyexename
1347
1361
1348 def _installhg(self):
1362 def _installhg(self):
1349 vlog("# Performing temporary installation of HG")
1363 vlog("# Performing temporary installation of HG")
1350 installerrs = os.path.join("tests", "install.err")
1364 installerrs = os.path.join("tests", "install.err")
1351 compiler = ''
1365 compiler = ''
1352 if self.options.compiler:
1366 if self.options.compiler:
1353 compiler = '--compiler ' + self.options.compiler
1367 compiler = '--compiler ' + self.options.compiler
1354 pure = self.options.pure and "--pure" or ""
1368 pure = self.options.pure and "--pure" or ""
1355 py3 = ''
1369 py3 = ''
1356 if sys.version_info[0] == 3:
1370 if sys.version_info[0] == 3:
1357 py3 = '--c2to3'
1371 py3 = '--c2to3'
1358
1372
1359 # Run installer in hg root
1373 # Run installer in hg root
1360 script = os.path.realpath(sys.argv[0])
1374 script = os.path.realpath(sys.argv[0])
1361 hgroot = os.path.dirname(os.path.dirname(script))
1375 hgroot = os.path.dirname(os.path.dirname(script))
1362 os.chdir(hgroot)
1376 os.chdir(hgroot)
1363 nohome = '--home=""'
1377 nohome = '--home=""'
1364 if os.name == 'nt':
1378 if os.name == 'nt':
1365 # The --home="" trick works only on OS where os.sep == '/'
1379 # The --home="" trick works only on OS where os.sep == '/'
1366 # because of a distutils convert_path() fast-path. Avoid it at
1380 # because of a distutils convert_path() fast-path. Avoid it at
1367 # least on Windows for now, deal with .pydistutils.cfg bugs
1381 # least on Windows for now, deal with .pydistutils.cfg bugs
1368 # when they happen.
1382 # when they happen.
1369 nohome = ''
1383 nohome = ''
1370 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1384 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1371 ' build %(compiler)s --build-base="%(base)s"'
1385 ' build %(compiler)s --build-base="%(base)s"'
1372 ' install --force --prefix="%(prefix)s"'
1386 ' install --force --prefix="%(prefix)s"'
1373 ' --install-lib="%(libdir)s"'
1387 ' --install-lib="%(libdir)s"'
1374 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1388 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1375 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1389 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1376 'compiler': compiler,
1390 'compiler': compiler,
1377 'base': os.path.join(self.hgtmp, "build"),
1391 'base': os.path.join(self.hgtmp, "build"),
1378 'prefix': self.inst, 'libdir': self.pythondir,
1392 'prefix': self.inst, 'libdir': self.pythondir,
1379 'bindir': self.bindir,
1393 'bindir': self.bindir,
1380 'nohome': nohome, 'logfile': installerrs})
1394 'nohome': nohome, 'logfile': installerrs})
1381 vlog("# Running", cmd)
1395 vlog("# Running", cmd)
1382 if os.system(cmd) == 0:
1396 if os.system(cmd) == 0:
1383 if not self.options.verbose:
1397 if not self.options.verbose:
1384 os.remove(installerrs)
1398 os.remove(installerrs)
1385 else:
1399 else:
1386 f = open(installerrs)
1400 f = open(installerrs)
1387 for line in f:
1401 for line in f:
1388 print line,
1402 print line,
1389 f.close()
1403 f.close()
1390 sys.exit(1)
1404 sys.exit(1)
1391 os.chdir(self.testdir)
1405 os.chdir(self.testdir)
1392
1406
1393 self._usecorrectpython()
1407 self._usecorrectpython()
1394
1408
1395 if self.options.py3k_warnings and not self.options.anycoverage:
1409 if self.options.py3k_warnings and not self.options.anycoverage:
1396 vlog("# Updating hg command to enable Py3k Warnings switch")
1410 vlog("# Updating hg command to enable Py3k Warnings switch")
1397 f = open(os.path.join(self.bindir, 'hg'), 'r')
1411 f = open(os.path.join(self.bindir, 'hg'), 'r')
1398 lines = [line.rstrip() for line in f]
1412 lines = [line.rstrip() for line in f]
1399 lines[0] += ' -3'
1413 lines[0] += ' -3'
1400 f.close()
1414 f.close()
1401 f = open(os.path.join(self.bindir, 'hg'), 'w')
1415 f = open(os.path.join(self.bindir, 'hg'), 'w')
1402 for line in lines:
1416 for line in lines:
1403 f.write(line + '\n')
1417 f.write(line + '\n')
1404 f.close()
1418 f.close()
1405
1419
1406 hgbat = os.path.join(self.bindir, 'hg.bat')
1420 hgbat = os.path.join(self.bindir, 'hg.bat')
1407 if os.path.isfile(hgbat):
1421 if os.path.isfile(hgbat):
1408 # hg.bat expects to be put in bin/scripts while run-tests.py
1422 # hg.bat expects to be put in bin/scripts while run-tests.py
1409 # installation layout put it in bin/ directly. Fix it
1423 # installation layout put it in bin/ directly. Fix it
1410 f = open(hgbat, 'rb')
1424 f = open(hgbat, 'rb')
1411 data = f.read()
1425 data = f.read()
1412 f.close()
1426 f.close()
1413 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1427 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1414 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1428 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1415 '"%~dp0python" "%~dp0hg" %*')
1429 '"%~dp0python" "%~dp0hg" %*')
1416 f = open(hgbat, 'wb')
1430 f = open(hgbat, 'wb')
1417 f.write(data)
1431 f.write(data)
1418 f.close()
1432 f.close()
1419 else:
1433 else:
1420 print 'WARNING: cannot fix hg.bat reference to python.exe'
1434 print 'WARNING: cannot fix hg.bat reference to python.exe'
1421
1435
1422 if self.options.anycoverage:
1436 if self.options.anycoverage:
1423 custom = os.path.join(self.testdir, 'sitecustomize.py')
1437 custom = os.path.join(self.testdir, 'sitecustomize.py')
1424 target = os.path.join(self.pythondir, 'sitecustomize.py')
1438 target = os.path.join(self.pythondir, 'sitecustomize.py')
1425 vlog('# Installing coverage trigger to %s' % target)
1439 vlog('# Installing coverage trigger to %s' % target)
1426 shutil.copyfile(custom, target)
1440 shutil.copyfile(custom, target)
1427 rc = os.path.join(self.testdir, '.coveragerc')
1441 rc = os.path.join(self.testdir, '.coveragerc')
1428 vlog('# Installing coverage rc to %s' % rc)
1442 vlog('# Installing coverage rc to %s' % rc)
1429 os.environ['COVERAGE_PROCESS_START'] = rc
1443 os.environ['COVERAGE_PROCESS_START'] = rc
1430 fn = os.path.join(self.inst, '..', '.coverage')
1444 fn = os.path.join(self.inst, '..', '.coverage')
1431 os.environ['COVERAGE_FILE'] = fn
1445 os.environ['COVERAGE_FILE'] = fn
1432
1446
1433 def _checkhglib(self, verb):
1447 def _checkhglib(self, verb):
1434 """Ensure that the 'mercurial' package imported by python is
1448 """Ensure that the 'mercurial' package imported by python is
1435 the one we expect it to be. If not, print a warning to stderr."""
1449 the one we expect it to be. If not, print a warning to stderr."""
1436 expecthg = os.path.join(self.pythondir, 'mercurial')
1450 expecthg = os.path.join(self.pythondir, 'mercurial')
1437 actualhg = self._gethgpath()
1451 actualhg = self._gethgpath()
1438 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1452 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1439 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1453 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1440 ' (expected %s)\n'
1454 ' (expected %s)\n'
1441 % (verb, actualhg, expecthg))
1455 % (verb, actualhg, expecthg))
1442 def _gethgpath(self):
1456 def _gethgpath(self):
1443 """Return the path to the mercurial package that is actually found by
1457 """Return the path to the mercurial package that is actually found by
1444 the current Python interpreter."""
1458 the current Python interpreter."""
1445 if self._hgpath is not None:
1459 if self._hgpath is not None:
1446 return self._hgpath
1460 return self._hgpath
1447
1461
1448 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1462 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1449 pipe = os.popen(cmd % PYTHON)
1463 pipe = os.popen(cmd % PYTHON)
1450 try:
1464 try:
1451 self._hgpath = pipe.read().strip()
1465 self._hgpath = pipe.read().strip()
1452 finally:
1466 finally:
1453 pipe.close()
1467 pipe.close()
1454
1468
1455 return self._hgpath
1469 return self._hgpath
1456
1470
1457 def _outputtimes(self):
1471 def _outputtimes(self):
1458 vlog('# Producing time report')
1472 vlog('# Producing time report')
1459 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1473 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1460 cols = '%7.3f %s'
1474 cols = '%7.3f %s'
1461 print '\n%-7s %s' % ('Time', 'Test')
1475 print '\n%-7s %s' % ('Time', 'Test')
1462 for test, timetaken in self.times:
1476 for test, timetaken in self.times:
1463 print cols % (timetaken, test)
1477 print cols % (timetaken, test)
1464
1478
1465 def _outputcoverage(self):
1479 def _outputcoverage(self):
1466 vlog('# Producing coverage report')
1480 vlog('# Producing coverage report')
1467 os.chdir(self.pythondir)
1481 os.chdir(self.pythondir)
1468
1482
1469 def covrun(*args):
1483 def covrun(*args):
1470 cmd = 'coverage %s' % ' '.join(args)
1484 cmd = 'coverage %s' % ' '.join(args)
1471 vlog('# Running: %s' % cmd)
1485 vlog('# Running: %s' % cmd)
1472 os.system(cmd)
1486 os.system(cmd)
1473
1487
1474 covrun('-c')
1488 covrun('-c')
1475 omit = ','.join(os.path.join(x, '*') for x in
1489 omit = ','.join(os.path.join(x, '*') for x in
1476 [self.bindir, self.testdir])
1490 [self.bindir, self.testdir])
1477 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1491 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1478 if self.options.htmlcov:
1492 if self.options.htmlcov:
1479 htmldir = os.path.join(self.testdir, 'htmlcov')
1493 htmldir = os.path.join(self.testdir, 'htmlcov')
1480 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1494 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1481 '"--omit=%s"' % omit)
1495 '"--omit=%s"' % omit)
1482 if self.options.annotate:
1496 if self.options.annotate:
1483 adir = os.path.join(self.testdir, 'annotated')
1497 adir = os.path.join(self.testdir, 'annotated')
1484 if not os.path.isdir(adir):
1498 if not os.path.isdir(adir):
1485 os.mkdir(adir)
1499 os.mkdir(adir)
1486 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1500 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1487
1501
1488 def _executetests(self, tests):
1502 def _executetests(self, tests):
1489 jobs = self.options.jobs
1503 jobs = self.options.jobs
1490 done = queue.Queue()
1504 done = queue.Queue()
1491 running = 0
1505 running = 0
1492 count = 0
1506 count = 0
1493
1507
1494 def job(test, count):
1508 def job(test, count):
1495 try:
1509 try:
1496 t = self._gettest(test, count)
1510 t = self._gettest(test, count)
1497 done.put(t.run())
1511 done.put(t.run())
1498 t.cleanup()
1512 t.cleanup()
1499 except KeyboardInterrupt:
1513 except KeyboardInterrupt:
1500 pass
1514 pass
1501 except: # re-raises
1515 except: # re-raises
1502 done.put(('!', test, 'run-test raised an error, see traceback'))
1516 done.put(('!', test, 'run-test raised an error, see traceback'))
1503 raise
1517 raise
1504
1518
1505 try:
1519 try:
1506 while tests or running:
1520 while tests or running:
1507 if not done.empty() or running == jobs or not tests:
1521 if not done.empty() or running == jobs or not tests:
1508 try:
1522 try:
1509 code, test, msg = done.get(True, 1)
1523 code, test, msg = done.get(True, 1)
1510 self.results[code].append((test, msg))
1524 self.results[code].append((test, msg))
1511 if self.options.first and code not in '.si':
1525 if self.options.first and code not in '.si':
1512 break
1526 break
1513 except queue.Empty:
1527 except queue.Empty:
1514 continue
1528 continue
1515 running -= 1
1529 running -= 1
1516 if tests and not running == jobs:
1530 if tests and not running == jobs:
1517 test = tests.pop(0)
1531 test = tests.pop(0)
1518 if self.options.loop:
1532 if self.options.loop:
1519 tests.append(test)
1533 tests.append(test)
1520 t = threading.Thread(target=job, name=test,
1534 t = threading.Thread(target=job, name=test,
1521 args=(test, count))
1535 args=(test, count))
1522 t.start()
1536 t.start()
1523 running += 1
1537 running += 1
1524 count += 1
1538 count += 1
1525 except KeyboardInterrupt:
1539 except KeyboardInterrupt:
1526 self.abort[0] = True
1540 self.abort[0] = True
1527
1541
1528 def _findprogram(self, program):
1542 def _findprogram(self, program):
1529 """Search PATH for a executable program"""
1543 """Search PATH for a executable program"""
1530 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1544 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1531 name = os.path.join(p, program)
1545 name = os.path.join(p, program)
1532 if os.name == 'nt' or os.access(name, os.X_OK):
1546 if os.name == 'nt' or os.access(name, os.X_OK):
1533 return name
1547 return name
1534 return None
1548 return None
1535
1549
1536 def _checktools(self):
1550 def _checktools(self):
1537 # Before we go any further, check for pre-requisite tools
1551 # Before we go any further, check for pre-requisite tools
1538 # stuff from coreutils (cat, rm, etc) are not tested
1552 # stuff from coreutils (cat, rm, etc) are not tested
1539 for p in self.REQUIREDTOOLS:
1553 for p in self.REQUIREDTOOLS:
1540 if os.name == 'nt' and not p.endswith('.exe'):
1554 if os.name == 'nt' and not p.endswith('.exe'):
1541 p += '.exe'
1555 p += '.exe'
1542 found = self._findprogram(p)
1556 found = self._findprogram(p)
1543 if found:
1557 if found:
1544 vlog("# Found prerequisite", p, "at", found)
1558 vlog("# Found prerequisite", p, "at", found)
1545 else:
1559 else:
1546 print "WARNING: Did not find prerequisite tool: %s " % p
1560 print "WARNING: Did not find prerequisite tool: %s " % p
1547
1561
1548 if __name__ == '__main__':
1562 if __name__ == '__main__':
1549 runner = TestRunner()
1563 runner = TestRunner()
1550 sys.exit(runner.run(sys.argv[1:]))
1564 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now