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