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