##// END OF EJS Templates
runtests: add a function to test if IPv6 is available...
Jun Wu -
r30984:15f9084a default
parent child Browse files
Show More
@@ -1,2581 +1,2604 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 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import difflib
48 import difflib
49 import distutils.version as version
49 import distutils.version as version
50 import errno
50 import errno
51 import json
51 import json
52 import optparse
52 import optparse
53 import os
53 import os
54 import random
54 import random
55 import re
55 import re
56 import shutil
56 import shutil
57 import signal
57 import signal
58 import socket
58 import socket
59 import subprocess
59 import subprocess
60 import sys
60 import sys
61 try:
61 try:
62 import sysconfig
62 import sysconfig
63 except ImportError:
63 except ImportError:
64 # sysconfig doesn't exist in Python 2.6
64 # sysconfig doesn't exist in Python 2.6
65 sysconfig = None
65 sysconfig = None
66 import tempfile
66 import tempfile
67 import threading
67 import threading
68 import time
68 import time
69 import unittest
69 import unittest
70 import xml.dom.minidom as minidom
70 import xml.dom.minidom as minidom
71
71
72 try:
72 try:
73 import Queue as queue
73 import Queue as queue
74 except ImportError:
74 except ImportError:
75 import queue
75 import queue
76
76
77 if os.environ.get('RTUNICODEPEDANTRY', False):
77 if os.environ.get('RTUNICODEPEDANTRY', False):
78 try:
78 try:
79 reload(sys)
79 reload(sys)
80 sys.setdefaultencoding("undefined")
80 sys.setdefaultencoding("undefined")
81 except NameError:
81 except NameError:
82 pass
82 pass
83
83
84 osenvironb = getattr(os, 'environb', os.environ)
84 osenvironb = getattr(os, 'environb', os.environ)
85 processlock = threading.Lock()
85 processlock = threading.Lock()
86
86
87 if sys.version_info > (3, 5, 0):
87 if sys.version_info > (3, 5, 0):
88 PYTHON3 = True
88 PYTHON3 = True
89 xrange = range # we use xrange in one place, and we'd rather not use range
89 xrange = range # we use xrange in one place, and we'd rather not use range
90 def _bytespath(p):
90 def _bytespath(p):
91 return p.encode('utf-8')
91 return p.encode('utf-8')
92
92
93 def _strpath(p):
93 def _strpath(p):
94 return p.decode('utf-8')
94 return p.decode('utf-8')
95
95
96 elif sys.version_info >= (3, 0, 0):
96 elif sys.version_info >= (3, 0, 0):
97 print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
97 print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
98 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
98 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
99 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
99 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
100 else:
100 else:
101 PYTHON3 = False
101 PYTHON3 = False
102
102
103 # In python 2.x, path operations are generally done using
103 # In python 2.x, path operations are generally done using
104 # bytestrings by default, so we don't have to do any extra
104 # bytestrings by default, so we don't have to do any extra
105 # fiddling there. We define the wrapper functions anyway just to
105 # fiddling there. We define the wrapper functions anyway just to
106 # help keep code consistent between platforms.
106 # help keep code consistent between platforms.
107 def _bytespath(p):
107 def _bytespath(p):
108 return p
108 return p
109
109
110 _strpath = _bytespath
110 _strpath = _bytespath
111
111
112 # For Windows support
112 # For Windows support
113 wifexited = getattr(os, "WIFEXITED", lambda x: False)
113 wifexited = getattr(os, "WIFEXITED", lambda x: False)
114
114
115 # Whether to use IPv6
116 def checkipv6available(port=20058):
117 """return true if we can listen on localhost's IPv6 ports"""
118 family = getattr(socket, 'AF_INET6', None)
119 if family is None:
120 return False
121 try:
122 s = socket.socket(family, socket.SOCK_STREAM)
123 s.bind(('localhost', port))
124 s.close()
125 return True
126 except socket.error as exc:
127 if exc.errno == errno.EADDRINUSE:
128 return True
129 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
130 return False
131 else:
132 raise
133 else:
134 return False
135
136 useipv6 = checkipv6available()
137
115 def checkportisavailable(port):
138 def checkportisavailable(port):
116 """return true if a port seems free to bind on localhost"""
139 """return true if a port seems free to bind on localhost"""
117 families = [getattr(socket, i, None)
140 families = [getattr(socket, i, None)
118 for i in ('AF_INET', 'AF_INET6')
141 for i in ('AF_INET', 'AF_INET6')
119 if getattr(socket, i, None) is not None]
142 if getattr(socket, i, None) is not None]
120 for family in families:
143 for family in families:
121 try:
144 try:
122 s = socket.socket(family, socket.SOCK_STREAM)
145 s = socket.socket(family, socket.SOCK_STREAM)
123 s.bind(('localhost', port))
146 s.bind(('localhost', port))
124 s.close()
147 s.close()
125 return True
148 return True
126 except socket.error as exc:
149 except socket.error as exc:
127 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
150 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
128 errno.EPROTONOSUPPORT):
151 errno.EPROTONOSUPPORT):
129 raise
152 raise
130 return False
153 return False
131
154
132 closefds = os.name == 'posix'
155 closefds = os.name == 'posix'
133 def Popen4(cmd, wd, timeout, env=None):
156 def Popen4(cmd, wd, timeout, env=None):
134 processlock.acquire()
157 processlock.acquire()
135 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
158 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
136 close_fds=closefds,
159 close_fds=closefds,
137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
160 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 stderr=subprocess.STDOUT)
161 stderr=subprocess.STDOUT)
139 processlock.release()
162 processlock.release()
140
163
141 p.fromchild = p.stdout
164 p.fromchild = p.stdout
142 p.tochild = p.stdin
165 p.tochild = p.stdin
143 p.childerr = p.stderr
166 p.childerr = p.stderr
144
167
145 p.timeout = False
168 p.timeout = False
146 if timeout:
169 if timeout:
147 def t():
170 def t():
148 start = time.time()
171 start = time.time()
149 while time.time() - start < timeout and p.returncode is None:
172 while time.time() - start < timeout and p.returncode is None:
150 time.sleep(.1)
173 time.sleep(.1)
151 p.timeout = True
174 p.timeout = True
152 if p.returncode is None:
175 if p.returncode is None:
153 terminate(p)
176 terminate(p)
154 threading.Thread(target=t).start()
177 threading.Thread(target=t).start()
155
178
156 return p
179 return p
157
180
158 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
181 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
159 IMPL_PATH = b'PYTHONPATH'
182 IMPL_PATH = b'PYTHONPATH'
160 if 'java' in sys.platform:
183 if 'java' in sys.platform:
161 IMPL_PATH = b'JYTHONPATH'
184 IMPL_PATH = b'JYTHONPATH'
162
185
163 defaults = {
186 defaults = {
164 'jobs': ('HGTEST_JOBS', 1),
187 'jobs': ('HGTEST_JOBS', 1),
165 'timeout': ('HGTEST_TIMEOUT', 180),
188 'timeout': ('HGTEST_TIMEOUT', 180),
166 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
189 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
167 'port': ('HGTEST_PORT', 20059),
190 'port': ('HGTEST_PORT', 20059),
168 'shell': ('HGTEST_SHELL', 'sh'),
191 'shell': ('HGTEST_SHELL', 'sh'),
169 }
192 }
170
193
171 def canonpath(path):
194 def canonpath(path):
172 return os.path.realpath(os.path.expanduser(path))
195 return os.path.realpath(os.path.expanduser(path))
173
196
174 def parselistfiles(files, listtype, warn=True):
197 def parselistfiles(files, listtype, warn=True):
175 entries = dict()
198 entries = dict()
176 for filename in files:
199 for filename in files:
177 try:
200 try:
178 path = os.path.expanduser(os.path.expandvars(filename))
201 path = os.path.expanduser(os.path.expandvars(filename))
179 f = open(path, "rb")
202 f = open(path, "rb")
180 except IOError as err:
203 except IOError as err:
181 if err.errno != errno.ENOENT:
204 if err.errno != errno.ENOENT:
182 raise
205 raise
183 if warn:
206 if warn:
184 print("warning: no such %s file: %s" % (listtype, filename))
207 print("warning: no such %s file: %s" % (listtype, filename))
185 continue
208 continue
186
209
187 for line in f.readlines():
210 for line in f.readlines():
188 line = line.split(b'#', 1)[0].strip()
211 line = line.split(b'#', 1)[0].strip()
189 if line:
212 if line:
190 entries[line] = filename
213 entries[line] = filename
191
214
192 f.close()
215 f.close()
193 return entries
216 return entries
194
217
195 def getparser():
218 def getparser():
196 """Obtain the OptionParser used by the CLI."""
219 """Obtain the OptionParser used by the CLI."""
197 parser = optparse.OptionParser("%prog [options] [tests]")
220 parser = optparse.OptionParser("%prog [options] [tests]")
198
221
199 # keep these sorted
222 # keep these sorted
200 parser.add_option("--blacklist", action="append",
223 parser.add_option("--blacklist", action="append",
201 help="skip tests listed in the specified blacklist file")
224 help="skip tests listed in the specified blacklist file")
202 parser.add_option("--whitelist", action="append",
225 parser.add_option("--whitelist", action="append",
203 help="always run tests listed in the specified whitelist file")
226 help="always run tests listed in the specified whitelist file")
204 parser.add_option("--changed", type="string",
227 parser.add_option("--changed", type="string",
205 help="run tests that are changed in parent rev or working directory")
228 help="run tests that are changed in parent rev or working directory")
206 parser.add_option("-C", "--annotate", action="store_true",
229 parser.add_option("-C", "--annotate", action="store_true",
207 help="output files annotated with coverage")
230 help="output files annotated with coverage")
208 parser.add_option("-c", "--cover", action="store_true",
231 parser.add_option("-c", "--cover", action="store_true",
209 help="print a test coverage report")
232 help="print a test coverage report")
210 parser.add_option("-d", "--debug", action="store_true",
233 parser.add_option("-d", "--debug", action="store_true",
211 help="debug mode: write output of test scripts to console"
234 help="debug mode: write output of test scripts to console"
212 " rather than capturing and diffing it (disables timeout)")
235 " rather than capturing and diffing it (disables timeout)")
213 parser.add_option("-f", "--first", action="store_true",
236 parser.add_option("-f", "--first", action="store_true",
214 help="exit on the first test failure")
237 help="exit on the first test failure")
215 parser.add_option("-H", "--htmlcov", action="store_true",
238 parser.add_option("-H", "--htmlcov", action="store_true",
216 help="create an HTML report of the coverage of the files")
239 help="create an HTML report of the coverage of the files")
217 parser.add_option("-i", "--interactive", action="store_true",
240 parser.add_option("-i", "--interactive", action="store_true",
218 help="prompt to accept changed output")
241 help="prompt to accept changed output")
219 parser.add_option("-j", "--jobs", type="int",
242 parser.add_option("-j", "--jobs", type="int",
220 help="number of jobs to run in parallel"
243 help="number of jobs to run in parallel"
221 " (default: $%s or %d)" % defaults['jobs'])
244 " (default: $%s or %d)" % defaults['jobs'])
222 parser.add_option("--keep-tmpdir", action="store_true",
245 parser.add_option("--keep-tmpdir", action="store_true",
223 help="keep temporary directory after running tests")
246 help="keep temporary directory after running tests")
224 parser.add_option("-k", "--keywords",
247 parser.add_option("-k", "--keywords",
225 help="run tests matching keywords")
248 help="run tests matching keywords")
226 parser.add_option("-l", "--local", action="store_true",
249 parser.add_option("-l", "--local", action="store_true",
227 help="shortcut for --with-hg=<testdir>/../hg, "
250 help="shortcut for --with-hg=<testdir>/../hg, "
228 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
251 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
229 parser.add_option("--loop", action="store_true",
252 parser.add_option("--loop", action="store_true",
230 help="loop tests repeatedly")
253 help="loop tests repeatedly")
231 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
254 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
232 help="run each test N times (default=1)", default=1)
255 help="run each test N times (default=1)", default=1)
233 parser.add_option("-n", "--nodiff", action="store_true",
256 parser.add_option("-n", "--nodiff", action="store_true",
234 help="skip showing test changes")
257 help="skip showing test changes")
235 parser.add_option("-p", "--port", type="int",
258 parser.add_option("-p", "--port", type="int",
236 help="port on which servers should listen"
259 help="port on which servers should listen"
237 " (default: $%s or %d)" % defaults['port'])
260 " (default: $%s or %d)" % defaults['port'])
238 parser.add_option("--compiler", type="string",
261 parser.add_option("--compiler", type="string",
239 help="compiler to build with")
262 help="compiler to build with")
240 parser.add_option("--pure", action="store_true",
263 parser.add_option("--pure", action="store_true",
241 help="use pure Python code instead of C extensions")
264 help="use pure Python code instead of C extensions")
242 parser.add_option("-R", "--restart", action="store_true",
265 parser.add_option("-R", "--restart", action="store_true",
243 help="restart at last error")
266 help="restart at last error")
244 parser.add_option("-r", "--retest", action="store_true",
267 parser.add_option("-r", "--retest", action="store_true",
245 help="retest failed tests")
268 help="retest failed tests")
246 parser.add_option("-S", "--noskips", action="store_true",
269 parser.add_option("-S", "--noskips", action="store_true",
247 help="don't report skip tests verbosely")
270 help="don't report skip tests verbosely")
248 parser.add_option("--shell", type="string",
271 parser.add_option("--shell", type="string",
249 help="shell to use (default: $%s or %s)" % defaults['shell'])
272 help="shell to use (default: $%s or %s)" % defaults['shell'])
250 parser.add_option("-t", "--timeout", type="int",
273 parser.add_option("-t", "--timeout", type="int",
251 help="kill errant tests after TIMEOUT seconds"
274 help="kill errant tests after TIMEOUT seconds"
252 " (default: $%s or %d)" % defaults['timeout'])
275 " (default: $%s or %d)" % defaults['timeout'])
253 parser.add_option("--slowtimeout", type="int",
276 parser.add_option("--slowtimeout", type="int",
254 help="kill errant slow tests after SLOWTIMEOUT seconds"
277 help="kill errant slow tests after SLOWTIMEOUT seconds"
255 " (default: $%s or %d)" % defaults['slowtimeout'])
278 " (default: $%s or %d)" % defaults['slowtimeout'])
256 parser.add_option("--time", action="store_true",
279 parser.add_option("--time", action="store_true",
257 help="time how long each test takes")
280 help="time how long each test takes")
258 parser.add_option("--json", action="store_true",
281 parser.add_option("--json", action="store_true",
259 help="store test result data in 'report.json' file")
282 help="store test result data in 'report.json' file")
260 parser.add_option("--tmpdir", type="string",
283 parser.add_option("--tmpdir", type="string",
261 help="run tests in the given temporary directory"
284 help="run tests in the given temporary directory"
262 " (implies --keep-tmpdir)")
285 " (implies --keep-tmpdir)")
263 parser.add_option("-v", "--verbose", action="store_true",
286 parser.add_option("-v", "--verbose", action="store_true",
264 help="output verbose messages")
287 help="output verbose messages")
265 parser.add_option("--xunit", type="string",
288 parser.add_option("--xunit", type="string",
266 help="record xunit results at specified path")
289 help="record xunit results at specified path")
267 parser.add_option("--view", type="string",
290 parser.add_option("--view", type="string",
268 help="external diff viewer")
291 help="external diff viewer")
269 parser.add_option("--with-hg", type="string",
292 parser.add_option("--with-hg", type="string",
270 metavar="HG",
293 metavar="HG",
271 help="test using specified hg script rather than a "
294 help="test using specified hg script rather than a "
272 "temporary installation")
295 "temporary installation")
273 parser.add_option("--chg", action="store_true",
296 parser.add_option("--chg", action="store_true",
274 help="install and use chg wrapper in place of hg")
297 help="install and use chg wrapper in place of hg")
275 parser.add_option("--with-chg", metavar="CHG",
298 parser.add_option("--with-chg", metavar="CHG",
276 help="use specified chg wrapper in place of hg")
299 help="use specified chg wrapper in place of hg")
277 parser.add_option("-3", "--py3k-warnings", action="store_true",
300 parser.add_option("-3", "--py3k-warnings", action="store_true",
278 help="enable Py3k warnings on Python 2.6+")
301 help="enable Py3k warnings on Python 2.6+")
279 # This option should be deleted once test-check-py3-compat.t and other
302 # This option should be deleted once test-check-py3-compat.t and other
280 # Python 3 tests run with Python 3.
303 # Python 3 tests run with Python 3.
281 parser.add_option("--with-python3", metavar="PYTHON3",
304 parser.add_option("--with-python3", metavar="PYTHON3",
282 help="Python 3 interpreter (if running under Python 2)"
305 help="Python 3 interpreter (if running under Python 2)"
283 " (TEMPORARY)")
306 " (TEMPORARY)")
284 parser.add_option('--extra-config-opt', action="append",
307 parser.add_option('--extra-config-opt', action="append",
285 help='set the given config opt in the test hgrc')
308 help='set the given config opt in the test hgrc')
286 parser.add_option('--random', action="store_true",
309 parser.add_option('--random', action="store_true",
287 help='run tests in random order')
310 help='run tests in random order')
288 parser.add_option('--profile-runner', action='store_true',
311 parser.add_option('--profile-runner', action='store_true',
289 help='run statprof on run-tests')
312 help='run statprof on run-tests')
290 parser.add_option('--allow-slow-tests', action='store_true',
313 parser.add_option('--allow-slow-tests', action='store_true',
291 help='allow extremely slow tests')
314 help='allow extremely slow tests')
292 parser.add_option('--showchannels', action='store_true',
315 parser.add_option('--showchannels', action='store_true',
293 help='show scheduling channels')
316 help='show scheduling channels')
294 parser.add_option('--known-good-rev', type="string",
317 parser.add_option('--known-good-rev', type="string",
295 metavar="known_good_rev",
318 metavar="known_good_rev",
296 help=("Automatically bisect any failures using this "
319 help=("Automatically bisect any failures using this "
297 "revision as a known-good revision."))
320 "revision as a known-good revision."))
298
321
299 for option, (envvar, default) in defaults.items():
322 for option, (envvar, default) in defaults.items():
300 defaults[option] = type(default)(os.environ.get(envvar, default))
323 defaults[option] = type(default)(os.environ.get(envvar, default))
301 parser.set_defaults(**defaults)
324 parser.set_defaults(**defaults)
302
325
303 return parser
326 return parser
304
327
305 def parseargs(args, parser):
328 def parseargs(args, parser):
306 """Parse arguments with our OptionParser and validate results."""
329 """Parse arguments with our OptionParser and validate results."""
307 (options, args) = parser.parse_args(args)
330 (options, args) = parser.parse_args(args)
308
331
309 # jython is always pure
332 # jython is always pure
310 if 'java' in sys.platform or '__pypy__' in sys.modules:
333 if 'java' in sys.platform or '__pypy__' in sys.modules:
311 options.pure = True
334 options.pure = True
312
335
313 if options.with_hg:
336 if options.with_hg:
314 options.with_hg = canonpath(_bytespath(options.with_hg))
337 options.with_hg = canonpath(_bytespath(options.with_hg))
315 if not (os.path.isfile(options.with_hg) and
338 if not (os.path.isfile(options.with_hg) and
316 os.access(options.with_hg, os.X_OK)):
339 os.access(options.with_hg, os.X_OK)):
317 parser.error('--with-hg must specify an executable hg script')
340 parser.error('--with-hg must specify an executable hg script')
318 if not os.path.basename(options.with_hg) == b'hg':
341 if not os.path.basename(options.with_hg) == b'hg':
319 sys.stderr.write('warning: --with-hg should specify an hg script\n')
342 sys.stderr.write('warning: --with-hg should specify an hg script\n')
320 if options.local:
343 if options.local:
321 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
344 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
322 reporootdir = os.path.dirname(testdir)
345 reporootdir = os.path.dirname(testdir)
323 pathandattrs = [(b'hg', 'with_hg')]
346 pathandattrs = [(b'hg', 'with_hg')]
324 if options.chg:
347 if options.chg:
325 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
348 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
326 for relpath, attr in pathandattrs:
349 for relpath, attr in pathandattrs:
327 binpath = os.path.join(reporootdir, relpath)
350 binpath = os.path.join(reporootdir, relpath)
328 if os.name != 'nt' and not os.access(binpath, os.X_OK):
351 if os.name != 'nt' and not os.access(binpath, os.X_OK):
329 parser.error('--local specified, but %r not found or '
352 parser.error('--local specified, but %r not found or '
330 'not executable' % binpath)
353 'not executable' % binpath)
331 setattr(options, attr, binpath)
354 setattr(options, attr, binpath)
332
355
333 if (options.chg or options.with_chg) and os.name == 'nt':
356 if (options.chg or options.with_chg) and os.name == 'nt':
334 parser.error('chg does not work on %s' % os.name)
357 parser.error('chg does not work on %s' % os.name)
335 if options.with_chg:
358 if options.with_chg:
336 options.chg = False # no installation to temporary location
359 options.chg = False # no installation to temporary location
337 options.with_chg = canonpath(_bytespath(options.with_chg))
360 options.with_chg = canonpath(_bytespath(options.with_chg))
338 if not (os.path.isfile(options.with_chg) and
361 if not (os.path.isfile(options.with_chg) and
339 os.access(options.with_chg, os.X_OK)):
362 os.access(options.with_chg, os.X_OK)):
340 parser.error('--with-chg must specify a chg executable')
363 parser.error('--with-chg must specify a chg executable')
341 if options.chg and options.with_hg:
364 if options.chg and options.with_hg:
342 # chg shares installation location with hg
365 # chg shares installation location with hg
343 parser.error('--chg does not work when --with-hg is specified '
366 parser.error('--chg does not work when --with-hg is specified '
344 '(use --with-chg instead)')
367 '(use --with-chg instead)')
345
368
346 options.anycoverage = options.cover or options.annotate or options.htmlcov
369 options.anycoverage = options.cover or options.annotate or options.htmlcov
347 if options.anycoverage:
370 if options.anycoverage:
348 try:
371 try:
349 import coverage
372 import coverage
350 covver = version.StrictVersion(coverage.__version__).version
373 covver = version.StrictVersion(coverage.__version__).version
351 if covver < (3, 3):
374 if covver < (3, 3):
352 parser.error('coverage options require coverage 3.3 or later')
375 parser.error('coverage options require coverage 3.3 or later')
353 except ImportError:
376 except ImportError:
354 parser.error('coverage options now require the coverage package')
377 parser.error('coverage options now require the coverage package')
355
378
356 if options.anycoverage and options.local:
379 if options.anycoverage and options.local:
357 # this needs some path mangling somewhere, I guess
380 # this needs some path mangling somewhere, I guess
358 parser.error("sorry, coverage options do not work when --local "
381 parser.error("sorry, coverage options do not work when --local "
359 "is specified")
382 "is specified")
360
383
361 if options.anycoverage and options.with_hg:
384 if options.anycoverage and options.with_hg:
362 parser.error("sorry, coverage options do not work when --with-hg "
385 parser.error("sorry, coverage options do not work when --with-hg "
363 "is specified")
386 "is specified")
364
387
365 global verbose
388 global verbose
366 if options.verbose:
389 if options.verbose:
367 verbose = ''
390 verbose = ''
368
391
369 if options.tmpdir:
392 if options.tmpdir:
370 options.tmpdir = canonpath(options.tmpdir)
393 options.tmpdir = canonpath(options.tmpdir)
371
394
372 if options.jobs < 1:
395 if options.jobs < 1:
373 parser.error('--jobs must be positive')
396 parser.error('--jobs must be positive')
374 if options.interactive and options.debug:
397 if options.interactive and options.debug:
375 parser.error("-i/--interactive and -d/--debug are incompatible")
398 parser.error("-i/--interactive and -d/--debug are incompatible")
376 if options.debug:
399 if options.debug:
377 if options.timeout != defaults['timeout']:
400 if options.timeout != defaults['timeout']:
378 sys.stderr.write(
401 sys.stderr.write(
379 'warning: --timeout option ignored with --debug\n')
402 'warning: --timeout option ignored with --debug\n')
380 if options.slowtimeout != defaults['slowtimeout']:
403 if options.slowtimeout != defaults['slowtimeout']:
381 sys.stderr.write(
404 sys.stderr.write(
382 'warning: --slowtimeout option ignored with --debug\n')
405 'warning: --slowtimeout option ignored with --debug\n')
383 options.timeout = 0
406 options.timeout = 0
384 options.slowtimeout = 0
407 options.slowtimeout = 0
385 if options.py3k_warnings:
408 if options.py3k_warnings:
386 if PYTHON3:
409 if PYTHON3:
387 parser.error(
410 parser.error(
388 '--py3k-warnings can only be used on Python 2.6 and 2.7')
411 '--py3k-warnings can only be used on Python 2.6 and 2.7')
389 if options.with_python3:
412 if options.with_python3:
390 if PYTHON3:
413 if PYTHON3:
391 parser.error('--with-python3 cannot be used when executing with '
414 parser.error('--with-python3 cannot be used when executing with '
392 'Python 3')
415 'Python 3')
393
416
394 options.with_python3 = canonpath(options.with_python3)
417 options.with_python3 = canonpath(options.with_python3)
395 # Verify Python3 executable is acceptable.
418 # Verify Python3 executable is acceptable.
396 proc = subprocess.Popen([options.with_python3, b'--version'],
419 proc = subprocess.Popen([options.with_python3, b'--version'],
397 stdout=subprocess.PIPE,
420 stdout=subprocess.PIPE,
398 stderr=subprocess.STDOUT)
421 stderr=subprocess.STDOUT)
399 out, _err = proc.communicate()
422 out, _err = proc.communicate()
400 ret = proc.wait()
423 ret = proc.wait()
401 if ret != 0:
424 if ret != 0:
402 parser.error('could not determine version of python 3')
425 parser.error('could not determine version of python 3')
403 if not out.startswith('Python '):
426 if not out.startswith('Python '):
404 parser.error('unexpected output from python3 --version: %s' %
427 parser.error('unexpected output from python3 --version: %s' %
405 out)
428 out)
406 vers = version.LooseVersion(out[len('Python '):])
429 vers = version.LooseVersion(out[len('Python '):])
407 if vers < version.LooseVersion('3.5.0'):
430 if vers < version.LooseVersion('3.5.0'):
408 parser.error('--with-python3 version must be 3.5.0 or greater; '
431 parser.error('--with-python3 version must be 3.5.0 or greater; '
409 'got %s' % out)
432 'got %s' % out)
410
433
411 if options.blacklist:
434 if options.blacklist:
412 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
435 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
413 if options.whitelist:
436 if options.whitelist:
414 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
437 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
415 else:
438 else:
416 options.whitelisted = {}
439 options.whitelisted = {}
417
440
418 if options.showchannels:
441 if options.showchannels:
419 options.nodiff = True
442 options.nodiff = True
420
443
421 return (options, args)
444 return (options, args)
422
445
423 def rename(src, dst):
446 def rename(src, dst):
424 """Like os.rename(), trade atomicity and opened files friendliness
447 """Like os.rename(), trade atomicity and opened files friendliness
425 for existing destination support.
448 for existing destination support.
426 """
449 """
427 shutil.copy(src, dst)
450 shutil.copy(src, dst)
428 os.remove(src)
451 os.remove(src)
429
452
430 _unified_diff = difflib.unified_diff
453 _unified_diff = difflib.unified_diff
431 if PYTHON3:
454 if PYTHON3:
432 import functools
455 import functools
433 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
456 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
434
457
435 def getdiff(expected, output, ref, err):
458 def getdiff(expected, output, ref, err):
436 servefail = False
459 servefail = False
437 lines = []
460 lines = []
438 for line in _unified_diff(expected, output, ref, err):
461 for line in _unified_diff(expected, output, ref, err):
439 if line.startswith(b'+++') or line.startswith(b'---'):
462 if line.startswith(b'+++') or line.startswith(b'---'):
440 line = line.replace(b'\\', b'/')
463 line = line.replace(b'\\', b'/')
441 if line.endswith(b' \n'):
464 if line.endswith(b' \n'):
442 line = line[:-2] + b'\n'
465 line = line[:-2] + b'\n'
443 lines.append(line)
466 lines.append(line)
444 if not servefail and line.startswith(
467 if not servefail and line.startswith(
445 b'+ abort: child process failed to start'):
468 b'+ abort: child process failed to start'):
446 servefail = True
469 servefail = True
447
470
448 return servefail, lines
471 return servefail, lines
449
472
450 verbose = False
473 verbose = False
451 def vlog(*msg):
474 def vlog(*msg):
452 """Log only when in verbose mode."""
475 """Log only when in verbose mode."""
453 if verbose is False:
476 if verbose is False:
454 return
477 return
455
478
456 return log(*msg)
479 return log(*msg)
457
480
458 # Bytes that break XML even in a CDATA block: control characters 0-31
481 # Bytes that break XML even in a CDATA block: control characters 0-31
459 # sans \t, \n and \r
482 # sans \t, \n and \r
460 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
483 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
461
484
462 def cdatasafe(data):
485 def cdatasafe(data):
463 """Make a string safe to include in a CDATA block.
486 """Make a string safe to include in a CDATA block.
464
487
465 Certain control characters are illegal in a CDATA block, and
488 Certain control characters are illegal in a CDATA block, and
466 there's no way to include a ]]> in a CDATA either. This function
489 there's no way to include a ]]> in a CDATA either. This function
467 replaces illegal bytes with ? and adds a space between the ]] so
490 replaces illegal bytes with ? and adds a space between the ]] so
468 that it won't break the CDATA block.
491 that it won't break the CDATA block.
469 """
492 """
470 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
493 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
471
494
472 def log(*msg):
495 def log(*msg):
473 """Log something to stdout.
496 """Log something to stdout.
474
497
475 Arguments are strings to print.
498 Arguments are strings to print.
476 """
499 """
477 with iolock:
500 with iolock:
478 if verbose:
501 if verbose:
479 print(verbose, end=' ')
502 print(verbose, end=' ')
480 for m in msg:
503 for m in msg:
481 print(m, end=' ')
504 print(m, end=' ')
482 print()
505 print()
483 sys.stdout.flush()
506 sys.stdout.flush()
484
507
485 def terminate(proc):
508 def terminate(proc):
486 """Terminate subprocess (with fallback for Python versions < 2.6)"""
509 """Terminate subprocess (with fallback for Python versions < 2.6)"""
487 vlog('# Terminating process %d' % proc.pid)
510 vlog('# Terminating process %d' % proc.pid)
488 try:
511 try:
489 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
512 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
490 except OSError:
513 except OSError:
491 pass
514 pass
492
515
493 def killdaemons(pidfile):
516 def killdaemons(pidfile):
494 import killdaemons as killmod
517 import killdaemons as killmod
495 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
518 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
496 logfn=vlog)
519 logfn=vlog)
497
520
498 class Test(unittest.TestCase):
521 class Test(unittest.TestCase):
499 """Encapsulates a single, runnable test.
522 """Encapsulates a single, runnable test.
500
523
501 While this class conforms to the unittest.TestCase API, it differs in that
524 While this class conforms to the unittest.TestCase API, it differs in that
502 instances need to be instantiated manually. (Typically, unittest.TestCase
525 instances need to be instantiated manually. (Typically, unittest.TestCase
503 classes are instantiated automatically by scanning modules.)
526 classes are instantiated automatically by scanning modules.)
504 """
527 """
505
528
506 # Status code reserved for skipped tests (used by hghave).
529 # Status code reserved for skipped tests (used by hghave).
507 SKIPPED_STATUS = 80
530 SKIPPED_STATUS = 80
508
531
509 def __init__(self, path, tmpdir, keeptmpdir=False,
532 def __init__(self, path, tmpdir, keeptmpdir=False,
510 debug=False,
533 debug=False,
511 timeout=defaults['timeout'],
534 timeout=defaults['timeout'],
512 startport=defaults['port'], extraconfigopts=None,
535 startport=defaults['port'], extraconfigopts=None,
513 py3kwarnings=False, shell=None, hgcommand=None,
536 py3kwarnings=False, shell=None, hgcommand=None,
514 slowtimeout=defaults['slowtimeout'], usechg=False):
537 slowtimeout=defaults['slowtimeout'], usechg=False):
515 """Create a test from parameters.
538 """Create a test from parameters.
516
539
517 path is the full path to the file defining the test.
540 path is the full path to the file defining the test.
518
541
519 tmpdir is the main temporary directory to use for this test.
542 tmpdir is the main temporary directory to use for this test.
520
543
521 keeptmpdir determines whether to keep the test's temporary directory
544 keeptmpdir determines whether to keep the test's temporary directory
522 after execution. It defaults to removal (False).
545 after execution. It defaults to removal (False).
523
546
524 debug mode will make the test execute verbosely, with unfiltered
547 debug mode will make the test execute verbosely, with unfiltered
525 output.
548 output.
526
549
527 timeout controls the maximum run time of the test. It is ignored when
550 timeout controls the maximum run time of the test. It is ignored when
528 debug is True. See slowtimeout for tests with #require slow.
551 debug is True. See slowtimeout for tests with #require slow.
529
552
530 slowtimeout overrides timeout if the test has #require slow.
553 slowtimeout overrides timeout if the test has #require slow.
531
554
532 startport controls the starting port number to use for this test. Each
555 startport controls the starting port number to use for this test. Each
533 test will reserve 3 port numbers for execution. It is the caller's
556 test will reserve 3 port numbers for execution. It is the caller's
534 responsibility to allocate a non-overlapping port range to Test
557 responsibility to allocate a non-overlapping port range to Test
535 instances.
558 instances.
536
559
537 extraconfigopts is an iterable of extra hgrc config options. Values
560 extraconfigopts is an iterable of extra hgrc config options. Values
538 must have the form "key=value" (something understood by hgrc). Values
561 must have the form "key=value" (something understood by hgrc). Values
539 of the form "foo.key=value" will result in "[foo] key=value".
562 of the form "foo.key=value" will result in "[foo] key=value".
540
563
541 py3kwarnings enables Py3k warnings.
564 py3kwarnings enables Py3k warnings.
542
565
543 shell is the shell to execute tests in.
566 shell is the shell to execute tests in.
544 """
567 """
545 self.path = path
568 self.path = path
546 self.bname = os.path.basename(path)
569 self.bname = os.path.basename(path)
547 self.name = _strpath(self.bname)
570 self.name = _strpath(self.bname)
548 self._testdir = os.path.dirname(path)
571 self._testdir = os.path.dirname(path)
549 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
572 self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
550
573
551 self._threadtmp = tmpdir
574 self._threadtmp = tmpdir
552 self._keeptmpdir = keeptmpdir
575 self._keeptmpdir = keeptmpdir
553 self._debug = debug
576 self._debug = debug
554 self._timeout = timeout
577 self._timeout = timeout
555 self._slowtimeout = slowtimeout
578 self._slowtimeout = slowtimeout
556 self._startport = startport
579 self._startport = startport
557 self._extraconfigopts = extraconfigopts or []
580 self._extraconfigopts = extraconfigopts or []
558 self._py3kwarnings = py3kwarnings
581 self._py3kwarnings = py3kwarnings
559 self._shell = _bytespath(shell)
582 self._shell = _bytespath(shell)
560 self._hgcommand = hgcommand or b'hg'
583 self._hgcommand = hgcommand or b'hg'
561 self._usechg = usechg
584 self._usechg = usechg
562
585
563 self._aborted = False
586 self._aborted = False
564 self._daemonpids = []
587 self._daemonpids = []
565 self._finished = None
588 self._finished = None
566 self._ret = None
589 self._ret = None
567 self._out = None
590 self._out = None
568 self._skipped = None
591 self._skipped = None
569 self._testtmp = None
592 self._testtmp = None
570 self._chgsockdir = None
593 self._chgsockdir = None
571
594
572 # If we're not in --debug mode and reference output file exists,
595 # If we're not in --debug mode and reference output file exists,
573 # check test output against it.
596 # check test output against it.
574 if debug:
597 if debug:
575 self._refout = None # to match "out is None"
598 self._refout = None # to match "out is None"
576 elif os.path.exists(self.refpath):
599 elif os.path.exists(self.refpath):
577 f = open(self.refpath, 'rb')
600 f = open(self.refpath, 'rb')
578 self._refout = f.read().splitlines(True)
601 self._refout = f.read().splitlines(True)
579 f.close()
602 f.close()
580 else:
603 else:
581 self._refout = []
604 self._refout = []
582
605
583 # needed to get base class __repr__ running
606 # needed to get base class __repr__ running
584 @property
607 @property
585 def _testMethodName(self):
608 def _testMethodName(self):
586 return self.name
609 return self.name
587
610
588 def __str__(self):
611 def __str__(self):
589 return self.name
612 return self.name
590
613
591 def shortDescription(self):
614 def shortDescription(self):
592 return self.name
615 return self.name
593
616
594 def setUp(self):
617 def setUp(self):
595 """Tasks to perform before run()."""
618 """Tasks to perform before run()."""
596 self._finished = False
619 self._finished = False
597 self._ret = None
620 self._ret = None
598 self._out = None
621 self._out = None
599 self._skipped = None
622 self._skipped = None
600
623
601 try:
624 try:
602 os.mkdir(self._threadtmp)
625 os.mkdir(self._threadtmp)
603 except OSError as e:
626 except OSError as e:
604 if e.errno != errno.EEXIST:
627 if e.errno != errno.EEXIST:
605 raise
628 raise
606
629
607 name = os.path.basename(self.path)
630 name = os.path.basename(self.path)
608 self._testtmp = os.path.join(self._threadtmp, name)
631 self._testtmp = os.path.join(self._threadtmp, name)
609 os.mkdir(self._testtmp)
632 os.mkdir(self._testtmp)
610
633
611 # Remove any previous output files.
634 # Remove any previous output files.
612 if os.path.exists(self.errpath):
635 if os.path.exists(self.errpath):
613 try:
636 try:
614 os.remove(self.errpath)
637 os.remove(self.errpath)
615 except OSError as e:
638 except OSError as e:
616 # We might have raced another test to clean up a .err
639 # We might have raced another test to clean up a .err
617 # file, so ignore ENOENT when removing a previous .err
640 # file, so ignore ENOENT when removing a previous .err
618 # file.
641 # file.
619 if e.errno != errno.ENOENT:
642 if e.errno != errno.ENOENT:
620 raise
643 raise
621
644
622 if self._usechg:
645 if self._usechg:
623 self._chgsockdir = os.path.join(self._threadtmp,
646 self._chgsockdir = os.path.join(self._threadtmp,
624 b'%s.chgsock' % name)
647 b'%s.chgsock' % name)
625 os.mkdir(self._chgsockdir)
648 os.mkdir(self._chgsockdir)
626
649
627 def run(self, result):
650 def run(self, result):
628 """Run this test and report results against a TestResult instance."""
651 """Run this test and report results against a TestResult instance."""
629 # This function is extremely similar to unittest.TestCase.run(). Once
652 # This function is extremely similar to unittest.TestCase.run(). Once
630 # we require Python 2.7 (or at least its version of unittest), this
653 # we require Python 2.7 (or at least its version of unittest), this
631 # function can largely go away.
654 # function can largely go away.
632 self._result = result
655 self._result = result
633 result.startTest(self)
656 result.startTest(self)
634 try:
657 try:
635 try:
658 try:
636 self.setUp()
659 self.setUp()
637 except (KeyboardInterrupt, SystemExit):
660 except (KeyboardInterrupt, SystemExit):
638 self._aborted = True
661 self._aborted = True
639 raise
662 raise
640 except Exception:
663 except Exception:
641 result.addError(self, sys.exc_info())
664 result.addError(self, sys.exc_info())
642 return
665 return
643
666
644 success = False
667 success = False
645 try:
668 try:
646 self.runTest()
669 self.runTest()
647 except KeyboardInterrupt:
670 except KeyboardInterrupt:
648 self._aborted = True
671 self._aborted = True
649 raise
672 raise
650 except SkipTest as e:
673 except SkipTest as e:
651 result.addSkip(self, str(e))
674 result.addSkip(self, str(e))
652 # The base class will have already counted this as a
675 # The base class will have already counted this as a
653 # test we "ran", but we want to exclude skipped tests
676 # test we "ran", but we want to exclude skipped tests
654 # from those we count towards those run.
677 # from those we count towards those run.
655 result.testsRun -= 1
678 result.testsRun -= 1
656 except IgnoreTest as e:
679 except IgnoreTest as e:
657 result.addIgnore(self, str(e))
680 result.addIgnore(self, str(e))
658 # As with skips, ignores also should be excluded from
681 # As with skips, ignores also should be excluded from
659 # the number of tests executed.
682 # the number of tests executed.
660 result.testsRun -= 1
683 result.testsRun -= 1
661 except WarnTest as e:
684 except WarnTest as e:
662 result.addWarn(self, str(e))
685 result.addWarn(self, str(e))
663 except ReportedTest as e:
686 except ReportedTest as e:
664 pass
687 pass
665 except self.failureException as e:
688 except self.failureException as e:
666 # This differs from unittest in that we don't capture
689 # This differs from unittest in that we don't capture
667 # the stack trace. This is for historical reasons and
690 # the stack trace. This is for historical reasons and
668 # this decision could be revisited in the future,
691 # this decision could be revisited in the future,
669 # especially for PythonTest instances.
692 # especially for PythonTest instances.
670 if result.addFailure(self, str(e)):
693 if result.addFailure(self, str(e)):
671 success = True
694 success = True
672 except Exception:
695 except Exception:
673 result.addError(self, sys.exc_info())
696 result.addError(self, sys.exc_info())
674 else:
697 else:
675 success = True
698 success = True
676
699
677 try:
700 try:
678 self.tearDown()
701 self.tearDown()
679 except (KeyboardInterrupt, SystemExit):
702 except (KeyboardInterrupt, SystemExit):
680 self._aborted = True
703 self._aborted = True
681 raise
704 raise
682 except Exception:
705 except Exception:
683 result.addError(self, sys.exc_info())
706 result.addError(self, sys.exc_info())
684 success = False
707 success = False
685
708
686 if success:
709 if success:
687 result.addSuccess(self)
710 result.addSuccess(self)
688 finally:
711 finally:
689 result.stopTest(self, interrupted=self._aborted)
712 result.stopTest(self, interrupted=self._aborted)
690
713
691 def runTest(self):
714 def runTest(self):
692 """Run this test instance.
715 """Run this test instance.
693
716
694 This will return a tuple describing the result of the test.
717 This will return a tuple describing the result of the test.
695 """
718 """
696 env = self._getenv()
719 env = self._getenv()
697 self._daemonpids.append(env['DAEMON_PIDS'])
720 self._daemonpids.append(env['DAEMON_PIDS'])
698 self._createhgrc(env['HGRCPATH'])
721 self._createhgrc(env['HGRCPATH'])
699
722
700 vlog('# Test', self.name)
723 vlog('# Test', self.name)
701
724
702 ret, out = self._run(env)
725 ret, out = self._run(env)
703 self._finished = True
726 self._finished = True
704 self._ret = ret
727 self._ret = ret
705 self._out = out
728 self._out = out
706
729
707 def describe(ret):
730 def describe(ret):
708 if ret < 0:
731 if ret < 0:
709 return 'killed by signal: %d' % -ret
732 return 'killed by signal: %d' % -ret
710 return 'returned error code %d' % ret
733 return 'returned error code %d' % ret
711
734
712 self._skipped = False
735 self._skipped = False
713
736
714 if ret == self.SKIPPED_STATUS:
737 if ret == self.SKIPPED_STATUS:
715 if out is None: # Debug mode, nothing to parse.
738 if out is None: # Debug mode, nothing to parse.
716 missing = ['unknown']
739 missing = ['unknown']
717 failed = None
740 failed = None
718 else:
741 else:
719 missing, failed = TTest.parsehghaveoutput(out)
742 missing, failed = TTest.parsehghaveoutput(out)
720
743
721 if not missing:
744 if not missing:
722 missing = ['skipped']
745 missing = ['skipped']
723
746
724 if failed:
747 if failed:
725 self.fail('hg have failed checking for %s' % failed[-1])
748 self.fail('hg have failed checking for %s' % failed[-1])
726 else:
749 else:
727 self._skipped = True
750 self._skipped = True
728 raise SkipTest(missing[-1])
751 raise SkipTest(missing[-1])
729 elif ret == 'timeout':
752 elif ret == 'timeout':
730 self.fail('timed out')
753 self.fail('timed out')
731 elif ret is False:
754 elif ret is False:
732 raise WarnTest('no result code from test')
755 raise WarnTest('no result code from test')
733 elif out != self._refout:
756 elif out != self._refout:
734 # Diff generation may rely on written .err file.
757 # Diff generation may rely on written .err file.
735 if (ret != 0 or out != self._refout) and not self._skipped \
758 if (ret != 0 or out != self._refout) and not self._skipped \
736 and not self._debug:
759 and not self._debug:
737 f = open(self.errpath, 'wb')
760 f = open(self.errpath, 'wb')
738 for line in out:
761 for line in out:
739 f.write(line)
762 f.write(line)
740 f.close()
763 f.close()
741
764
742 # The result object handles diff calculation for us.
765 # The result object handles diff calculation for us.
743 if self._result.addOutputMismatch(self, ret, out, self._refout):
766 if self._result.addOutputMismatch(self, ret, out, self._refout):
744 # change was accepted, skip failing
767 # change was accepted, skip failing
745 return
768 return
746
769
747 if ret:
770 if ret:
748 msg = 'output changed and ' + describe(ret)
771 msg = 'output changed and ' + describe(ret)
749 else:
772 else:
750 msg = 'output changed'
773 msg = 'output changed'
751
774
752 self.fail(msg)
775 self.fail(msg)
753 elif ret:
776 elif ret:
754 self.fail(describe(ret))
777 self.fail(describe(ret))
755
778
756 def tearDown(self):
779 def tearDown(self):
757 """Tasks to perform after run()."""
780 """Tasks to perform after run()."""
758 for entry in self._daemonpids:
781 for entry in self._daemonpids:
759 killdaemons(entry)
782 killdaemons(entry)
760 self._daemonpids = []
783 self._daemonpids = []
761
784
762 if self._keeptmpdir:
785 if self._keeptmpdir:
763 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
786 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
764 (self._testtmp.decode('utf-8'),
787 (self._testtmp.decode('utf-8'),
765 self._threadtmp.decode('utf-8')))
788 self._threadtmp.decode('utf-8')))
766 else:
789 else:
767 shutil.rmtree(self._testtmp, True)
790 shutil.rmtree(self._testtmp, True)
768 shutil.rmtree(self._threadtmp, True)
791 shutil.rmtree(self._threadtmp, True)
769
792
770 if self._usechg:
793 if self._usechg:
771 # chgservers will stop automatically after they find the socket
794 # chgservers will stop automatically after they find the socket
772 # files are deleted
795 # files are deleted
773 shutil.rmtree(self._chgsockdir, True)
796 shutil.rmtree(self._chgsockdir, True)
774
797
775 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
798 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
776 and not self._debug and self._out:
799 and not self._debug and self._out:
777 f = open(self.errpath, 'wb')
800 f = open(self.errpath, 'wb')
778 for line in self._out:
801 for line in self._out:
779 f.write(line)
802 f.write(line)
780 f.close()
803 f.close()
781
804
782 vlog("# Ret was:", self._ret, '(%s)' % self.name)
805 vlog("# Ret was:", self._ret, '(%s)' % self.name)
783
806
784 def _run(self, env):
807 def _run(self, env):
785 # This should be implemented in child classes to run tests.
808 # This should be implemented in child classes to run tests.
786 raise SkipTest('unknown test type')
809 raise SkipTest('unknown test type')
787
810
788 def abort(self):
811 def abort(self):
789 """Terminate execution of this test."""
812 """Terminate execution of this test."""
790 self._aborted = True
813 self._aborted = True
791
814
792 def _portmap(self, i):
815 def _portmap(self, i):
793 offset = b'' if i == 0 else b'%d' % i
816 offset = b'' if i == 0 else b'%d' % i
794 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
817 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
795
818
796 def _getreplacements(self):
819 def _getreplacements(self):
797 """Obtain a mapping of text replacements to apply to test output.
820 """Obtain a mapping of text replacements to apply to test output.
798
821
799 Test output needs to be normalized so it can be compared to expected
822 Test output needs to be normalized so it can be compared to expected
800 output. This function defines how some of that normalization will
823 output. This function defines how some of that normalization will
801 occur.
824 occur.
802 """
825 """
803 r = [
826 r = [
804 # This list should be parallel to defineport in _getenv
827 # This list should be parallel to defineport in _getenv
805 self._portmap(0),
828 self._portmap(0),
806 self._portmap(1),
829 self._portmap(1),
807 self._portmap(2),
830 self._portmap(2),
808 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
831 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
809 br'\1 (glob)'),
832 br'\1 (glob)'),
810 ]
833 ]
811 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
834 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
812
835
813 return r
836 return r
814
837
815 def _escapepath(self, p):
838 def _escapepath(self, p):
816 if os.name == 'nt':
839 if os.name == 'nt':
817 return (
840 return (
818 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
841 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
819 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
842 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
820 for c in p))
843 for c in p))
821 )
844 )
822 else:
845 else:
823 return re.escape(p)
846 return re.escape(p)
824
847
825 def _getenv(self):
848 def _getenv(self):
826 """Obtain environment variables to use during test execution."""
849 """Obtain environment variables to use during test execution."""
827 def defineport(i):
850 def defineport(i):
828 offset = '' if i == 0 else '%s' % i
851 offset = '' if i == 0 else '%s' % i
829 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
852 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
830 env = os.environ.copy()
853 env = os.environ.copy()
831 if sysconfig is not None:
854 if sysconfig is not None:
832 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
855 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
833 env['TESTTMP'] = self._testtmp
856 env['TESTTMP'] = self._testtmp
834 env['HOME'] = self._testtmp
857 env['HOME'] = self._testtmp
835 # This number should match portneeded in _getport
858 # This number should match portneeded in _getport
836 for port in xrange(3):
859 for port in xrange(3):
837 # This list should be parallel to _portmap in _getreplacements
860 # This list should be parallel to _portmap in _getreplacements
838 defineport(port)
861 defineport(port)
839 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
862 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
840 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
863 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
841 env["HGEDITOR"] = ('"' + sys.executable + '"'
864 env["HGEDITOR"] = ('"' + sys.executable + '"'
842 + ' -c "import sys; sys.exit(0)"')
865 + ' -c "import sys; sys.exit(0)"')
843 env["HGMERGE"] = "internal:merge"
866 env["HGMERGE"] = "internal:merge"
844 env["HGUSER"] = "test"
867 env["HGUSER"] = "test"
845 env["HGENCODING"] = "ascii"
868 env["HGENCODING"] = "ascii"
846 env["HGENCODINGMODE"] = "strict"
869 env["HGENCODINGMODE"] = "strict"
847
870
848 # Reset some environment variables to well-known values so that
871 # Reset some environment variables to well-known values so that
849 # the tests produce repeatable output.
872 # the tests produce repeatable output.
850 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
873 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
851 env['TZ'] = 'GMT'
874 env['TZ'] = 'GMT'
852 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
875 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
853 env['COLUMNS'] = '80'
876 env['COLUMNS'] = '80'
854 env['TERM'] = 'xterm'
877 env['TERM'] = 'xterm'
855
878
856 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
879 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
857 'NO_PROXY CHGDEBUG').split():
880 'NO_PROXY CHGDEBUG').split():
858 if k in env:
881 if k in env:
859 del env[k]
882 del env[k]
860
883
861 # unset env related to hooks
884 # unset env related to hooks
862 for k in env.keys():
885 for k in env.keys():
863 if k.startswith('HG_'):
886 if k.startswith('HG_'):
864 del env[k]
887 del env[k]
865
888
866 if self._usechg:
889 if self._usechg:
867 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
890 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
868
891
869 return env
892 return env
870
893
871 def _createhgrc(self, path):
894 def _createhgrc(self, path):
872 """Create an hgrc file for this test."""
895 """Create an hgrc file for this test."""
873 hgrc = open(path, 'wb')
896 hgrc = open(path, 'wb')
874 hgrc.write(b'[ui]\n')
897 hgrc.write(b'[ui]\n')
875 hgrc.write(b'slash = True\n')
898 hgrc.write(b'slash = True\n')
876 hgrc.write(b'interactive = False\n')
899 hgrc.write(b'interactive = False\n')
877 hgrc.write(b'mergemarkers = detailed\n')
900 hgrc.write(b'mergemarkers = detailed\n')
878 hgrc.write(b'promptecho = True\n')
901 hgrc.write(b'promptecho = True\n')
879 hgrc.write(b'[defaults]\n')
902 hgrc.write(b'[defaults]\n')
880 hgrc.write(b'backout = -d "0 0"\n')
903 hgrc.write(b'backout = -d "0 0"\n')
881 hgrc.write(b'commit = -d "0 0"\n')
904 hgrc.write(b'commit = -d "0 0"\n')
882 hgrc.write(b'shelve = --date "0 0"\n')
905 hgrc.write(b'shelve = --date "0 0"\n')
883 hgrc.write(b'tag = -d "0 0"\n')
906 hgrc.write(b'tag = -d "0 0"\n')
884 hgrc.write(b'[devel]\n')
907 hgrc.write(b'[devel]\n')
885 hgrc.write(b'all-warnings = true\n')
908 hgrc.write(b'all-warnings = true\n')
886 hgrc.write(b'[largefiles]\n')
909 hgrc.write(b'[largefiles]\n')
887 hgrc.write(b'usercache = %s\n' %
910 hgrc.write(b'usercache = %s\n' %
888 (os.path.join(self._testtmp, b'.cache/largefiles')))
911 (os.path.join(self._testtmp, b'.cache/largefiles')))
889
912
890 for opt in self._extraconfigopts:
913 for opt in self._extraconfigopts:
891 section, key = opt.split('.', 1)
914 section, key = opt.split('.', 1)
892 assert '=' in key, ('extra config opt %s must '
915 assert '=' in key, ('extra config opt %s must '
893 'have an = for assignment' % opt)
916 'have an = for assignment' % opt)
894 hgrc.write(b'[%s]\n%s\n' % (section, key))
917 hgrc.write(b'[%s]\n%s\n' % (section, key))
895 hgrc.close()
918 hgrc.close()
896
919
897 def fail(self, msg):
920 def fail(self, msg):
898 # unittest differentiates between errored and failed.
921 # unittest differentiates between errored and failed.
899 # Failed is denoted by AssertionError (by default at least).
922 # Failed is denoted by AssertionError (by default at least).
900 raise AssertionError(msg)
923 raise AssertionError(msg)
901
924
902 def _runcommand(self, cmd, env, normalizenewlines=False):
925 def _runcommand(self, cmd, env, normalizenewlines=False):
903 """Run command in a sub-process, capturing the output (stdout and
926 """Run command in a sub-process, capturing the output (stdout and
904 stderr).
927 stderr).
905
928
906 Return a tuple (exitcode, output). output is None in debug mode.
929 Return a tuple (exitcode, output). output is None in debug mode.
907 """
930 """
908 if self._debug:
931 if self._debug:
909 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
932 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
910 env=env)
933 env=env)
911 ret = proc.wait()
934 ret = proc.wait()
912 return (ret, None)
935 return (ret, None)
913
936
914 proc = Popen4(cmd, self._testtmp, self._timeout, env)
937 proc = Popen4(cmd, self._testtmp, self._timeout, env)
915 def cleanup():
938 def cleanup():
916 terminate(proc)
939 terminate(proc)
917 ret = proc.wait()
940 ret = proc.wait()
918 if ret == 0:
941 if ret == 0:
919 ret = signal.SIGTERM << 8
942 ret = signal.SIGTERM << 8
920 killdaemons(env['DAEMON_PIDS'])
943 killdaemons(env['DAEMON_PIDS'])
921 return ret
944 return ret
922
945
923 output = ''
946 output = ''
924 proc.tochild.close()
947 proc.tochild.close()
925
948
926 try:
949 try:
927 output = proc.fromchild.read()
950 output = proc.fromchild.read()
928 except KeyboardInterrupt:
951 except KeyboardInterrupt:
929 vlog('# Handling keyboard interrupt')
952 vlog('# Handling keyboard interrupt')
930 cleanup()
953 cleanup()
931 raise
954 raise
932
955
933 ret = proc.wait()
956 ret = proc.wait()
934 if wifexited(ret):
957 if wifexited(ret):
935 ret = os.WEXITSTATUS(ret)
958 ret = os.WEXITSTATUS(ret)
936
959
937 if proc.timeout:
960 if proc.timeout:
938 ret = 'timeout'
961 ret = 'timeout'
939
962
940 if ret:
963 if ret:
941 killdaemons(env['DAEMON_PIDS'])
964 killdaemons(env['DAEMON_PIDS'])
942
965
943 for s, r in self._getreplacements():
966 for s, r in self._getreplacements():
944 output = re.sub(s, r, output)
967 output = re.sub(s, r, output)
945
968
946 if normalizenewlines:
969 if normalizenewlines:
947 output = output.replace('\r\n', '\n')
970 output = output.replace('\r\n', '\n')
948
971
949 return ret, output.splitlines(True)
972 return ret, output.splitlines(True)
950
973
951 class PythonTest(Test):
974 class PythonTest(Test):
952 """A Python-based test."""
975 """A Python-based test."""
953
976
954 @property
977 @property
955 def refpath(self):
978 def refpath(self):
956 return os.path.join(self._testdir, b'%s.out' % self.bname)
979 return os.path.join(self._testdir, b'%s.out' % self.bname)
957
980
958 def _run(self, env):
981 def _run(self, env):
959 py3kswitch = self._py3kwarnings and b' -3' or b''
982 py3kswitch = self._py3kwarnings and b' -3' or b''
960 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
983 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
961 vlog("# Running", cmd)
984 vlog("# Running", cmd)
962 normalizenewlines = os.name == 'nt'
985 normalizenewlines = os.name == 'nt'
963 result = self._runcommand(cmd, env,
986 result = self._runcommand(cmd, env,
964 normalizenewlines=normalizenewlines)
987 normalizenewlines=normalizenewlines)
965 if self._aborted:
988 if self._aborted:
966 raise KeyboardInterrupt()
989 raise KeyboardInterrupt()
967
990
968 return result
991 return result
969
992
970 # Some glob patterns apply only in some circumstances, so the script
993 # Some glob patterns apply only in some circumstances, so the script
971 # might want to remove (glob) annotations that otherwise should be
994 # might want to remove (glob) annotations that otherwise should be
972 # retained.
995 # retained.
973 checkcodeglobpats = [
996 checkcodeglobpats = [
974 # On Windows it looks like \ doesn't require a (glob), but we know
997 # On Windows it looks like \ doesn't require a (glob), but we know
975 # better.
998 # better.
976 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
999 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
977 re.compile(br'^moving \S+/.*[^)]$'),
1000 re.compile(br'^moving \S+/.*[^)]$'),
978 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1001 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
979 # Not all platforms have 127.0.0.1 as loopback (though most do),
1002 # Not all platforms have 127.0.0.1 as loopback (though most do),
980 # so we always glob that too.
1003 # so we always glob that too.
981 re.compile(br'.*127.0.0.1.*$'),
1004 re.compile(br'.*127.0.0.1.*$'),
982 ]
1005 ]
983
1006
984 bchr = chr
1007 bchr = chr
985 if PYTHON3:
1008 if PYTHON3:
986 bchr = lambda x: bytes([x])
1009 bchr = lambda x: bytes([x])
987
1010
988 class TTest(Test):
1011 class TTest(Test):
989 """A "t test" is a test backed by a .t file."""
1012 """A "t test" is a test backed by a .t file."""
990
1013
991 SKIPPED_PREFIX = b'skipped: '
1014 SKIPPED_PREFIX = b'skipped: '
992 FAILED_PREFIX = b'hghave check failed: '
1015 FAILED_PREFIX = b'hghave check failed: '
993 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1016 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
994
1017
995 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1018 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
996 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1019 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
997 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1020 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
998
1021
999 @property
1022 @property
1000 def refpath(self):
1023 def refpath(self):
1001 return os.path.join(self._testdir, self.bname)
1024 return os.path.join(self._testdir, self.bname)
1002
1025
1003 def _run(self, env):
1026 def _run(self, env):
1004 f = open(self.path, 'rb')
1027 f = open(self.path, 'rb')
1005 lines = f.readlines()
1028 lines = f.readlines()
1006 f.close()
1029 f.close()
1007
1030
1008 salt, script, after, expected = self._parsetest(lines)
1031 salt, script, after, expected = self._parsetest(lines)
1009
1032
1010 # Write out the generated script.
1033 # Write out the generated script.
1011 fname = b'%s.sh' % self._testtmp
1034 fname = b'%s.sh' % self._testtmp
1012 f = open(fname, 'wb')
1035 f = open(fname, 'wb')
1013 for l in script:
1036 for l in script:
1014 f.write(l)
1037 f.write(l)
1015 f.close()
1038 f.close()
1016
1039
1017 cmd = b'%s "%s"' % (self._shell, fname)
1040 cmd = b'%s "%s"' % (self._shell, fname)
1018 vlog("# Running", cmd)
1041 vlog("# Running", cmd)
1019
1042
1020 exitcode, output = self._runcommand(cmd, env)
1043 exitcode, output = self._runcommand(cmd, env)
1021
1044
1022 if self._aborted:
1045 if self._aborted:
1023 raise KeyboardInterrupt()
1046 raise KeyboardInterrupt()
1024
1047
1025 # Do not merge output if skipped. Return hghave message instead.
1048 # Do not merge output if skipped. Return hghave message instead.
1026 # Similarly, with --debug, output is None.
1049 # Similarly, with --debug, output is None.
1027 if exitcode == self.SKIPPED_STATUS or output is None:
1050 if exitcode == self.SKIPPED_STATUS or output is None:
1028 return exitcode, output
1051 return exitcode, output
1029
1052
1030 return self._processoutput(exitcode, output, salt, after, expected)
1053 return self._processoutput(exitcode, output, salt, after, expected)
1031
1054
1032 def _hghave(self, reqs):
1055 def _hghave(self, reqs):
1033 # TODO do something smarter when all other uses of hghave are gone.
1056 # TODO do something smarter when all other uses of hghave are gone.
1034 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1057 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1035 tdir = runtestdir.replace(b'\\', b'/')
1058 tdir = runtestdir.replace(b'\\', b'/')
1036 proc = Popen4(b'%s -c "%s/hghave %s"' %
1059 proc = Popen4(b'%s -c "%s/hghave %s"' %
1037 (self._shell, tdir, b' '.join(reqs)),
1060 (self._shell, tdir, b' '.join(reqs)),
1038 self._testtmp, 0, self._getenv())
1061 self._testtmp, 0, self._getenv())
1039 stdout, stderr = proc.communicate()
1062 stdout, stderr = proc.communicate()
1040 ret = proc.wait()
1063 ret = proc.wait()
1041 if wifexited(ret):
1064 if wifexited(ret):
1042 ret = os.WEXITSTATUS(ret)
1065 ret = os.WEXITSTATUS(ret)
1043 if ret == 2:
1066 if ret == 2:
1044 print(stdout.decode('utf-8'))
1067 print(stdout.decode('utf-8'))
1045 sys.exit(1)
1068 sys.exit(1)
1046
1069
1047 if ret != 0:
1070 if ret != 0:
1048 return False, stdout
1071 return False, stdout
1049
1072
1050 if 'slow' in reqs:
1073 if 'slow' in reqs:
1051 self._timeout = self._slowtimeout
1074 self._timeout = self._slowtimeout
1052 return True, None
1075 return True, None
1053
1076
1054 def _parsetest(self, lines):
1077 def _parsetest(self, lines):
1055 # We generate a shell script which outputs unique markers to line
1078 # We generate a shell script which outputs unique markers to line
1056 # up script results with our source. These markers include input
1079 # up script results with our source. These markers include input
1057 # line number and the last return code.
1080 # line number and the last return code.
1058 salt = b"SALT%d" % time.time()
1081 salt = b"SALT%d" % time.time()
1059 def addsalt(line, inpython):
1082 def addsalt(line, inpython):
1060 if inpython:
1083 if inpython:
1061 script.append(b'%s %d 0\n' % (salt, line))
1084 script.append(b'%s %d 0\n' % (salt, line))
1062 else:
1085 else:
1063 script.append(b'echo %s %d $?\n' % (salt, line))
1086 script.append(b'echo %s %d $?\n' % (salt, line))
1064
1087
1065 script = []
1088 script = []
1066
1089
1067 # After we run the shell script, we re-unify the script output
1090 # After we run the shell script, we re-unify the script output
1068 # with non-active parts of the source, with synchronization by our
1091 # with non-active parts of the source, with synchronization by our
1069 # SALT line number markers. The after table contains the non-active
1092 # SALT line number markers. The after table contains the non-active
1070 # components, ordered by line number.
1093 # components, ordered by line number.
1071 after = {}
1094 after = {}
1072
1095
1073 # Expected shell script output.
1096 # Expected shell script output.
1074 expected = {}
1097 expected = {}
1075
1098
1076 pos = prepos = -1
1099 pos = prepos = -1
1077
1100
1078 # True or False when in a true or false conditional section
1101 # True or False when in a true or false conditional section
1079 skipping = None
1102 skipping = None
1080
1103
1081 # We keep track of whether or not we're in a Python block so we
1104 # We keep track of whether or not we're in a Python block so we
1082 # can generate the surrounding doctest magic.
1105 # can generate the surrounding doctest magic.
1083 inpython = False
1106 inpython = False
1084
1107
1085 if self._debug:
1108 if self._debug:
1086 script.append(b'set -x\n')
1109 script.append(b'set -x\n')
1087 if self._hgcommand != b'hg':
1110 if self._hgcommand != b'hg':
1088 script.append(b'alias hg="%s"\n' % self._hgcommand)
1111 script.append(b'alias hg="%s"\n' % self._hgcommand)
1089 if os.getenv('MSYSTEM'):
1112 if os.getenv('MSYSTEM'):
1090 script.append(b'alias pwd="pwd -W"\n')
1113 script.append(b'alias pwd="pwd -W"\n')
1091
1114
1092 n = 0
1115 n = 0
1093 for n, l in enumerate(lines):
1116 for n, l in enumerate(lines):
1094 if not l.endswith(b'\n'):
1117 if not l.endswith(b'\n'):
1095 l += b'\n'
1118 l += b'\n'
1096 if l.startswith(b'#require'):
1119 if l.startswith(b'#require'):
1097 lsplit = l.split()
1120 lsplit = l.split()
1098 if len(lsplit) < 2 or lsplit[0] != b'#require':
1121 if len(lsplit) < 2 or lsplit[0] != b'#require':
1099 after.setdefault(pos, []).append(' !!! invalid #require\n')
1122 after.setdefault(pos, []).append(' !!! invalid #require\n')
1100 haveresult, message = self._hghave(lsplit[1:])
1123 haveresult, message = self._hghave(lsplit[1:])
1101 if not haveresult:
1124 if not haveresult:
1102 script = [b'echo "%s"\nexit 80\n' % message]
1125 script = [b'echo "%s"\nexit 80\n' % message]
1103 break
1126 break
1104 after.setdefault(pos, []).append(l)
1127 after.setdefault(pos, []).append(l)
1105 elif l.startswith(b'#if'):
1128 elif l.startswith(b'#if'):
1106 lsplit = l.split()
1129 lsplit = l.split()
1107 if len(lsplit) < 2 or lsplit[0] != b'#if':
1130 if len(lsplit) < 2 or lsplit[0] != b'#if':
1108 after.setdefault(pos, []).append(' !!! invalid #if\n')
1131 after.setdefault(pos, []).append(' !!! invalid #if\n')
1109 if skipping is not None:
1132 if skipping is not None:
1110 after.setdefault(pos, []).append(' !!! nested #if\n')
1133 after.setdefault(pos, []).append(' !!! nested #if\n')
1111 skipping = not self._hghave(lsplit[1:])[0]
1134 skipping = not self._hghave(lsplit[1:])[0]
1112 after.setdefault(pos, []).append(l)
1135 after.setdefault(pos, []).append(l)
1113 elif l.startswith(b'#else'):
1136 elif l.startswith(b'#else'):
1114 if skipping is None:
1137 if skipping is None:
1115 after.setdefault(pos, []).append(' !!! missing #if\n')
1138 after.setdefault(pos, []).append(' !!! missing #if\n')
1116 skipping = not skipping
1139 skipping = not skipping
1117 after.setdefault(pos, []).append(l)
1140 after.setdefault(pos, []).append(l)
1118 elif l.startswith(b'#endif'):
1141 elif l.startswith(b'#endif'):
1119 if skipping is None:
1142 if skipping is None:
1120 after.setdefault(pos, []).append(' !!! missing #if\n')
1143 after.setdefault(pos, []).append(' !!! missing #if\n')
1121 skipping = None
1144 skipping = None
1122 after.setdefault(pos, []).append(l)
1145 after.setdefault(pos, []).append(l)
1123 elif skipping:
1146 elif skipping:
1124 after.setdefault(pos, []).append(l)
1147 after.setdefault(pos, []).append(l)
1125 elif l.startswith(b' >>> '): # python inlines
1148 elif l.startswith(b' >>> '): # python inlines
1126 after.setdefault(pos, []).append(l)
1149 after.setdefault(pos, []).append(l)
1127 prepos = pos
1150 prepos = pos
1128 pos = n
1151 pos = n
1129 if not inpython:
1152 if not inpython:
1130 # We've just entered a Python block. Add the header.
1153 # We've just entered a Python block. Add the header.
1131 inpython = True
1154 inpython = True
1132 addsalt(prepos, False) # Make sure we report the exit code.
1155 addsalt(prepos, False) # Make sure we report the exit code.
1133 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1156 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1134 addsalt(n, True)
1157 addsalt(n, True)
1135 script.append(l[2:])
1158 script.append(l[2:])
1136 elif l.startswith(b' ... '): # python inlines
1159 elif l.startswith(b' ... '): # python inlines
1137 after.setdefault(prepos, []).append(l)
1160 after.setdefault(prepos, []).append(l)
1138 script.append(l[2:])
1161 script.append(l[2:])
1139 elif l.startswith(b' $ '): # commands
1162 elif l.startswith(b' $ '): # commands
1140 if inpython:
1163 if inpython:
1141 script.append(b'EOF\n')
1164 script.append(b'EOF\n')
1142 inpython = False
1165 inpython = False
1143 after.setdefault(pos, []).append(l)
1166 after.setdefault(pos, []).append(l)
1144 prepos = pos
1167 prepos = pos
1145 pos = n
1168 pos = n
1146 addsalt(n, False)
1169 addsalt(n, False)
1147 cmd = l[4:].split()
1170 cmd = l[4:].split()
1148 if len(cmd) == 2 and cmd[0] == b'cd':
1171 if len(cmd) == 2 and cmd[0] == b'cd':
1149 l = b' $ cd %s || exit 1\n' % cmd[1]
1172 l = b' $ cd %s || exit 1\n' % cmd[1]
1150 script.append(l[4:])
1173 script.append(l[4:])
1151 elif l.startswith(b' > '): # continuations
1174 elif l.startswith(b' > '): # continuations
1152 after.setdefault(prepos, []).append(l)
1175 after.setdefault(prepos, []).append(l)
1153 script.append(l[4:])
1176 script.append(l[4:])
1154 elif l.startswith(b' '): # results
1177 elif l.startswith(b' '): # results
1155 # Queue up a list of expected results.
1178 # Queue up a list of expected results.
1156 expected.setdefault(pos, []).append(l[2:])
1179 expected.setdefault(pos, []).append(l[2:])
1157 else:
1180 else:
1158 if inpython:
1181 if inpython:
1159 script.append(b'EOF\n')
1182 script.append(b'EOF\n')
1160 inpython = False
1183 inpython = False
1161 # Non-command/result. Queue up for merged output.
1184 # Non-command/result. Queue up for merged output.
1162 after.setdefault(pos, []).append(l)
1185 after.setdefault(pos, []).append(l)
1163
1186
1164 if inpython:
1187 if inpython:
1165 script.append(b'EOF\n')
1188 script.append(b'EOF\n')
1166 if skipping is not None:
1189 if skipping is not None:
1167 after.setdefault(pos, []).append(' !!! missing #endif\n')
1190 after.setdefault(pos, []).append(' !!! missing #endif\n')
1168 addsalt(n + 1, False)
1191 addsalt(n + 1, False)
1169
1192
1170 return salt, script, after, expected
1193 return salt, script, after, expected
1171
1194
1172 def _processoutput(self, exitcode, output, salt, after, expected):
1195 def _processoutput(self, exitcode, output, salt, after, expected):
1173 # Merge the script output back into a unified test.
1196 # Merge the script output back into a unified test.
1174 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1197 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1175 if exitcode != 0:
1198 if exitcode != 0:
1176 warnonly = 3
1199 warnonly = 3
1177
1200
1178 pos = -1
1201 pos = -1
1179 postout = []
1202 postout = []
1180 for l in output:
1203 for l in output:
1181 lout, lcmd = l, None
1204 lout, lcmd = l, None
1182 if salt in l:
1205 if salt in l:
1183 lout, lcmd = l.split(salt, 1)
1206 lout, lcmd = l.split(salt, 1)
1184
1207
1185 while lout:
1208 while lout:
1186 if not lout.endswith(b'\n'):
1209 if not lout.endswith(b'\n'):
1187 lout += b' (no-eol)\n'
1210 lout += b' (no-eol)\n'
1188
1211
1189 # Find the expected output at the current position.
1212 # Find the expected output at the current position.
1190 els = [None]
1213 els = [None]
1191 if expected.get(pos, None):
1214 if expected.get(pos, None):
1192 els = expected[pos]
1215 els = expected[pos]
1193
1216
1194 i = 0
1217 i = 0
1195 optional = []
1218 optional = []
1196 while i < len(els):
1219 while i < len(els):
1197 el = els[i]
1220 el = els[i]
1198
1221
1199 r = TTest.linematch(el, lout)
1222 r = TTest.linematch(el, lout)
1200 if isinstance(r, str):
1223 if isinstance(r, str):
1201 if r == '+glob':
1224 if r == '+glob':
1202 lout = el[:-1] + ' (glob)\n'
1225 lout = el[:-1] + ' (glob)\n'
1203 r = '' # Warn only this line.
1226 r = '' # Warn only this line.
1204 elif r == '-glob':
1227 elif r == '-glob':
1205 lout = ''.join(el.rsplit(' (glob)', 1))
1228 lout = ''.join(el.rsplit(' (glob)', 1))
1206 r = '' # Warn only this line.
1229 r = '' # Warn only this line.
1207 elif r == "retry":
1230 elif r == "retry":
1208 postout.append(b' ' + el)
1231 postout.append(b' ' + el)
1209 els.pop(i)
1232 els.pop(i)
1210 break
1233 break
1211 else:
1234 else:
1212 log('\ninfo, unknown linematch result: %r\n' % r)
1235 log('\ninfo, unknown linematch result: %r\n' % r)
1213 r = False
1236 r = False
1214 if r:
1237 if r:
1215 els.pop(i)
1238 els.pop(i)
1216 break
1239 break
1217 if el and el.endswith(b" (?)\n"):
1240 if el and el.endswith(b" (?)\n"):
1218 optional.append(i)
1241 optional.append(i)
1219 i += 1
1242 i += 1
1220
1243
1221 if r:
1244 if r:
1222 if r == "retry":
1245 if r == "retry":
1223 continue
1246 continue
1224 # clean up any optional leftovers
1247 # clean up any optional leftovers
1225 for i in optional:
1248 for i in optional:
1226 postout.append(b' ' + els[i])
1249 postout.append(b' ' + els[i])
1227 for i in reversed(optional):
1250 for i in reversed(optional):
1228 del els[i]
1251 del els[i]
1229 postout.append(b' ' + el)
1252 postout.append(b' ' + el)
1230 else:
1253 else:
1231 if self.NEEDESCAPE(lout):
1254 if self.NEEDESCAPE(lout):
1232 lout = TTest._stringescape(b'%s (esc)\n' %
1255 lout = TTest._stringescape(b'%s (esc)\n' %
1233 lout.rstrip(b'\n'))
1256 lout.rstrip(b'\n'))
1234 postout.append(b' ' + lout) # Let diff deal with it.
1257 postout.append(b' ' + lout) # Let diff deal with it.
1235 if r != '': # If line failed.
1258 if r != '': # If line failed.
1236 warnonly = 3 # for sure not
1259 warnonly = 3 # for sure not
1237 elif warnonly == 1: # Is "not yet" and line is warn only.
1260 elif warnonly == 1: # Is "not yet" and line is warn only.
1238 warnonly = 2 # Yes do warn.
1261 warnonly = 2 # Yes do warn.
1239 break
1262 break
1240 else:
1263 else:
1241 # clean up any optional leftovers
1264 # clean up any optional leftovers
1242 while expected.get(pos, None):
1265 while expected.get(pos, None):
1243 el = expected[pos].pop(0)
1266 el = expected[pos].pop(0)
1244 if el and not el.endswith(b" (?)\n"):
1267 if el and not el.endswith(b" (?)\n"):
1245 break
1268 break
1246 postout.append(b' ' + el)
1269 postout.append(b' ' + el)
1247
1270
1248 if lcmd:
1271 if lcmd:
1249 # Add on last return code.
1272 # Add on last return code.
1250 ret = int(lcmd.split()[1])
1273 ret = int(lcmd.split()[1])
1251 if ret != 0:
1274 if ret != 0:
1252 postout.append(b' [%d]\n' % ret)
1275 postout.append(b' [%d]\n' % ret)
1253 if pos in after:
1276 if pos in after:
1254 # Merge in non-active test bits.
1277 # Merge in non-active test bits.
1255 postout += after.pop(pos)
1278 postout += after.pop(pos)
1256 pos = int(lcmd.split()[0])
1279 pos = int(lcmd.split()[0])
1257
1280
1258 if pos in after:
1281 if pos in after:
1259 postout += after.pop(pos)
1282 postout += after.pop(pos)
1260
1283
1261 if warnonly == 2:
1284 if warnonly == 2:
1262 exitcode = False # Set exitcode to warned.
1285 exitcode = False # Set exitcode to warned.
1263
1286
1264 return exitcode, postout
1287 return exitcode, postout
1265
1288
1266 @staticmethod
1289 @staticmethod
1267 def rematch(el, l):
1290 def rematch(el, l):
1268 try:
1291 try:
1269 # use \Z to ensure that the regex matches to the end of the string
1292 # use \Z to ensure that the regex matches to the end of the string
1270 if os.name == 'nt':
1293 if os.name == 'nt':
1271 return re.match(el + br'\r?\n\Z', l)
1294 return re.match(el + br'\r?\n\Z', l)
1272 return re.match(el + br'\n\Z', l)
1295 return re.match(el + br'\n\Z', l)
1273 except re.error:
1296 except re.error:
1274 # el is an invalid regex
1297 # el is an invalid regex
1275 return False
1298 return False
1276
1299
1277 @staticmethod
1300 @staticmethod
1278 def globmatch(el, l):
1301 def globmatch(el, l):
1279 # The only supported special characters are * and ? plus / which also
1302 # The only supported special characters are * and ? plus / which also
1280 # matches \ on windows. Escaping of these characters is supported.
1303 # matches \ on windows. Escaping of these characters is supported.
1281 if el + b'\n' == l:
1304 if el + b'\n' == l:
1282 if os.altsep:
1305 if os.altsep:
1283 # matching on "/" is not needed for this line
1306 # matching on "/" is not needed for this line
1284 for pat in checkcodeglobpats:
1307 for pat in checkcodeglobpats:
1285 if pat.match(el):
1308 if pat.match(el):
1286 return True
1309 return True
1287 return b'-glob'
1310 return b'-glob'
1288 return True
1311 return True
1289 el = el.replace(b'127.0.0.1', b'*')
1312 el = el.replace(b'127.0.0.1', b'*')
1290 i, n = 0, len(el)
1313 i, n = 0, len(el)
1291 res = b''
1314 res = b''
1292 while i < n:
1315 while i < n:
1293 c = el[i:i + 1]
1316 c = el[i:i + 1]
1294 i += 1
1317 i += 1
1295 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1318 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1296 res += el[i - 1:i + 1]
1319 res += el[i - 1:i + 1]
1297 i += 1
1320 i += 1
1298 elif c == b'*':
1321 elif c == b'*':
1299 res += b'.*'
1322 res += b'.*'
1300 elif c == b'?':
1323 elif c == b'?':
1301 res += b'.'
1324 res += b'.'
1302 elif c == b'/' and os.altsep:
1325 elif c == b'/' and os.altsep:
1303 res += b'[/\\\\]'
1326 res += b'[/\\\\]'
1304 else:
1327 else:
1305 res += re.escape(c)
1328 res += re.escape(c)
1306 return TTest.rematch(res, l)
1329 return TTest.rematch(res, l)
1307
1330
1308 @staticmethod
1331 @staticmethod
1309 def linematch(el, l):
1332 def linematch(el, l):
1310 retry = False
1333 retry = False
1311 if el == l: # perfect match (fast)
1334 if el == l: # perfect match (fast)
1312 return True
1335 return True
1313 if el:
1336 if el:
1314 if el.endswith(b" (?)\n"):
1337 if el.endswith(b" (?)\n"):
1315 retry = "retry"
1338 retry = "retry"
1316 el = el[:-5] + b"\n"
1339 el = el[:-5] + b"\n"
1317 if el.endswith(b" (esc)\n"):
1340 if el.endswith(b" (esc)\n"):
1318 if PYTHON3:
1341 if PYTHON3:
1319 el = el[:-7].decode('unicode_escape') + '\n'
1342 el = el[:-7].decode('unicode_escape') + '\n'
1320 el = el.encode('utf-8')
1343 el = el.encode('utf-8')
1321 else:
1344 else:
1322 el = el[:-7].decode('string-escape') + '\n'
1345 el = el[:-7].decode('string-escape') + '\n'
1323 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1346 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1324 return True
1347 return True
1325 if el.endswith(b" (re)\n"):
1348 if el.endswith(b" (re)\n"):
1326 return TTest.rematch(el[:-6], l) or retry
1349 return TTest.rematch(el[:-6], l) or retry
1327 if el.endswith(b" (glob)\n"):
1350 if el.endswith(b" (glob)\n"):
1328 # ignore '(glob)' added to l by 'replacements'
1351 # ignore '(glob)' added to l by 'replacements'
1329 if l.endswith(b" (glob)\n"):
1352 if l.endswith(b" (glob)\n"):
1330 l = l[:-8] + b"\n"
1353 l = l[:-8] + b"\n"
1331 return TTest.globmatch(el[:-8], l)
1354 return TTest.globmatch(el[:-8], l)
1332 if os.altsep and l.replace(b'\\', b'/') == el:
1355 if os.altsep and l.replace(b'\\', b'/') == el:
1333 return b'+glob'
1356 return b'+glob'
1334 return retry
1357 return retry
1335
1358
1336 @staticmethod
1359 @staticmethod
1337 def parsehghaveoutput(lines):
1360 def parsehghaveoutput(lines):
1338 '''Parse hghave log lines.
1361 '''Parse hghave log lines.
1339
1362
1340 Return tuple of lists (missing, failed):
1363 Return tuple of lists (missing, failed):
1341 * the missing/unknown features
1364 * the missing/unknown features
1342 * the features for which existence check failed'''
1365 * the features for which existence check failed'''
1343 missing = []
1366 missing = []
1344 failed = []
1367 failed = []
1345 for line in lines:
1368 for line in lines:
1346 if line.startswith(TTest.SKIPPED_PREFIX):
1369 if line.startswith(TTest.SKIPPED_PREFIX):
1347 line = line.splitlines()[0]
1370 line = line.splitlines()[0]
1348 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1371 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1349 elif line.startswith(TTest.FAILED_PREFIX):
1372 elif line.startswith(TTest.FAILED_PREFIX):
1350 line = line.splitlines()[0]
1373 line = line.splitlines()[0]
1351 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1374 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1352
1375
1353 return missing, failed
1376 return missing, failed
1354
1377
1355 @staticmethod
1378 @staticmethod
1356 def _escapef(m):
1379 def _escapef(m):
1357 return TTest.ESCAPEMAP[m.group(0)]
1380 return TTest.ESCAPEMAP[m.group(0)]
1358
1381
1359 @staticmethod
1382 @staticmethod
1360 def _stringescape(s):
1383 def _stringescape(s):
1361 return TTest.ESCAPESUB(TTest._escapef, s)
1384 return TTest.ESCAPESUB(TTest._escapef, s)
1362
1385
1363 iolock = threading.RLock()
1386 iolock = threading.RLock()
1364
1387
1365 class SkipTest(Exception):
1388 class SkipTest(Exception):
1366 """Raised to indicate that a test is to be skipped."""
1389 """Raised to indicate that a test is to be skipped."""
1367
1390
1368 class IgnoreTest(Exception):
1391 class IgnoreTest(Exception):
1369 """Raised to indicate that a test is to be ignored."""
1392 """Raised to indicate that a test is to be ignored."""
1370
1393
1371 class WarnTest(Exception):
1394 class WarnTest(Exception):
1372 """Raised to indicate that a test warned."""
1395 """Raised to indicate that a test warned."""
1373
1396
1374 class ReportedTest(Exception):
1397 class ReportedTest(Exception):
1375 """Raised to indicate that a test already reported."""
1398 """Raised to indicate that a test already reported."""
1376
1399
1377 class TestResult(unittest._TextTestResult):
1400 class TestResult(unittest._TextTestResult):
1378 """Holds results when executing via unittest."""
1401 """Holds results when executing via unittest."""
1379 # Don't worry too much about accessing the non-public _TextTestResult.
1402 # Don't worry too much about accessing the non-public _TextTestResult.
1380 # It is relatively common in Python testing tools.
1403 # It is relatively common in Python testing tools.
1381 def __init__(self, options, *args, **kwargs):
1404 def __init__(self, options, *args, **kwargs):
1382 super(TestResult, self).__init__(*args, **kwargs)
1405 super(TestResult, self).__init__(*args, **kwargs)
1383
1406
1384 self._options = options
1407 self._options = options
1385
1408
1386 # unittest.TestResult didn't have skipped until 2.7. We need to
1409 # unittest.TestResult didn't have skipped until 2.7. We need to
1387 # polyfill it.
1410 # polyfill it.
1388 self.skipped = []
1411 self.skipped = []
1389
1412
1390 # We have a custom "ignored" result that isn't present in any Python
1413 # We have a custom "ignored" result that isn't present in any Python
1391 # unittest implementation. It is very similar to skipped. It may make
1414 # unittest implementation. It is very similar to skipped. It may make
1392 # sense to map it into skip some day.
1415 # sense to map it into skip some day.
1393 self.ignored = []
1416 self.ignored = []
1394
1417
1395 # We have a custom "warned" result that isn't present in any Python
1418 # We have a custom "warned" result that isn't present in any Python
1396 # unittest implementation. It is very similar to failed. It may make
1419 # unittest implementation. It is very similar to failed. It may make
1397 # sense to map it into fail some day.
1420 # sense to map it into fail some day.
1398 self.warned = []
1421 self.warned = []
1399
1422
1400 self.times = []
1423 self.times = []
1401 self._firststarttime = None
1424 self._firststarttime = None
1402 # Data stored for the benefit of generating xunit reports.
1425 # Data stored for the benefit of generating xunit reports.
1403 self.successes = []
1426 self.successes = []
1404 self.faildata = {}
1427 self.faildata = {}
1405
1428
1406 def addFailure(self, test, reason):
1429 def addFailure(self, test, reason):
1407 self.failures.append((test, reason))
1430 self.failures.append((test, reason))
1408
1431
1409 if self._options.first:
1432 if self._options.first:
1410 self.stop()
1433 self.stop()
1411 else:
1434 else:
1412 with iolock:
1435 with iolock:
1413 if reason == "timed out":
1436 if reason == "timed out":
1414 self.stream.write('t')
1437 self.stream.write('t')
1415 else:
1438 else:
1416 if not self._options.nodiff:
1439 if not self._options.nodiff:
1417 self.stream.write('\nERROR: %s output changed\n' % test)
1440 self.stream.write('\nERROR: %s output changed\n' % test)
1418 self.stream.write('!')
1441 self.stream.write('!')
1419
1442
1420 self.stream.flush()
1443 self.stream.flush()
1421
1444
1422 def addSuccess(self, test):
1445 def addSuccess(self, test):
1423 with iolock:
1446 with iolock:
1424 super(TestResult, self).addSuccess(test)
1447 super(TestResult, self).addSuccess(test)
1425 self.successes.append(test)
1448 self.successes.append(test)
1426
1449
1427 def addError(self, test, err):
1450 def addError(self, test, err):
1428 super(TestResult, self).addError(test, err)
1451 super(TestResult, self).addError(test, err)
1429 if self._options.first:
1452 if self._options.first:
1430 self.stop()
1453 self.stop()
1431
1454
1432 # Polyfill.
1455 # Polyfill.
1433 def addSkip(self, test, reason):
1456 def addSkip(self, test, reason):
1434 self.skipped.append((test, reason))
1457 self.skipped.append((test, reason))
1435 with iolock:
1458 with iolock:
1436 if self.showAll:
1459 if self.showAll:
1437 self.stream.writeln('skipped %s' % reason)
1460 self.stream.writeln('skipped %s' % reason)
1438 else:
1461 else:
1439 self.stream.write('s')
1462 self.stream.write('s')
1440 self.stream.flush()
1463 self.stream.flush()
1441
1464
1442 def addIgnore(self, test, reason):
1465 def addIgnore(self, test, reason):
1443 self.ignored.append((test, reason))
1466 self.ignored.append((test, reason))
1444 with iolock:
1467 with iolock:
1445 if self.showAll:
1468 if self.showAll:
1446 self.stream.writeln('ignored %s' % reason)
1469 self.stream.writeln('ignored %s' % reason)
1447 else:
1470 else:
1448 if reason not in ('not retesting', "doesn't match keyword"):
1471 if reason not in ('not retesting', "doesn't match keyword"):
1449 self.stream.write('i')
1472 self.stream.write('i')
1450 else:
1473 else:
1451 self.testsRun += 1
1474 self.testsRun += 1
1452 self.stream.flush()
1475 self.stream.flush()
1453
1476
1454 def addWarn(self, test, reason):
1477 def addWarn(self, test, reason):
1455 self.warned.append((test, reason))
1478 self.warned.append((test, reason))
1456
1479
1457 if self._options.first:
1480 if self._options.first:
1458 self.stop()
1481 self.stop()
1459
1482
1460 with iolock:
1483 with iolock:
1461 if self.showAll:
1484 if self.showAll:
1462 self.stream.writeln('warned %s' % reason)
1485 self.stream.writeln('warned %s' % reason)
1463 else:
1486 else:
1464 self.stream.write('~')
1487 self.stream.write('~')
1465 self.stream.flush()
1488 self.stream.flush()
1466
1489
1467 def addOutputMismatch(self, test, ret, got, expected):
1490 def addOutputMismatch(self, test, ret, got, expected):
1468 """Record a mismatch in test output for a particular test."""
1491 """Record a mismatch in test output for a particular test."""
1469 if self.shouldStop:
1492 if self.shouldStop:
1470 # don't print, some other test case already failed and
1493 # don't print, some other test case already failed and
1471 # printed, we're just stale and probably failed due to our
1494 # printed, we're just stale and probably failed due to our
1472 # temp dir getting cleaned up.
1495 # temp dir getting cleaned up.
1473 return
1496 return
1474
1497
1475 accepted = False
1498 accepted = False
1476 lines = []
1499 lines = []
1477
1500
1478 with iolock:
1501 with iolock:
1479 if self._options.nodiff:
1502 if self._options.nodiff:
1480 pass
1503 pass
1481 elif self._options.view:
1504 elif self._options.view:
1482 v = self._options.view
1505 v = self._options.view
1483 if PYTHON3:
1506 if PYTHON3:
1484 v = _bytespath(v)
1507 v = _bytespath(v)
1485 os.system(b"%s %s %s" %
1508 os.system(b"%s %s %s" %
1486 (v, test.refpath, test.errpath))
1509 (v, test.refpath, test.errpath))
1487 else:
1510 else:
1488 servefail, lines = getdiff(expected, got,
1511 servefail, lines = getdiff(expected, got,
1489 test.refpath, test.errpath)
1512 test.refpath, test.errpath)
1490 if servefail:
1513 if servefail:
1491 self.addFailure(
1514 self.addFailure(
1492 test,
1515 test,
1493 'server failed to start (HGPORT=%s)' % test._startport)
1516 'server failed to start (HGPORT=%s)' % test._startport)
1494 raise ReportedTest('server failed to start')
1517 raise ReportedTest('server failed to start')
1495 else:
1518 else:
1496 self.stream.write('\n')
1519 self.stream.write('\n')
1497 for line in lines:
1520 for line in lines:
1498 if PYTHON3:
1521 if PYTHON3:
1499 self.stream.flush()
1522 self.stream.flush()
1500 self.stream.buffer.write(line)
1523 self.stream.buffer.write(line)
1501 self.stream.buffer.flush()
1524 self.stream.buffer.flush()
1502 else:
1525 else:
1503 self.stream.write(line)
1526 self.stream.write(line)
1504 self.stream.flush()
1527 self.stream.flush()
1505
1528
1506 # handle interactive prompt without releasing iolock
1529 # handle interactive prompt without releasing iolock
1507 if self._options.interactive:
1530 if self._options.interactive:
1508 self.stream.write('Accept this change? [n] ')
1531 self.stream.write('Accept this change? [n] ')
1509 answer = sys.stdin.readline().strip()
1532 answer = sys.stdin.readline().strip()
1510 if answer.lower() in ('y', 'yes'):
1533 if answer.lower() in ('y', 'yes'):
1511 if test.name.endswith('.t'):
1534 if test.name.endswith('.t'):
1512 rename(test.errpath, test.path)
1535 rename(test.errpath, test.path)
1513 else:
1536 else:
1514 rename(test.errpath, '%s.out' % test.path)
1537 rename(test.errpath, '%s.out' % test.path)
1515 accepted = True
1538 accepted = True
1516 if not accepted:
1539 if not accepted:
1517 self.faildata[test.name] = b''.join(lines)
1540 self.faildata[test.name] = b''.join(lines)
1518
1541
1519 return accepted
1542 return accepted
1520
1543
1521 def startTest(self, test):
1544 def startTest(self, test):
1522 super(TestResult, self).startTest(test)
1545 super(TestResult, self).startTest(test)
1523
1546
1524 # os.times module computes the user time and system time spent by
1547 # os.times module computes the user time and system time spent by
1525 # child's processes along with real elapsed time taken by a process.
1548 # child's processes along with real elapsed time taken by a process.
1526 # This module has one limitation. It can only work for Linux user
1549 # This module has one limitation. It can only work for Linux user
1527 # and not for Windows.
1550 # and not for Windows.
1528 test.started = os.times()
1551 test.started = os.times()
1529 if self._firststarttime is None: # thread racy but irrelevant
1552 if self._firststarttime is None: # thread racy but irrelevant
1530 self._firststarttime = test.started[4]
1553 self._firststarttime = test.started[4]
1531
1554
1532 def stopTest(self, test, interrupted=False):
1555 def stopTest(self, test, interrupted=False):
1533 super(TestResult, self).stopTest(test)
1556 super(TestResult, self).stopTest(test)
1534
1557
1535 test.stopped = os.times()
1558 test.stopped = os.times()
1536
1559
1537 starttime = test.started
1560 starttime = test.started
1538 endtime = test.stopped
1561 endtime = test.stopped
1539 origin = self._firststarttime
1562 origin = self._firststarttime
1540 self.times.append((test.name,
1563 self.times.append((test.name,
1541 endtime[2] - starttime[2], # user space CPU time
1564 endtime[2] - starttime[2], # user space CPU time
1542 endtime[3] - starttime[3], # sys space CPU time
1565 endtime[3] - starttime[3], # sys space CPU time
1543 endtime[4] - starttime[4], # real time
1566 endtime[4] - starttime[4], # real time
1544 starttime[4] - origin, # start date in run context
1567 starttime[4] - origin, # start date in run context
1545 endtime[4] - origin, # end date in run context
1568 endtime[4] - origin, # end date in run context
1546 ))
1569 ))
1547
1570
1548 if interrupted:
1571 if interrupted:
1549 with iolock:
1572 with iolock:
1550 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1573 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1551 test.name, self.times[-1][3]))
1574 test.name, self.times[-1][3]))
1552
1575
1553 class TestSuite(unittest.TestSuite):
1576 class TestSuite(unittest.TestSuite):
1554 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1577 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1555
1578
1556 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1579 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1557 retest=False, keywords=None, loop=False, runs_per_test=1,
1580 retest=False, keywords=None, loop=False, runs_per_test=1,
1558 loadtest=None, showchannels=False,
1581 loadtest=None, showchannels=False,
1559 *args, **kwargs):
1582 *args, **kwargs):
1560 """Create a new instance that can run tests with a configuration.
1583 """Create a new instance that can run tests with a configuration.
1561
1584
1562 testdir specifies the directory where tests are executed from. This
1585 testdir specifies the directory where tests are executed from. This
1563 is typically the ``tests`` directory from Mercurial's source
1586 is typically the ``tests`` directory from Mercurial's source
1564 repository.
1587 repository.
1565
1588
1566 jobs specifies the number of jobs to run concurrently. Each test
1589 jobs specifies the number of jobs to run concurrently. Each test
1567 executes on its own thread. Tests actually spawn new processes, so
1590 executes on its own thread. Tests actually spawn new processes, so
1568 state mutation should not be an issue.
1591 state mutation should not be an issue.
1569
1592
1570 If there is only one job, it will use the main thread.
1593 If there is only one job, it will use the main thread.
1571
1594
1572 whitelist and blacklist denote tests that have been whitelisted and
1595 whitelist and blacklist denote tests that have been whitelisted and
1573 blacklisted, respectively. These arguments don't belong in TestSuite.
1596 blacklisted, respectively. These arguments don't belong in TestSuite.
1574 Instead, whitelist and blacklist should be handled by the thing that
1597 Instead, whitelist and blacklist should be handled by the thing that
1575 populates the TestSuite with tests. They are present to preserve
1598 populates the TestSuite with tests. They are present to preserve
1576 backwards compatible behavior which reports skipped tests as part
1599 backwards compatible behavior which reports skipped tests as part
1577 of the results.
1600 of the results.
1578
1601
1579 retest denotes whether to retest failed tests. This arguably belongs
1602 retest denotes whether to retest failed tests. This arguably belongs
1580 outside of TestSuite.
1603 outside of TestSuite.
1581
1604
1582 keywords denotes key words that will be used to filter which tests
1605 keywords denotes key words that will be used to filter which tests
1583 to execute. This arguably belongs outside of TestSuite.
1606 to execute. This arguably belongs outside of TestSuite.
1584
1607
1585 loop denotes whether to loop over tests forever.
1608 loop denotes whether to loop over tests forever.
1586 """
1609 """
1587 super(TestSuite, self).__init__(*args, **kwargs)
1610 super(TestSuite, self).__init__(*args, **kwargs)
1588
1611
1589 self._jobs = jobs
1612 self._jobs = jobs
1590 self._whitelist = whitelist
1613 self._whitelist = whitelist
1591 self._blacklist = blacklist
1614 self._blacklist = blacklist
1592 self._retest = retest
1615 self._retest = retest
1593 self._keywords = keywords
1616 self._keywords = keywords
1594 self._loop = loop
1617 self._loop = loop
1595 self._runs_per_test = runs_per_test
1618 self._runs_per_test = runs_per_test
1596 self._loadtest = loadtest
1619 self._loadtest = loadtest
1597 self._showchannels = showchannels
1620 self._showchannels = showchannels
1598
1621
1599 def run(self, result):
1622 def run(self, result):
1600 # We have a number of filters that need to be applied. We do this
1623 # We have a number of filters that need to be applied. We do this
1601 # here instead of inside Test because it makes the running logic for
1624 # here instead of inside Test because it makes the running logic for
1602 # Test simpler.
1625 # Test simpler.
1603 tests = []
1626 tests = []
1604 num_tests = [0]
1627 num_tests = [0]
1605 for test in self._tests:
1628 for test in self._tests:
1606 def get():
1629 def get():
1607 num_tests[0] += 1
1630 num_tests[0] += 1
1608 if getattr(test, 'should_reload', False):
1631 if getattr(test, 'should_reload', False):
1609 return self._loadtest(test.path, num_tests[0])
1632 return self._loadtest(test.path, num_tests[0])
1610 return test
1633 return test
1611 if not os.path.exists(test.path):
1634 if not os.path.exists(test.path):
1612 result.addSkip(test, "Doesn't exist")
1635 result.addSkip(test, "Doesn't exist")
1613 continue
1636 continue
1614
1637
1615 if not (self._whitelist and test.name in self._whitelist):
1638 if not (self._whitelist and test.name in self._whitelist):
1616 if self._blacklist and test.bname in self._blacklist:
1639 if self._blacklist and test.bname in self._blacklist:
1617 result.addSkip(test, 'blacklisted')
1640 result.addSkip(test, 'blacklisted')
1618 continue
1641 continue
1619
1642
1620 if self._retest and not os.path.exists(test.errpath):
1643 if self._retest and not os.path.exists(test.errpath):
1621 result.addIgnore(test, 'not retesting')
1644 result.addIgnore(test, 'not retesting')
1622 continue
1645 continue
1623
1646
1624 if self._keywords:
1647 if self._keywords:
1625 f = open(test.path, 'rb')
1648 f = open(test.path, 'rb')
1626 t = f.read().lower() + test.bname.lower()
1649 t = f.read().lower() + test.bname.lower()
1627 f.close()
1650 f.close()
1628 ignored = False
1651 ignored = False
1629 for k in self._keywords.lower().split():
1652 for k in self._keywords.lower().split():
1630 if k not in t:
1653 if k not in t:
1631 result.addIgnore(test, "doesn't match keyword")
1654 result.addIgnore(test, "doesn't match keyword")
1632 ignored = True
1655 ignored = True
1633 break
1656 break
1634
1657
1635 if ignored:
1658 if ignored:
1636 continue
1659 continue
1637 for _ in xrange(self._runs_per_test):
1660 for _ in xrange(self._runs_per_test):
1638 tests.append(get())
1661 tests.append(get())
1639
1662
1640 runtests = list(tests)
1663 runtests = list(tests)
1641 done = queue.Queue()
1664 done = queue.Queue()
1642 running = 0
1665 running = 0
1643
1666
1644 channels = [""] * self._jobs
1667 channels = [""] * self._jobs
1645
1668
1646 def job(test, result):
1669 def job(test, result):
1647 for n, v in enumerate(channels):
1670 for n, v in enumerate(channels):
1648 if not v:
1671 if not v:
1649 channel = n
1672 channel = n
1650 break
1673 break
1651 channels[channel] = "=" + test.name[5:].split(".")[0]
1674 channels[channel] = "=" + test.name[5:].split(".")[0]
1652 try:
1675 try:
1653 test(result)
1676 test(result)
1654 done.put(None)
1677 done.put(None)
1655 except KeyboardInterrupt:
1678 except KeyboardInterrupt:
1656 pass
1679 pass
1657 except: # re-raises
1680 except: # re-raises
1658 done.put(('!', test, 'run-test raised an error, see traceback'))
1681 done.put(('!', test, 'run-test raised an error, see traceback'))
1659 raise
1682 raise
1660 try:
1683 try:
1661 channels[channel] = ''
1684 channels[channel] = ''
1662 except IndexError:
1685 except IndexError:
1663 pass
1686 pass
1664
1687
1665 def stat():
1688 def stat():
1666 count = 0
1689 count = 0
1667 while channels:
1690 while channels:
1668 d = '\n%03s ' % count
1691 d = '\n%03s ' % count
1669 for n, v in enumerate(channels):
1692 for n, v in enumerate(channels):
1670 if v:
1693 if v:
1671 d += v[0]
1694 d += v[0]
1672 channels[n] = v[1:] or '.'
1695 channels[n] = v[1:] or '.'
1673 else:
1696 else:
1674 d += ' '
1697 d += ' '
1675 d += ' '
1698 d += ' '
1676 with iolock:
1699 with iolock:
1677 sys.stdout.write(d + ' ')
1700 sys.stdout.write(d + ' ')
1678 sys.stdout.flush()
1701 sys.stdout.flush()
1679 for x in xrange(10):
1702 for x in xrange(10):
1680 if channels:
1703 if channels:
1681 time.sleep(.1)
1704 time.sleep(.1)
1682 count += 1
1705 count += 1
1683
1706
1684 stoppedearly = False
1707 stoppedearly = False
1685
1708
1686 if self._showchannels:
1709 if self._showchannels:
1687 statthread = threading.Thread(target=stat, name="stat")
1710 statthread = threading.Thread(target=stat, name="stat")
1688 statthread.start()
1711 statthread.start()
1689
1712
1690 try:
1713 try:
1691 while tests or running:
1714 while tests or running:
1692 if not done.empty() or running == self._jobs or not tests:
1715 if not done.empty() or running == self._jobs or not tests:
1693 try:
1716 try:
1694 done.get(True, 1)
1717 done.get(True, 1)
1695 running -= 1
1718 running -= 1
1696 if result and result.shouldStop:
1719 if result and result.shouldStop:
1697 stoppedearly = True
1720 stoppedearly = True
1698 break
1721 break
1699 except queue.Empty:
1722 except queue.Empty:
1700 continue
1723 continue
1701 if tests and not running == self._jobs:
1724 if tests and not running == self._jobs:
1702 test = tests.pop(0)
1725 test = tests.pop(0)
1703 if self._loop:
1726 if self._loop:
1704 if getattr(test, 'should_reload', False):
1727 if getattr(test, 'should_reload', False):
1705 num_tests[0] += 1
1728 num_tests[0] += 1
1706 tests.append(
1729 tests.append(
1707 self._loadtest(test.name, num_tests[0]))
1730 self._loadtest(test.name, num_tests[0]))
1708 else:
1731 else:
1709 tests.append(test)
1732 tests.append(test)
1710 if self._jobs == 1:
1733 if self._jobs == 1:
1711 job(test, result)
1734 job(test, result)
1712 else:
1735 else:
1713 t = threading.Thread(target=job, name=test.name,
1736 t = threading.Thread(target=job, name=test.name,
1714 args=(test, result))
1737 args=(test, result))
1715 t.start()
1738 t.start()
1716 running += 1
1739 running += 1
1717
1740
1718 # If we stop early we still need to wait on started tests to
1741 # If we stop early we still need to wait on started tests to
1719 # finish. Otherwise, there is a race between the test completing
1742 # finish. Otherwise, there is a race between the test completing
1720 # and the test's cleanup code running. This could result in the
1743 # and the test's cleanup code running. This could result in the
1721 # test reporting incorrect.
1744 # test reporting incorrect.
1722 if stoppedearly:
1745 if stoppedearly:
1723 while running:
1746 while running:
1724 try:
1747 try:
1725 done.get(True, 1)
1748 done.get(True, 1)
1726 running -= 1
1749 running -= 1
1727 except queue.Empty:
1750 except queue.Empty:
1728 continue
1751 continue
1729 except KeyboardInterrupt:
1752 except KeyboardInterrupt:
1730 for test in runtests:
1753 for test in runtests:
1731 test.abort()
1754 test.abort()
1732
1755
1733 channels = []
1756 channels = []
1734
1757
1735 return result
1758 return result
1736
1759
1737 # Save the most recent 5 wall-clock runtimes of each test to a
1760 # Save the most recent 5 wall-clock runtimes of each test to a
1738 # human-readable text file named .testtimes. Tests are sorted
1761 # human-readable text file named .testtimes. Tests are sorted
1739 # alphabetically, while times for each test are listed from oldest to
1762 # alphabetically, while times for each test are listed from oldest to
1740 # newest.
1763 # newest.
1741
1764
1742 def loadtimes(testdir):
1765 def loadtimes(testdir):
1743 times = []
1766 times = []
1744 try:
1767 try:
1745 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1768 with open(os.path.join(testdir, b'.testtimes-')) as fp:
1746 for line in fp:
1769 for line in fp:
1747 ts = line.split()
1770 ts = line.split()
1748 times.append((ts[0], [float(t) for t in ts[1:]]))
1771 times.append((ts[0], [float(t) for t in ts[1:]]))
1749 except IOError as err:
1772 except IOError as err:
1750 if err.errno != errno.ENOENT:
1773 if err.errno != errno.ENOENT:
1751 raise
1774 raise
1752 return times
1775 return times
1753
1776
1754 def savetimes(testdir, result):
1777 def savetimes(testdir, result):
1755 saved = dict(loadtimes(testdir))
1778 saved = dict(loadtimes(testdir))
1756 maxruns = 5
1779 maxruns = 5
1757 skipped = set([str(t[0]) for t in result.skipped])
1780 skipped = set([str(t[0]) for t in result.skipped])
1758 for tdata in result.times:
1781 for tdata in result.times:
1759 test, real = tdata[0], tdata[3]
1782 test, real = tdata[0], tdata[3]
1760 if test not in skipped:
1783 if test not in skipped:
1761 ts = saved.setdefault(test, [])
1784 ts = saved.setdefault(test, [])
1762 ts.append(real)
1785 ts.append(real)
1763 ts[:] = ts[-maxruns:]
1786 ts[:] = ts[-maxruns:]
1764
1787
1765 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1788 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1766 dir=testdir, text=True)
1789 dir=testdir, text=True)
1767 with os.fdopen(fd, 'w') as fp:
1790 with os.fdopen(fd, 'w') as fp:
1768 for name, ts in sorted(saved.items()):
1791 for name, ts in sorted(saved.items()):
1769 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1792 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1770 timepath = os.path.join(testdir, b'.testtimes')
1793 timepath = os.path.join(testdir, b'.testtimes')
1771 try:
1794 try:
1772 os.unlink(timepath)
1795 os.unlink(timepath)
1773 except OSError:
1796 except OSError:
1774 pass
1797 pass
1775 try:
1798 try:
1776 os.rename(tmpname, timepath)
1799 os.rename(tmpname, timepath)
1777 except OSError:
1800 except OSError:
1778 pass
1801 pass
1779
1802
1780 class TextTestRunner(unittest.TextTestRunner):
1803 class TextTestRunner(unittest.TextTestRunner):
1781 """Custom unittest test runner that uses appropriate settings."""
1804 """Custom unittest test runner that uses appropriate settings."""
1782
1805
1783 def __init__(self, runner, *args, **kwargs):
1806 def __init__(self, runner, *args, **kwargs):
1784 super(TextTestRunner, self).__init__(*args, **kwargs)
1807 super(TextTestRunner, self).__init__(*args, **kwargs)
1785
1808
1786 self._runner = runner
1809 self._runner = runner
1787
1810
1788 def run(self, test):
1811 def run(self, test):
1789 result = TestResult(self._runner.options, self.stream,
1812 result = TestResult(self._runner.options, self.stream,
1790 self.descriptions, self.verbosity)
1813 self.descriptions, self.verbosity)
1791
1814
1792 test(result)
1815 test(result)
1793
1816
1794 failed = len(result.failures)
1817 failed = len(result.failures)
1795 warned = len(result.warned)
1818 warned = len(result.warned)
1796 skipped = len(result.skipped)
1819 skipped = len(result.skipped)
1797 ignored = len(result.ignored)
1820 ignored = len(result.ignored)
1798
1821
1799 with iolock:
1822 with iolock:
1800 self.stream.writeln('')
1823 self.stream.writeln('')
1801
1824
1802 if not self._runner.options.noskips:
1825 if not self._runner.options.noskips:
1803 for test, msg in result.skipped:
1826 for test, msg in result.skipped:
1804 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1827 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1805 for test, msg in result.warned:
1828 for test, msg in result.warned:
1806 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1829 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1807 for test, msg in result.failures:
1830 for test, msg in result.failures:
1808 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1831 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1809 for test, msg in result.errors:
1832 for test, msg in result.errors:
1810 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1833 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1811
1834
1812 if self._runner.options.xunit:
1835 if self._runner.options.xunit:
1813 with open(self._runner.options.xunit, 'wb') as xuf:
1836 with open(self._runner.options.xunit, 'wb') as xuf:
1814 timesd = dict((t[0], t[3]) for t in result.times)
1837 timesd = dict((t[0], t[3]) for t in result.times)
1815 doc = minidom.Document()
1838 doc = minidom.Document()
1816 s = doc.createElement('testsuite')
1839 s = doc.createElement('testsuite')
1817 s.setAttribute('name', 'run-tests')
1840 s.setAttribute('name', 'run-tests')
1818 s.setAttribute('tests', str(result.testsRun))
1841 s.setAttribute('tests', str(result.testsRun))
1819 s.setAttribute('errors', "0") # TODO
1842 s.setAttribute('errors', "0") # TODO
1820 s.setAttribute('failures', str(failed))
1843 s.setAttribute('failures', str(failed))
1821 s.setAttribute('skipped', str(skipped + ignored))
1844 s.setAttribute('skipped', str(skipped + ignored))
1822 doc.appendChild(s)
1845 doc.appendChild(s)
1823 for tc in result.successes:
1846 for tc in result.successes:
1824 t = doc.createElement('testcase')
1847 t = doc.createElement('testcase')
1825 t.setAttribute('name', tc.name)
1848 t.setAttribute('name', tc.name)
1826 t.setAttribute('time', '%.3f' % timesd[tc.name])
1849 t.setAttribute('time', '%.3f' % timesd[tc.name])
1827 s.appendChild(t)
1850 s.appendChild(t)
1828 for tc, err in sorted(result.faildata.items()):
1851 for tc, err in sorted(result.faildata.items()):
1829 t = doc.createElement('testcase')
1852 t = doc.createElement('testcase')
1830 t.setAttribute('name', tc)
1853 t.setAttribute('name', tc)
1831 t.setAttribute('time', '%.3f' % timesd[tc])
1854 t.setAttribute('time', '%.3f' % timesd[tc])
1832 # createCDATASection expects a unicode or it will
1855 # createCDATASection expects a unicode or it will
1833 # convert using default conversion rules, which will
1856 # convert using default conversion rules, which will
1834 # fail if string isn't ASCII.
1857 # fail if string isn't ASCII.
1835 err = cdatasafe(err).decode('utf-8', 'replace')
1858 err = cdatasafe(err).decode('utf-8', 'replace')
1836 cd = doc.createCDATASection(err)
1859 cd = doc.createCDATASection(err)
1837 t.appendChild(cd)
1860 t.appendChild(cd)
1838 s.appendChild(t)
1861 s.appendChild(t)
1839 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1862 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1840
1863
1841 if self._runner.options.json:
1864 if self._runner.options.json:
1842 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1865 jsonpath = os.path.join(self._runner._testdir, b'report.json')
1843 with open(jsonpath, 'w') as fp:
1866 with open(jsonpath, 'w') as fp:
1844 timesd = {}
1867 timesd = {}
1845 for tdata in result.times:
1868 for tdata in result.times:
1846 test = tdata[0]
1869 test = tdata[0]
1847 timesd[test] = tdata[1:]
1870 timesd[test] = tdata[1:]
1848
1871
1849 outcome = {}
1872 outcome = {}
1850 groups = [('success', ((tc, None)
1873 groups = [('success', ((tc, None)
1851 for tc in result.successes)),
1874 for tc in result.successes)),
1852 ('failure', result.failures),
1875 ('failure', result.failures),
1853 ('skip', result.skipped)]
1876 ('skip', result.skipped)]
1854 for res, testcases in groups:
1877 for res, testcases in groups:
1855 for tc, __ in testcases:
1878 for tc, __ in testcases:
1856 if tc.name in timesd:
1879 if tc.name in timesd:
1857 diff = result.faildata.get(tc.name, b'')
1880 diff = result.faildata.get(tc.name, b'')
1858 tres = {'result': res,
1881 tres = {'result': res,
1859 'time': ('%0.3f' % timesd[tc.name][2]),
1882 'time': ('%0.3f' % timesd[tc.name][2]),
1860 'cuser': ('%0.3f' % timesd[tc.name][0]),
1883 'cuser': ('%0.3f' % timesd[tc.name][0]),
1861 'csys': ('%0.3f' % timesd[tc.name][1]),
1884 'csys': ('%0.3f' % timesd[tc.name][1]),
1862 'start': ('%0.3f' % timesd[tc.name][3]),
1885 'start': ('%0.3f' % timesd[tc.name][3]),
1863 'end': ('%0.3f' % timesd[tc.name][4]),
1886 'end': ('%0.3f' % timesd[tc.name][4]),
1864 'diff': diff.decode('unicode_escape'),
1887 'diff': diff.decode('unicode_escape'),
1865 }
1888 }
1866 else:
1889 else:
1867 # blacklisted test
1890 # blacklisted test
1868 tres = {'result': res}
1891 tres = {'result': res}
1869
1892
1870 outcome[tc.name] = tres
1893 outcome[tc.name] = tres
1871 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
1894 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
1872 separators=(',', ': '))
1895 separators=(',', ': '))
1873 fp.writelines(("testreport =", jsonout))
1896 fp.writelines(("testreport =", jsonout))
1874
1897
1875 self._runner._checkhglib('Tested')
1898 self._runner._checkhglib('Tested')
1876
1899
1877 savetimes(self._runner._testdir, result)
1900 savetimes(self._runner._testdir, result)
1878
1901
1879 if failed and self._runner.options.known_good_rev:
1902 if failed and self._runner.options.known_good_rev:
1880 def nooutput(args):
1903 def nooutput(args):
1881 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
1904 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
1882 stdout=subprocess.PIPE)
1905 stdout=subprocess.PIPE)
1883 p.stdout.read()
1906 p.stdout.read()
1884 p.wait()
1907 p.wait()
1885 for test, msg in result.failures:
1908 for test, msg in result.failures:
1886 nooutput(['hg', 'bisect', '--reset']),
1909 nooutput(['hg', 'bisect', '--reset']),
1887 nooutput(['hg', 'bisect', '--bad', '.'])
1910 nooutput(['hg', 'bisect', '--bad', '.'])
1888 nooutput(['hg', 'bisect', '--good',
1911 nooutput(['hg', 'bisect', '--good',
1889 self._runner.options.known_good_rev])
1912 self._runner.options.known_good_rev])
1890 # TODO: we probably need to forward some options
1913 # TODO: we probably need to forward some options
1891 # that alter hg's behavior inside the tests.
1914 # that alter hg's behavior inside the tests.
1892 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
1915 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
1893 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
1916 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
1894 stderr=subprocess.STDOUT,
1917 stderr=subprocess.STDOUT,
1895 stdout=subprocess.PIPE)
1918 stdout=subprocess.PIPE)
1896 data = sub.stdout.read()
1919 data = sub.stdout.read()
1897 sub.wait()
1920 sub.wait()
1898 m = re.search(
1921 m = re.search(
1899 (r'\nThe first (?P<goodbad>bad|good) revision '
1922 (r'\nThe first (?P<goodbad>bad|good) revision '
1900 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
1923 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
1901 r'summary: +(?P<summary>[^\n]+)\n'),
1924 r'summary: +(?P<summary>[^\n]+)\n'),
1902 data, (re.MULTILINE | re.DOTALL))
1925 data, (re.MULTILINE | re.DOTALL))
1903 if m is None:
1926 if m is None:
1904 self.stream.writeln(
1927 self.stream.writeln(
1905 'Failed to identify failure point for %s' % test)
1928 'Failed to identify failure point for %s' % test)
1906 continue
1929 continue
1907 dat = m.groupdict()
1930 dat = m.groupdict()
1908 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
1931 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
1909 self.stream.writeln(
1932 self.stream.writeln(
1910 '%s %s by %s (%s)' % (
1933 '%s %s by %s (%s)' % (
1911 test, verb, dat['node'], dat['summary']))
1934 test, verb, dat['node'], dat['summary']))
1912 self.stream.writeln(
1935 self.stream.writeln(
1913 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1936 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1914 % (result.testsRun,
1937 % (result.testsRun,
1915 skipped + ignored, warned, failed))
1938 skipped + ignored, warned, failed))
1916 if failed:
1939 if failed:
1917 self.stream.writeln('python hash seed: %s' %
1940 self.stream.writeln('python hash seed: %s' %
1918 os.environ['PYTHONHASHSEED'])
1941 os.environ['PYTHONHASHSEED'])
1919 if self._runner.options.time:
1942 if self._runner.options.time:
1920 self.printtimes(result.times)
1943 self.printtimes(result.times)
1921
1944
1922 return result
1945 return result
1923
1946
1924 def printtimes(self, times):
1947 def printtimes(self, times):
1925 # iolock held by run
1948 # iolock held by run
1926 self.stream.writeln('# Producing time report')
1949 self.stream.writeln('# Producing time report')
1927 times.sort(key=lambda t: (t[3]))
1950 times.sort(key=lambda t: (t[3]))
1928 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1951 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1929 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1952 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1930 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1953 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1931 for tdata in times:
1954 for tdata in times:
1932 test = tdata[0]
1955 test = tdata[0]
1933 cuser, csys, real, start, end = tdata[1:6]
1956 cuser, csys, real, start, end = tdata[1:6]
1934 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1957 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1935
1958
1936 class TestRunner(object):
1959 class TestRunner(object):
1937 """Holds context for executing tests.
1960 """Holds context for executing tests.
1938
1961
1939 Tests rely on a lot of state. This object holds it for them.
1962 Tests rely on a lot of state. This object holds it for them.
1940 """
1963 """
1941
1964
1942 # Programs required to run tests.
1965 # Programs required to run tests.
1943 REQUIREDTOOLS = [
1966 REQUIREDTOOLS = [
1944 os.path.basename(_bytespath(sys.executable)),
1967 os.path.basename(_bytespath(sys.executable)),
1945 b'diff',
1968 b'diff',
1946 b'grep',
1969 b'grep',
1947 b'unzip',
1970 b'unzip',
1948 b'gunzip',
1971 b'gunzip',
1949 b'bunzip2',
1972 b'bunzip2',
1950 b'sed',
1973 b'sed',
1951 ]
1974 ]
1952
1975
1953 # Maps file extensions to test class.
1976 # Maps file extensions to test class.
1954 TESTTYPES = [
1977 TESTTYPES = [
1955 (b'.py', PythonTest),
1978 (b'.py', PythonTest),
1956 (b'.t', TTest),
1979 (b'.t', TTest),
1957 ]
1980 ]
1958
1981
1959 def __init__(self):
1982 def __init__(self):
1960 self.options = None
1983 self.options = None
1961 self._hgroot = None
1984 self._hgroot = None
1962 self._testdir = None
1985 self._testdir = None
1963 self._hgtmp = None
1986 self._hgtmp = None
1964 self._installdir = None
1987 self._installdir = None
1965 self._bindir = None
1988 self._bindir = None
1966 self._tmpbinddir = None
1989 self._tmpbinddir = None
1967 self._pythondir = None
1990 self._pythondir = None
1968 self._coveragefile = None
1991 self._coveragefile = None
1969 self._createdfiles = []
1992 self._createdfiles = []
1970 self._hgcommand = None
1993 self._hgcommand = None
1971 self._hgpath = None
1994 self._hgpath = None
1972 self._portoffset = 0
1995 self._portoffset = 0
1973 self._ports = {}
1996 self._ports = {}
1974
1997
1975 def run(self, args, parser=None):
1998 def run(self, args, parser=None):
1976 """Run the test suite."""
1999 """Run the test suite."""
1977 oldmask = os.umask(0o22)
2000 oldmask = os.umask(0o22)
1978 try:
2001 try:
1979 parser = parser or getparser()
2002 parser = parser or getparser()
1980 options, args = parseargs(args, parser)
2003 options, args = parseargs(args, parser)
1981 # positional arguments are paths to test files to run, so
2004 # positional arguments are paths to test files to run, so
1982 # we make sure they're all bytestrings
2005 # we make sure they're all bytestrings
1983 args = [_bytespath(a) for a in args]
2006 args = [_bytespath(a) for a in args]
1984 self.options = options
2007 self.options = options
1985
2008
1986 self._checktools()
2009 self._checktools()
1987 tests = self.findtests(args)
2010 tests = self.findtests(args)
1988 if options.profile_runner:
2011 if options.profile_runner:
1989 import statprof
2012 import statprof
1990 statprof.start()
2013 statprof.start()
1991 result = self._run(tests)
2014 result = self._run(tests)
1992 if options.profile_runner:
2015 if options.profile_runner:
1993 statprof.stop()
2016 statprof.stop()
1994 statprof.display()
2017 statprof.display()
1995 return result
2018 return result
1996
2019
1997 finally:
2020 finally:
1998 os.umask(oldmask)
2021 os.umask(oldmask)
1999
2022
2000 def _run(self, tests):
2023 def _run(self, tests):
2001 if self.options.random:
2024 if self.options.random:
2002 random.shuffle(tests)
2025 random.shuffle(tests)
2003 else:
2026 else:
2004 # keywords for slow tests
2027 # keywords for slow tests
2005 slow = {b'svn': 10,
2028 slow = {b'svn': 10,
2006 b'cvs': 10,
2029 b'cvs': 10,
2007 b'hghave': 10,
2030 b'hghave': 10,
2008 b'largefiles-update': 10,
2031 b'largefiles-update': 10,
2009 b'run-tests': 10,
2032 b'run-tests': 10,
2010 b'corruption': 10,
2033 b'corruption': 10,
2011 b'race': 10,
2034 b'race': 10,
2012 b'i18n': 10,
2035 b'i18n': 10,
2013 b'check': 100,
2036 b'check': 100,
2014 b'gendoc': 100,
2037 b'gendoc': 100,
2015 b'contrib-perf': 200,
2038 b'contrib-perf': 200,
2016 }
2039 }
2017 perf = {}
2040 perf = {}
2018 def sortkey(f):
2041 def sortkey(f):
2019 # run largest tests first, as they tend to take the longest
2042 # run largest tests first, as they tend to take the longest
2020 try:
2043 try:
2021 return perf[f]
2044 return perf[f]
2022 except KeyError:
2045 except KeyError:
2023 try:
2046 try:
2024 val = -os.stat(f).st_size
2047 val = -os.stat(f).st_size
2025 except OSError as e:
2048 except OSError as e:
2026 if e.errno != errno.ENOENT:
2049 if e.errno != errno.ENOENT:
2027 raise
2050 raise
2028 perf[f] = -1e9 # file does not exist, tell early
2051 perf[f] = -1e9 # file does not exist, tell early
2029 return -1e9
2052 return -1e9
2030 for kw, mul in slow.items():
2053 for kw, mul in slow.items():
2031 if kw in f:
2054 if kw in f:
2032 val *= mul
2055 val *= mul
2033 if f.endswith(b'.py'):
2056 if f.endswith(b'.py'):
2034 val /= 10.0
2057 val /= 10.0
2035 perf[f] = val / 1000.0
2058 perf[f] = val / 1000.0
2036 return perf[f]
2059 return perf[f]
2037 tests.sort(key=sortkey)
2060 tests.sort(key=sortkey)
2038
2061
2039 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2062 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2040 os, 'getcwdb', os.getcwd)()
2063 os, 'getcwdb', os.getcwd)()
2041
2064
2042 if 'PYTHONHASHSEED' not in os.environ:
2065 if 'PYTHONHASHSEED' not in os.environ:
2043 # use a random python hash seed all the time
2066 # use a random python hash seed all the time
2044 # we do the randomness ourself to know what seed is used
2067 # we do the randomness ourself to know what seed is used
2045 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2068 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2046
2069
2047 if self.options.tmpdir:
2070 if self.options.tmpdir:
2048 self.options.keep_tmpdir = True
2071 self.options.keep_tmpdir = True
2049 tmpdir = _bytespath(self.options.tmpdir)
2072 tmpdir = _bytespath(self.options.tmpdir)
2050 if os.path.exists(tmpdir):
2073 if os.path.exists(tmpdir):
2051 # Meaning of tmpdir has changed since 1.3: we used to create
2074 # Meaning of tmpdir has changed since 1.3: we used to create
2052 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2075 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2053 # tmpdir already exists.
2076 # tmpdir already exists.
2054 print("error: temp dir %r already exists" % tmpdir)
2077 print("error: temp dir %r already exists" % tmpdir)
2055 return 1
2078 return 1
2056
2079
2057 # Automatically removing tmpdir sounds convenient, but could
2080 # Automatically removing tmpdir sounds convenient, but could
2058 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2081 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2059 # or "--tmpdir=$HOME".
2082 # or "--tmpdir=$HOME".
2060 #vlog("# Removing temp dir", tmpdir)
2083 #vlog("# Removing temp dir", tmpdir)
2061 #shutil.rmtree(tmpdir)
2084 #shutil.rmtree(tmpdir)
2062 os.makedirs(tmpdir)
2085 os.makedirs(tmpdir)
2063 else:
2086 else:
2064 d = None
2087 d = None
2065 if os.name == 'nt':
2088 if os.name == 'nt':
2066 # without this, we get the default temp dir location, but
2089 # without this, we get the default temp dir location, but
2067 # in all lowercase, which causes troubles with paths (issue3490)
2090 # in all lowercase, which causes troubles with paths (issue3490)
2068 d = osenvironb.get(b'TMP', None)
2091 d = osenvironb.get(b'TMP', None)
2069 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2092 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2070
2093
2071 self._hgtmp = osenvironb[b'HGTMP'] = (
2094 self._hgtmp = osenvironb[b'HGTMP'] = (
2072 os.path.realpath(tmpdir))
2095 os.path.realpath(tmpdir))
2073
2096
2074 if self.options.with_hg:
2097 if self.options.with_hg:
2075 self._installdir = None
2098 self._installdir = None
2076 whg = self.options.with_hg
2099 whg = self.options.with_hg
2077 self._bindir = os.path.dirname(os.path.realpath(whg))
2100 self._bindir = os.path.dirname(os.path.realpath(whg))
2078 assert isinstance(self._bindir, bytes)
2101 assert isinstance(self._bindir, bytes)
2079 self._hgcommand = os.path.basename(whg)
2102 self._hgcommand = os.path.basename(whg)
2080 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2103 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2081 os.makedirs(self._tmpbindir)
2104 os.makedirs(self._tmpbindir)
2082
2105
2083 # This looks redundant with how Python initializes sys.path from
2106 # This looks redundant with how Python initializes sys.path from
2084 # the location of the script being executed. Needed because the
2107 # the location of the script being executed. Needed because the
2085 # "hg" specified by --with-hg is not the only Python script
2108 # "hg" specified by --with-hg is not the only Python script
2086 # executed in the test suite that needs to import 'mercurial'
2109 # executed in the test suite that needs to import 'mercurial'
2087 # ... which means it's not really redundant at all.
2110 # ... which means it's not really redundant at all.
2088 self._pythondir = self._bindir
2111 self._pythondir = self._bindir
2089 else:
2112 else:
2090 self._installdir = os.path.join(self._hgtmp, b"install")
2113 self._installdir = os.path.join(self._hgtmp, b"install")
2091 self._bindir = os.path.join(self._installdir, b"bin")
2114 self._bindir = os.path.join(self._installdir, b"bin")
2092 self._hgcommand = b'hg'
2115 self._hgcommand = b'hg'
2093 self._tmpbindir = self._bindir
2116 self._tmpbindir = self._bindir
2094 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2117 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2095
2118
2096 # set CHGHG, then replace "hg" command by "chg"
2119 # set CHGHG, then replace "hg" command by "chg"
2097 chgbindir = self._bindir
2120 chgbindir = self._bindir
2098 if self.options.chg or self.options.with_chg:
2121 if self.options.chg or self.options.with_chg:
2099 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2122 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2100 else:
2123 else:
2101 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2124 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2102 if self.options.chg:
2125 if self.options.chg:
2103 self._hgcommand = b'chg'
2126 self._hgcommand = b'chg'
2104 elif self.options.with_chg:
2127 elif self.options.with_chg:
2105 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2128 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2106 self._hgcommand = os.path.basename(self.options.with_chg)
2129 self._hgcommand = os.path.basename(self.options.with_chg)
2107
2130
2108 osenvironb[b"BINDIR"] = self._bindir
2131 osenvironb[b"BINDIR"] = self._bindir
2109 osenvironb[b"PYTHON"] = PYTHON
2132 osenvironb[b"PYTHON"] = PYTHON
2110
2133
2111 if self.options.with_python3:
2134 if self.options.with_python3:
2112 osenvironb[b'PYTHON3'] = self.options.with_python3
2135 osenvironb[b'PYTHON3'] = self.options.with_python3
2113
2136
2114 fileb = _bytespath(__file__)
2137 fileb = _bytespath(__file__)
2115 runtestdir = os.path.abspath(os.path.dirname(fileb))
2138 runtestdir = os.path.abspath(os.path.dirname(fileb))
2116 osenvironb[b'RUNTESTDIR'] = runtestdir
2139 osenvironb[b'RUNTESTDIR'] = runtestdir
2117 if PYTHON3:
2140 if PYTHON3:
2118 sepb = _bytespath(os.pathsep)
2141 sepb = _bytespath(os.pathsep)
2119 else:
2142 else:
2120 sepb = os.pathsep
2143 sepb = os.pathsep
2121 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2144 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2122 if os.path.islink(__file__):
2145 if os.path.islink(__file__):
2123 # test helper will likely be at the end of the symlink
2146 # test helper will likely be at the end of the symlink
2124 realfile = os.path.realpath(fileb)
2147 realfile = os.path.realpath(fileb)
2125 realdir = os.path.abspath(os.path.dirname(realfile))
2148 realdir = os.path.abspath(os.path.dirname(realfile))
2126 path.insert(2, realdir)
2149 path.insert(2, realdir)
2127 if chgbindir != self._bindir:
2150 if chgbindir != self._bindir:
2128 path.insert(1, chgbindir)
2151 path.insert(1, chgbindir)
2129 if self._testdir != runtestdir:
2152 if self._testdir != runtestdir:
2130 path = [self._testdir] + path
2153 path = [self._testdir] + path
2131 if self._tmpbindir != self._bindir:
2154 if self._tmpbindir != self._bindir:
2132 path = [self._tmpbindir] + path
2155 path = [self._tmpbindir] + path
2133 osenvironb[b"PATH"] = sepb.join(path)
2156 osenvironb[b"PATH"] = sepb.join(path)
2134
2157
2135 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2158 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2136 # can run .../tests/run-tests.py test-foo where test-foo
2159 # can run .../tests/run-tests.py test-foo where test-foo
2137 # adds an extension to HGRC. Also include run-test.py directory to
2160 # adds an extension to HGRC. Also include run-test.py directory to
2138 # import modules like heredoctest.
2161 # import modules like heredoctest.
2139 pypath = [self._pythondir, self._testdir, runtestdir]
2162 pypath = [self._pythondir, self._testdir, runtestdir]
2140 # We have to augment PYTHONPATH, rather than simply replacing
2163 # We have to augment PYTHONPATH, rather than simply replacing
2141 # it, in case external libraries are only available via current
2164 # it, in case external libraries are only available via current
2142 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2165 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2143 # are in /opt/subversion.)
2166 # are in /opt/subversion.)
2144 oldpypath = osenvironb.get(IMPL_PATH)
2167 oldpypath = osenvironb.get(IMPL_PATH)
2145 if oldpypath:
2168 if oldpypath:
2146 pypath.append(oldpypath)
2169 pypath.append(oldpypath)
2147 osenvironb[IMPL_PATH] = sepb.join(pypath)
2170 osenvironb[IMPL_PATH] = sepb.join(pypath)
2148
2171
2149 if self.options.pure:
2172 if self.options.pure:
2150 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2173 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2151 os.environ["HGMODULEPOLICY"] = "py"
2174 os.environ["HGMODULEPOLICY"] = "py"
2152
2175
2153 if self.options.allow_slow_tests:
2176 if self.options.allow_slow_tests:
2154 os.environ["HGTEST_SLOW"] = "slow"
2177 os.environ["HGTEST_SLOW"] = "slow"
2155 elif 'HGTEST_SLOW' in os.environ:
2178 elif 'HGTEST_SLOW' in os.environ:
2156 del os.environ['HGTEST_SLOW']
2179 del os.environ['HGTEST_SLOW']
2157
2180
2158 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2181 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2159
2182
2160 vlog("# Using TESTDIR", self._testdir)
2183 vlog("# Using TESTDIR", self._testdir)
2161 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2184 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2162 vlog("# Using HGTMP", self._hgtmp)
2185 vlog("# Using HGTMP", self._hgtmp)
2163 vlog("# Using PATH", os.environ["PATH"])
2186 vlog("# Using PATH", os.environ["PATH"])
2164 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2187 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2165
2188
2166 try:
2189 try:
2167 return self._runtests(tests) or 0
2190 return self._runtests(tests) or 0
2168 finally:
2191 finally:
2169 time.sleep(.1)
2192 time.sleep(.1)
2170 self._cleanup()
2193 self._cleanup()
2171
2194
2172 def findtests(self, args):
2195 def findtests(self, args):
2173 """Finds possible test files from arguments.
2196 """Finds possible test files from arguments.
2174
2197
2175 If you wish to inject custom tests into the test harness, this would
2198 If you wish to inject custom tests into the test harness, this would
2176 be a good function to monkeypatch or override in a derived class.
2199 be a good function to monkeypatch or override in a derived class.
2177 """
2200 """
2178 if not args:
2201 if not args:
2179 if self.options.changed:
2202 if self.options.changed:
2180 proc = Popen4('hg st --rev "%s" -man0 .' %
2203 proc = Popen4('hg st --rev "%s" -man0 .' %
2181 self.options.changed, None, 0)
2204 self.options.changed, None, 0)
2182 stdout, stderr = proc.communicate()
2205 stdout, stderr = proc.communicate()
2183 args = stdout.strip(b'\0').split(b'\0')
2206 args = stdout.strip(b'\0').split(b'\0')
2184 else:
2207 else:
2185 args = os.listdir(b'.')
2208 args = os.listdir(b'.')
2186
2209
2187 return [t for t in args
2210 return [t for t in args
2188 if os.path.basename(t).startswith(b'test-')
2211 if os.path.basename(t).startswith(b'test-')
2189 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2212 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2190
2213
2191 def _runtests(self, tests):
2214 def _runtests(self, tests):
2192 try:
2215 try:
2193 if self._installdir:
2216 if self._installdir:
2194 self._installhg()
2217 self._installhg()
2195 self._checkhglib("Testing")
2218 self._checkhglib("Testing")
2196 else:
2219 else:
2197 self._usecorrectpython()
2220 self._usecorrectpython()
2198 if self.options.chg:
2221 if self.options.chg:
2199 assert self._installdir
2222 assert self._installdir
2200 self._installchg()
2223 self._installchg()
2201
2224
2202 if self.options.restart:
2225 if self.options.restart:
2203 orig = list(tests)
2226 orig = list(tests)
2204 while tests:
2227 while tests:
2205 if os.path.exists(tests[0] + ".err"):
2228 if os.path.exists(tests[0] + ".err"):
2206 break
2229 break
2207 tests.pop(0)
2230 tests.pop(0)
2208 if not tests:
2231 if not tests:
2209 print("running all tests")
2232 print("running all tests")
2210 tests = orig
2233 tests = orig
2211
2234
2212 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2235 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2213
2236
2214 failed = False
2237 failed = False
2215 warned = False
2238 warned = False
2216 kws = self.options.keywords
2239 kws = self.options.keywords
2217 if kws is not None and PYTHON3:
2240 if kws is not None and PYTHON3:
2218 kws = kws.encode('utf-8')
2241 kws = kws.encode('utf-8')
2219
2242
2220 suite = TestSuite(self._testdir,
2243 suite = TestSuite(self._testdir,
2221 jobs=self.options.jobs,
2244 jobs=self.options.jobs,
2222 whitelist=self.options.whitelisted,
2245 whitelist=self.options.whitelisted,
2223 blacklist=self.options.blacklist,
2246 blacklist=self.options.blacklist,
2224 retest=self.options.retest,
2247 retest=self.options.retest,
2225 keywords=kws,
2248 keywords=kws,
2226 loop=self.options.loop,
2249 loop=self.options.loop,
2227 runs_per_test=self.options.runs_per_test,
2250 runs_per_test=self.options.runs_per_test,
2228 showchannels=self.options.showchannels,
2251 showchannels=self.options.showchannels,
2229 tests=tests, loadtest=self._gettest)
2252 tests=tests, loadtest=self._gettest)
2230 verbosity = 1
2253 verbosity = 1
2231 if self.options.verbose:
2254 if self.options.verbose:
2232 verbosity = 2
2255 verbosity = 2
2233 runner = TextTestRunner(self, verbosity=verbosity)
2256 runner = TextTestRunner(self, verbosity=verbosity)
2234 result = runner.run(suite)
2257 result = runner.run(suite)
2235
2258
2236 if result.failures:
2259 if result.failures:
2237 failed = True
2260 failed = True
2238 if result.warned:
2261 if result.warned:
2239 warned = True
2262 warned = True
2240
2263
2241 if self.options.anycoverage:
2264 if self.options.anycoverage:
2242 self._outputcoverage()
2265 self._outputcoverage()
2243 except KeyboardInterrupt:
2266 except KeyboardInterrupt:
2244 failed = True
2267 failed = True
2245 print("\ninterrupted!")
2268 print("\ninterrupted!")
2246
2269
2247 if failed:
2270 if failed:
2248 return 1
2271 return 1
2249 if warned:
2272 if warned:
2250 return 80
2273 return 80
2251
2274
2252 def _getport(self, count):
2275 def _getport(self, count):
2253 port = self._ports.get(count) # do we have a cached entry?
2276 port = self._ports.get(count) # do we have a cached entry?
2254 if port is None:
2277 if port is None:
2255 portneeded = 3
2278 portneeded = 3
2256 # above 100 tries we just give up and let test reports failure
2279 # above 100 tries we just give up and let test reports failure
2257 for tries in xrange(100):
2280 for tries in xrange(100):
2258 allfree = True
2281 allfree = True
2259 port = self.options.port + self._portoffset
2282 port = self.options.port + self._portoffset
2260 for idx in xrange(portneeded):
2283 for idx in xrange(portneeded):
2261 if not checkportisavailable(port + idx):
2284 if not checkportisavailable(port + idx):
2262 allfree = False
2285 allfree = False
2263 break
2286 break
2264 self._portoffset += portneeded
2287 self._portoffset += portneeded
2265 if allfree:
2288 if allfree:
2266 break
2289 break
2267 self._ports[count] = port
2290 self._ports[count] = port
2268 return port
2291 return port
2269
2292
2270 def _gettest(self, test, count):
2293 def _gettest(self, test, count):
2271 """Obtain a Test by looking at its filename.
2294 """Obtain a Test by looking at its filename.
2272
2295
2273 Returns a Test instance. The Test may not be runnable if it doesn't
2296 Returns a Test instance. The Test may not be runnable if it doesn't
2274 map to a known type.
2297 map to a known type.
2275 """
2298 """
2276 lctest = test.lower()
2299 lctest = test.lower()
2277 testcls = Test
2300 testcls = Test
2278
2301
2279 for ext, cls in self.TESTTYPES:
2302 for ext, cls in self.TESTTYPES:
2280 if lctest.endswith(ext):
2303 if lctest.endswith(ext):
2281 testcls = cls
2304 testcls = cls
2282 break
2305 break
2283
2306
2284 refpath = os.path.join(self._testdir, test)
2307 refpath = os.path.join(self._testdir, test)
2285 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2308 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2286
2309
2287 t = testcls(refpath, tmpdir,
2310 t = testcls(refpath, tmpdir,
2288 keeptmpdir=self.options.keep_tmpdir,
2311 keeptmpdir=self.options.keep_tmpdir,
2289 debug=self.options.debug,
2312 debug=self.options.debug,
2290 timeout=self.options.timeout,
2313 timeout=self.options.timeout,
2291 startport=self._getport(count),
2314 startport=self._getport(count),
2292 extraconfigopts=self.options.extra_config_opt,
2315 extraconfigopts=self.options.extra_config_opt,
2293 py3kwarnings=self.options.py3k_warnings,
2316 py3kwarnings=self.options.py3k_warnings,
2294 shell=self.options.shell,
2317 shell=self.options.shell,
2295 hgcommand=self._hgcommand,
2318 hgcommand=self._hgcommand,
2296 usechg=bool(self.options.with_chg or self.options.chg))
2319 usechg=bool(self.options.with_chg or self.options.chg))
2297 t.should_reload = True
2320 t.should_reload = True
2298 return t
2321 return t
2299
2322
2300 def _cleanup(self):
2323 def _cleanup(self):
2301 """Clean up state from this test invocation."""
2324 """Clean up state from this test invocation."""
2302 if self.options.keep_tmpdir:
2325 if self.options.keep_tmpdir:
2303 return
2326 return
2304
2327
2305 vlog("# Cleaning up HGTMP", self._hgtmp)
2328 vlog("# Cleaning up HGTMP", self._hgtmp)
2306 shutil.rmtree(self._hgtmp, True)
2329 shutil.rmtree(self._hgtmp, True)
2307 for f in self._createdfiles:
2330 for f in self._createdfiles:
2308 try:
2331 try:
2309 os.remove(f)
2332 os.remove(f)
2310 except OSError:
2333 except OSError:
2311 pass
2334 pass
2312
2335
2313 def _usecorrectpython(self):
2336 def _usecorrectpython(self):
2314 """Configure the environment to use the appropriate Python in tests."""
2337 """Configure the environment to use the appropriate Python in tests."""
2315 # Tests must use the same interpreter as us or bad things will happen.
2338 # Tests must use the same interpreter as us or bad things will happen.
2316 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2339 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2317 if getattr(os, 'symlink', None):
2340 if getattr(os, 'symlink', None):
2318 vlog("# Making python executable in test path a symlink to '%s'" %
2341 vlog("# Making python executable in test path a symlink to '%s'" %
2319 sys.executable)
2342 sys.executable)
2320 mypython = os.path.join(self._tmpbindir, pyexename)
2343 mypython = os.path.join(self._tmpbindir, pyexename)
2321 try:
2344 try:
2322 if os.readlink(mypython) == sys.executable:
2345 if os.readlink(mypython) == sys.executable:
2323 return
2346 return
2324 os.unlink(mypython)
2347 os.unlink(mypython)
2325 except OSError as err:
2348 except OSError as err:
2326 if err.errno != errno.ENOENT:
2349 if err.errno != errno.ENOENT:
2327 raise
2350 raise
2328 if self._findprogram(pyexename) != sys.executable:
2351 if self._findprogram(pyexename) != sys.executable:
2329 try:
2352 try:
2330 os.symlink(sys.executable, mypython)
2353 os.symlink(sys.executable, mypython)
2331 self._createdfiles.append(mypython)
2354 self._createdfiles.append(mypython)
2332 except OSError as err:
2355 except OSError as err:
2333 # child processes may race, which is harmless
2356 # child processes may race, which is harmless
2334 if err.errno != errno.EEXIST:
2357 if err.errno != errno.EEXIST:
2335 raise
2358 raise
2336 else:
2359 else:
2337 exedir, exename = os.path.split(sys.executable)
2360 exedir, exename = os.path.split(sys.executable)
2338 vlog("# Modifying search path to find %s as %s in '%s'" %
2361 vlog("# Modifying search path to find %s as %s in '%s'" %
2339 (exename, pyexename, exedir))
2362 (exename, pyexename, exedir))
2340 path = os.environ['PATH'].split(os.pathsep)
2363 path = os.environ['PATH'].split(os.pathsep)
2341 while exedir in path:
2364 while exedir in path:
2342 path.remove(exedir)
2365 path.remove(exedir)
2343 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2366 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2344 if not self._findprogram(pyexename):
2367 if not self._findprogram(pyexename):
2345 print("WARNING: Cannot find %s in search path" % pyexename)
2368 print("WARNING: Cannot find %s in search path" % pyexename)
2346
2369
2347 def _installhg(self):
2370 def _installhg(self):
2348 """Install hg into the test environment.
2371 """Install hg into the test environment.
2349
2372
2350 This will also configure hg with the appropriate testing settings.
2373 This will also configure hg with the appropriate testing settings.
2351 """
2374 """
2352 vlog("# Performing temporary installation of HG")
2375 vlog("# Performing temporary installation of HG")
2353 installerrs = os.path.join(self._hgtmp, b"install.err")
2376 installerrs = os.path.join(self._hgtmp, b"install.err")
2354 compiler = ''
2377 compiler = ''
2355 if self.options.compiler:
2378 if self.options.compiler:
2356 compiler = '--compiler ' + self.options.compiler
2379 compiler = '--compiler ' + self.options.compiler
2357 if self.options.pure:
2380 if self.options.pure:
2358 pure = b"--pure"
2381 pure = b"--pure"
2359 else:
2382 else:
2360 pure = b""
2383 pure = b""
2361
2384
2362 # Run installer in hg root
2385 # Run installer in hg root
2363 script = os.path.realpath(sys.argv[0])
2386 script = os.path.realpath(sys.argv[0])
2364 exe = sys.executable
2387 exe = sys.executable
2365 if PYTHON3:
2388 if PYTHON3:
2366 compiler = _bytespath(compiler)
2389 compiler = _bytespath(compiler)
2367 script = _bytespath(script)
2390 script = _bytespath(script)
2368 exe = _bytespath(exe)
2391 exe = _bytespath(exe)
2369 hgroot = os.path.dirname(os.path.dirname(script))
2392 hgroot = os.path.dirname(os.path.dirname(script))
2370 self._hgroot = hgroot
2393 self._hgroot = hgroot
2371 os.chdir(hgroot)
2394 os.chdir(hgroot)
2372 nohome = b'--home=""'
2395 nohome = b'--home=""'
2373 if os.name == 'nt':
2396 if os.name == 'nt':
2374 # The --home="" trick works only on OS where os.sep == '/'
2397 # The --home="" trick works only on OS where os.sep == '/'
2375 # because of a distutils convert_path() fast-path. Avoid it at
2398 # because of a distutils convert_path() fast-path. Avoid it at
2376 # least on Windows for now, deal with .pydistutils.cfg bugs
2399 # least on Windows for now, deal with .pydistutils.cfg bugs
2377 # when they happen.
2400 # when they happen.
2378 nohome = b''
2401 nohome = b''
2379 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2402 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2380 b' build %(compiler)s --build-base="%(base)s"'
2403 b' build %(compiler)s --build-base="%(base)s"'
2381 b' install --force --prefix="%(prefix)s"'
2404 b' install --force --prefix="%(prefix)s"'
2382 b' --install-lib="%(libdir)s"'
2405 b' --install-lib="%(libdir)s"'
2383 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2406 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2384 % {b'exe': exe, b'pure': pure,
2407 % {b'exe': exe, b'pure': pure,
2385 b'compiler': compiler,
2408 b'compiler': compiler,
2386 b'base': os.path.join(self._hgtmp, b"build"),
2409 b'base': os.path.join(self._hgtmp, b"build"),
2387 b'prefix': self._installdir, b'libdir': self._pythondir,
2410 b'prefix': self._installdir, b'libdir': self._pythondir,
2388 b'bindir': self._bindir,
2411 b'bindir': self._bindir,
2389 b'nohome': nohome, b'logfile': installerrs})
2412 b'nohome': nohome, b'logfile': installerrs})
2390
2413
2391 # setuptools requires install directories to exist.
2414 # setuptools requires install directories to exist.
2392 def makedirs(p):
2415 def makedirs(p):
2393 try:
2416 try:
2394 os.makedirs(p)
2417 os.makedirs(p)
2395 except OSError as e:
2418 except OSError as e:
2396 if e.errno != errno.EEXIST:
2419 if e.errno != errno.EEXIST:
2397 raise
2420 raise
2398 makedirs(self._pythondir)
2421 makedirs(self._pythondir)
2399 makedirs(self._bindir)
2422 makedirs(self._bindir)
2400
2423
2401 vlog("# Running", cmd)
2424 vlog("# Running", cmd)
2402 if os.system(cmd) == 0:
2425 if os.system(cmd) == 0:
2403 if not self.options.verbose:
2426 if not self.options.verbose:
2404 try:
2427 try:
2405 os.remove(installerrs)
2428 os.remove(installerrs)
2406 except OSError as e:
2429 except OSError as e:
2407 if e.errno != errno.ENOENT:
2430 if e.errno != errno.ENOENT:
2408 raise
2431 raise
2409 else:
2432 else:
2410 f = open(installerrs, 'rb')
2433 f = open(installerrs, 'rb')
2411 for line in f:
2434 for line in f:
2412 if PYTHON3:
2435 if PYTHON3:
2413 sys.stdout.buffer.write(line)
2436 sys.stdout.buffer.write(line)
2414 else:
2437 else:
2415 sys.stdout.write(line)
2438 sys.stdout.write(line)
2416 f.close()
2439 f.close()
2417 sys.exit(1)
2440 sys.exit(1)
2418 os.chdir(self._testdir)
2441 os.chdir(self._testdir)
2419
2442
2420 self._usecorrectpython()
2443 self._usecorrectpython()
2421
2444
2422 if self.options.py3k_warnings and not self.options.anycoverage:
2445 if self.options.py3k_warnings and not self.options.anycoverage:
2423 vlog("# Updating hg command to enable Py3k Warnings switch")
2446 vlog("# Updating hg command to enable Py3k Warnings switch")
2424 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2447 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2425 lines = [line.rstrip() for line in f]
2448 lines = [line.rstrip() for line in f]
2426 lines[0] += ' -3'
2449 lines[0] += ' -3'
2427 f.close()
2450 f.close()
2428 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2451 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2429 for line in lines:
2452 for line in lines:
2430 f.write(line + '\n')
2453 f.write(line + '\n')
2431 f.close()
2454 f.close()
2432
2455
2433 hgbat = os.path.join(self._bindir, b'hg.bat')
2456 hgbat = os.path.join(self._bindir, b'hg.bat')
2434 if os.path.isfile(hgbat):
2457 if os.path.isfile(hgbat):
2435 # hg.bat expects to be put in bin/scripts while run-tests.py
2458 # hg.bat expects to be put in bin/scripts while run-tests.py
2436 # installation layout put it in bin/ directly. Fix it
2459 # installation layout put it in bin/ directly. Fix it
2437 f = open(hgbat, 'rb')
2460 f = open(hgbat, 'rb')
2438 data = f.read()
2461 data = f.read()
2439 f.close()
2462 f.close()
2440 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2463 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2441 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2464 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2442 b'"%~dp0python" "%~dp0hg" %*')
2465 b'"%~dp0python" "%~dp0hg" %*')
2443 f = open(hgbat, 'wb')
2466 f = open(hgbat, 'wb')
2444 f.write(data)
2467 f.write(data)
2445 f.close()
2468 f.close()
2446 else:
2469 else:
2447 print('WARNING: cannot fix hg.bat reference to python.exe')
2470 print('WARNING: cannot fix hg.bat reference to python.exe')
2448
2471
2449 if self.options.anycoverage:
2472 if self.options.anycoverage:
2450 custom = os.path.join(self._testdir, 'sitecustomize.py')
2473 custom = os.path.join(self._testdir, 'sitecustomize.py')
2451 target = os.path.join(self._pythondir, 'sitecustomize.py')
2474 target = os.path.join(self._pythondir, 'sitecustomize.py')
2452 vlog('# Installing coverage trigger to %s' % target)
2475 vlog('# Installing coverage trigger to %s' % target)
2453 shutil.copyfile(custom, target)
2476 shutil.copyfile(custom, target)
2454 rc = os.path.join(self._testdir, '.coveragerc')
2477 rc = os.path.join(self._testdir, '.coveragerc')
2455 vlog('# Installing coverage rc to %s' % rc)
2478 vlog('# Installing coverage rc to %s' % rc)
2456 os.environ['COVERAGE_PROCESS_START'] = rc
2479 os.environ['COVERAGE_PROCESS_START'] = rc
2457 covdir = os.path.join(self._installdir, '..', 'coverage')
2480 covdir = os.path.join(self._installdir, '..', 'coverage')
2458 try:
2481 try:
2459 os.mkdir(covdir)
2482 os.mkdir(covdir)
2460 except OSError as e:
2483 except OSError as e:
2461 if e.errno != errno.EEXIST:
2484 if e.errno != errno.EEXIST:
2462 raise
2485 raise
2463
2486
2464 os.environ['COVERAGE_DIR'] = covdir
2487 os.environ['COVERAGE_DIR'] = covdir
2465
2488
2466 def _checkhglib(self, verb):
2489 def _checkhglib(self, verb):
2467 """Ensure that the 'mercurial' package imported by python is
2490 """Ensure that the 'mercurial' package imported by python is
2468 the one we expect it to be. If not, print a warning to stderr."""
2491 the one we expect it to be. If not, print a warning to stderr."""
2469 if ((self._bindir == self._pythondir) and
2492 if ((self._bindir == self._pythondir) and
2470 (self._bindir != self._tmpbindir)):
2493 (self._bindir != self._tmpbindir)):
2471 # The pythondir has been inferred from --with-hg flag.
2494 # The pythondir has been inferred from --with-hg flag.
2472 # We cannot expect anything sensible here.
2495 # We cannot expect anything sensible here.
2473 return
2496 return
2474 expecthg = os.path.join(self._pythondir, b'mercurial')
2497 expecthg = os.path.join(self._pythondir, b'mercurial')
2475 actualhg = self._gethgpath()
2498 actualhg = self._gethgpath()
2476 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2499 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2477 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2500 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2478 ' (expected %s)\n'
2501 ' (expected %s)\n'
2479 % (verb, actualhg, expecthg))
2502 % (verb, actualhg, expecthg))
2480 def _gethgpath(self):
2503 def _gethgpath(self):
2481 """Return the path to the mercurial package that is actually found by
2504 """Return the path to the mercurial package that is actually found by
2482 the current Python interpreter."""
2505 the current Python interpreter."""
2483 if self._hgpath is not None:
2506 if self._hgpath is not None:
2484 return self._hgpath
2507 return self._hgpath
2485
2508
2486 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2509 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2487 cmd = cmd % PYTHON
2510 cmd = cmd % PYTHON
2488 if PYTHON3:
2511 if PYTHON3:
2489 cmd = _strpath(cmd)
2512 cmd = _strpath(cmd)
2490 pipe = os.popen(cmd)
2513 pipe = os.popen(cmd)
2491 try:
2514 try:
2492 self._hgpath = _bytespath(pipe.read().strip())
2515 self._hgpath = _bytespath(pipe.read().strip())
2493 finally:
2516 finally:
2494 pipe.close()
2517 pipe.close()
2495
2518
2496 return self._hgpath
2519 return self._hgpath
2497
2520
2498 def _installchg(self):
2521 def _installchg(self):
2499 """Install chg into the test environment"""
2522 """Install chg into the test environment"""
2500 vlog('# Performing temporary installation of CHG')
2523 vlog('# Performing temporary installation of CHG')
2501 assert os.path.dirname(self._bindir) == self._installdir
2524 assert os.path.dirname(self._bindir) == self._installdir
2502 assert self._hgroot, 'must be called after _installhg()'
2525 assert self._hgroot, 'must be called after _installhg()'
2503 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2526 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2504 % {b'make': 'make', # TODO: switch by option or environment?
2527 % {b'make': 'make', # TODO: switch by option or environment?
2505 b'prefix': self._installdir})
2528 b'prefix': self._installdir})
2506 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2529 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2507 vlog("# Running", cmd)
2530 vlog("# Running", cmd)
2508 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2531 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2509 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2532 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2510 stderr=subprocess.STDOUT)
2533 stderr=subprocess.STDOUT)
2511 out, _err = proc.communicate()
2534 out, _err = proc.communicate()
2512 if proc.returncode != 0:
2535 if proc.returncode != 0:
2513 if PYTHON3:
2536 if PYTHON3:
2514 sys.stdout.buffer.write(out)
2537 sys.stdout.buffer.write(out)
2515 else:
2538 else:
2516 sys.stdout.write(out)
2539 sys.stdout.write(out)
2517 sys.exit(1)
2540 sys.exit(1)
2518
2541
2519 def _outputcoverage(self):
2542 def _outputcoverage(self):
2520 """Produce code coverage output."""
2543 """Produce code coverage output."""
2521 import coverage
2544 import coverage
2522 coverage = coverage.coverage
2545 coverage = coverage.coverage
2523
2546
2524 vlog('# Producing coverage report')
2547 vlog('# Producing coverage report')
2525 # chdir is the easiest way to get short, relative paths in the
2548 # chdir is the easiest way to get short, relative paths in the
2526 # output.
2549 # output.
2527 os.chdir(self._hgroot)
2550 os.chdir(self._hgroot)
2528 covdir = os.path.join(self._installdir, '..', 'coverage')
2551 covdir = os.path.join(self._installdir, '..', 'coverage')
2529 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2552 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2530
2553
2531 # Map install directory paths back to source directory.
2554 # Map install directory paths back to source directory.
2532 cov.config.paths['srcdir'] = ['.', self._pythondir]
2555 cov.config.paths['srcdir'] = ['.', self._pythondir]
2533
2556
2534 cov.combine()
2557 cov.combine()
2535
2558
2536 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2559 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2537 cov.report(ignore_errors=True, omit=omit)
2560 cov.report(ignore_errors=True, omit=omit)
2538
2561
2539 if self.options.htmlcov:
2562 if self.options.htmlcov:
2540 htmldir = os.path.join(self._testdir, 'htmlcov')
2563 htmldir = os.path.join(self._testdir, 'htmlcov')
2541 cov.html_report(directory=htmldir, omit=omit)
2564 cov.html_report(directory=htmldir, omit=omit)
2542 if self.options.annotate:
2565 if self.options.annotate:
2543 adir = os.path.join(self._testdir, 'annotated')
2566 adir = os.path.join(self._testdir, 'annotated')
2544 if not os.path.isdir(adir):
2567 if not os.path.isdir(adir):
2545 os.mkdir(adir)
2568 os.mkdir(adir)
2546 cov.annotate(directory=adir, omit=omit)
2569 cov.annotate(directory=adir, omit=omit)
2547
2570
2548 def _findprogram(self, program):
2571 def _findprogram(self, program):
2549 """Search PATH for a executable program"""
2572 """Search PATH for a executable program"""
2550 dpb = _bytespath(os.defpath)
2573 dpb = _bytespath(os.defpath)
2551 sepb = _bytespath(os.pathsep)
2574 sepb = _bytespath(os.pathsep)
2552 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2575 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2553 name = os.path.join(p, program)
2576 name = os.path.join(p, program)
2554 if os.name == 'nt' or os.access(name, os.X_OK):
2577 if os.name == 'nt' or os.access(name, os.X_OK):
2555 return name
2578 return name
2556 return None
2579 return None
2557
2580
2558 def _checktools(self):
2581 def _checktools(self):
2559 """Ensure tools required to run tests are present."""
2582 """Ensure tools required to run tests are present."""
2560 for p in self.REQUIREDTOOLS:
2583 for p in self.REQUIREDTOOLS:
2561 if os.name == 'nt' and not p.endswith('.exe'):
2584 if os.name == 'nt' and not p.endswith('.exe'):
2562 p += '.exe'
2585 p += '.exe'
2563 found = self._findprogram(p)
2586 found = self._findprogram(p)
2564 if found:
2587 if found:
2565 vlog("# Found prerequisite", p, "at", found)
2588 vlog("# Found prerequisite", p, "at", found)
2566 else:
2589 else:
2567 print("WARNING: Did not find prerequisite tool: %s " %
2590 print("WARNING: Did not find prerequisite tool: %s " %
2568 p.decode("utf-8"))
2591 p.decode("utf-8"))
2569
2592
2570 if __name__ == '__main__':
2593 if __name__ == '__main__':
2571 runner = TestRunner()
2594 runner = TestRunner()
2572
2595
2573 try:
2596 try:
2574 import msvcrt
2597 import msvcrt
2575 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2598 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2576 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2599 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2577 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2600 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2578 except ImportError:
2601 except ImportError:
2579 pass
2602 pass
2580
2603
2581 sys.exit(runner.run(sys.argv[1:]))
2604 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now