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