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