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