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