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