##// END OF EJS Templates
run-tests: drop required (feature !) style lines when the output is missing...
Matt Harbison -
r33658:eeed2350 default
parent child Browse files
Show More
@@ -1,2838 +1,2845 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 import sysconfig
61 import sysconfig
62 import tempfile
62 import tempfile
63 import threading
63 import threading
64 import time
64 import time
65 import unittest
65 import unittest
66 import xml.dom.minidom as minidom
66 import xml.dom.minidom as minidom
67
67
68 try:
68 try:
69 import Queue as queue
69 import Queue as queue
70 except ImportError:
70 except ImportError:
71 import queue
71 import queue
72
72
73 try:
73 try:
74 import shlex
74 import shlex
75 shellquote = shlex.quote
75 shellquote = shlex.quote
76 except (ImportError, AttributeError):
76 except (ImportError, AttributeError):
77 import pipes
77 import pipes
78 shellquote = pipes.quote
78 shellquote = pipes.quote
79
79
80 if os.environ.get('RTUNICODEPEDANTRY', False):
80 if os.environ.get('RTUNICODEPEDANTRY', False):
81 try:
81 try:
82 reload(sys)
82 reload(sys)
83 sys.setdefaultencoding("undefined")
83 sys.setdefaultencoding("undefined")
84 except NameError:
84 except NameError:
85 pass
85 pass
86
86
87 origenviron = os.environ.copy()
87 origenviron = os.environ.copy()
88 osenvironb = getattr(os, 'environb', os.environ)
88 osenvironb = getattr(os, 'environb', os.environ)
89 processlock = threading.Lock()
89 processlock = threading.Lock()
90
90
91 pygmentspresent = False
91 pygmentspresent = False
92 # ANSI color is unsupported prior to Windows 10
92 # ANSI color is unsupported prior to Windows 10
93 if os.name != 'nt':
93 if os.name != 'nt':
94 try: # is pygments installed
94 try: # is pygments installed
95 import pygments
95 import pygments
96 import pygments.lexers as lexers
96 import pygments.lexers as lexers
97 import pygments.formatters as formatters
97 import pygments.formatters as formatters
98 pygmentspresent = True
98 pygmentspresent = True
99 difflexer = lexers.DiffLexer()
99 difflexer = lexers.DiffLexer()
100 terminal256formatter = formatters.Terminal256Formatter()
100 terminal256formatter = formatters.Terminal256Formatter()
101 except ImportError:
101 except ImportError:
102 pass
102 pass
103
103
104 if sys.version_info > (3, 5, 0):
104 if sys.version_info > (3, 5, 0):
105 PYTHON3 = True
105 PYTHON3 = True
106 xrange = range # we use xrange in one place, and we'd rather not use range
106 xrange = range # we use xrange in one place, and we'd rather not use range
107 def _bytespath(p):
107 def _bytespath(p):
108 return p.encode('utf-8')
108 return p.encode('utf-8')
109
109
110 def _strpath(p):
110 def _strpath(p):
111 return p.decode('utf-8')
111 return p.decode('utf-8')
112
112
113 elif sys.version_info >= (3, 0, 0):
113 elif sys.version_info >= (3, 0, 0):
114 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
114 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
115 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
115 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
116 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
116 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
117 else:
117 else:
118 PYTHON3 = False
118 PYTHON3 = False
119
119
120 # In python 2.x, path operations are generally done using
120 # In python 2.x, path operations are generally done using
121 # bytestrings by default, so we don't have to do any extra
121 # bytestrings by default, so we don't have to do any extra
122 # fiddling there. We define the wrapper functions anyway just to
122 # fiddling there. We define the wrapper functions anyway just to
123 # help keep code consistent between platforms.
123 # help keep code consistent between platforms.
124 def _bytespath(p):
124 def _bytespath(p):
125 return p
125 return p
126
126
127 _strpath = _bytespath
127 _strpath = _bytespath
128
128
129 # For Windows support
129 # For Windows support
130 wifexited = getattr(os, "WIFEXITED", lambda x: False)
130 wifexited = getattr(os, "WIFEXITED", lambda x: False)
131
131
132 # Whether to use IPv6
132 # Whether to use IPv6
133 def checksocketfamily(name, port=20058):
133 def checksocketfamily(name, port=20058):
134 """return true if we can listen on localhost using family=name
134 """return true if we can listen on localhost using family=name
135
135
136 name should be either 'AF_INET', or 'AF_INET6'.
136 name should be either 'AF_INET', or 'AF_INET6'.
137 port being used is okay - EADDRINUSE is considered as successful.
137 port being used is okay - EADDRINUSE is considered as successful.
138 """
138 """
139 family = getattr(socket, name, None)
139 family = getattr(socket, name, None)
140 if family is None:
140 if family is None:
141 return False
141 return False
142 try:
142 try:
143 s = socket.socket(family, socket.SOCK_STREAM)
143 s = socket.socket(family, socket.SOCK_STREAM)
144 s.bind(('localhost', port))
144 s.bind(('localhost', port))
145 s.close()
145 s.close()
146 return True
146 return True
147 except socket.error as exc:
147 except socket.error as exc:
148 if exc.errno == errno.EADDRINUSE:
148 if exc.errno == errno.EADDRINUSE:
149 return True
149 return True
150 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
150 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
151 return False
151 return False
152 else:
152 else:
153 raise
153 raise
154 else:
154 else:
155 return False
155 return False
156
156
157 # useipv6 will be set by parseargs
157 # useipv6 will be set by parseargs
158 useipv6 = None
158 useipv6 = None
159
159
160 def checkportisavailable(port):
160 def checkportisavailable(port):
161 """return true if a port seems free to bind on localhost"""
161 """return true if a port seems free to bind on localhost"""
162 if useipv6:
162 if useipv6:
163 family = socket.AF_INET6
163 family = socket.AF_INET6
164 else:
164 else:
165 family = socket.AF_INET
165 family = socket.AF_INET
166 try:
166 try:
167 s = socket.socket(family, socket.SOCK_STREAM)
167 s = socket.socket(family, socket.SOCK_STREAM)
168 s.bind(('localhost', port))
168 s.bind(('localhost', port))
169 s.close()
169 s.close()
170 return True
170 return True
171 except socket.error as exc:
171 except socket.error as exc:
172 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
172 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
173 errno.EPROTONOSUPPORT):
173 errno.EPROTONOSUPPORT):
174 raise
174 raise
175 return False
175 return False
176
176
177 closefds = os.name == 'posix'
177 closefds = os.name == 'posix'
178 def Popen4(cmd, wd, timeout, env=None):
178 def Popen4(cmd, wd, timeout, env=None):
179 processlock.acquire()
179 processlock.acquire()
180 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
180 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
181 close_fds=closefds,
181 close_fds=closefds,
182 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
182 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
183 stderr=subprocess.STDOUT)
183 stderr=subprocess.STDOUT)
184 processlock.release()
184 processlock.release()
185
185
186 p.fromchild = p.stdout
186 p.fromchild = p.stdout
187 p.tochild = p.stdin
187 p.tochild = p.stdin
188 p.childerr = p.stderr
188 p.childerr = p.stderr
189
189
190 p.timeout = False
190 p.timeout = False
191 if timeout:
191 if timeout:
192 def t():
192 def t():
193 start = time.time()
193 start = time.time()
194 while time.time() - start < timeout and p.returncode is None:
194 while time.time() - start < timeout and p.returncode is None:
195 time.sleep(.1)
195 time.sleep(.1)
196 p.timeout = True
196 p.timeout = True
197 if p.returncode is None:
197 if p.returncode is None:
198 terminate(p)
198 terminate(p)
199 threading.Thread(target=t).start()
199 threading.Thread(target=t).start()
200
200
201 return p
201 return p
202
202
203 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
203 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
204 IMPL_PATH = b'PYTHONPATH'
204 IMPL_PATH = b'PYTHONPATH'
205 if 'java' in sys.platform:
205 if 'java' in sys.platform:
206 IMPL_PATH = b'JYTHONPATH'
206 IMPL_PATH = b'JYTHONPATH'
207
207
208 defaults = {
208 defaults = {
209 'jobs': ('HGTEST_JOBS', 1),
209 'jobs': ('HGTEST_JOBS', 1),
210 'timeout': ('HGTEST_TIMEOUT', 180),
210 'timeout': ('HGTEST_TIMEOUT', 180),
211 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
211 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
212 'port': ('HGTEST_PORT', 20059),
212 'port': ('HGTEST_PORT', 20059),
213 'shell': ('HGTEST_SHELL', 'sh'),
213 'shell': ('HGTEST_SHELL', 'sh'),
214 }
214 }
215
215
216 def canonpath(path):
216 def canonpath(path):
217 return os.path.realpath(os.path.expanduser(path))
217 return os.path.realpath(os.path.expanduser(path))
218
218
219 def parselistfiles(files, listtype, warn=True):
219 def parselistfiles(files, listtype, warn=True):
220 entries = dict()
220 entries = dict()
221 for filename in files:
221 for filename in files:
222 try:
222 try:
223 path = os.path.expanduser(os.path.expandvars(filename))
223 path = os.path.expanduser(os.path.expandvars(filename))
224 f = open(path, "rb")
224 f = open(path, "rb")
225 except IOError as err:
225 except IOError as err:
226 if err.errno != errno.ENOENT:
226 if err.errno != errno.ENOENT:
227 raise
227 raise
228 if warn:
228 if warn:
229 print("warning: no such %s file: %s" % (listtype, filename))
229 print("warning: no such %s file: %s" % (listtype, filename))
230 continue
230 continue
231
231
232 for line in f.readlines():
232 for line in f.readlines():
233 line = line.split(b'#', 1)[0].strip()
233 line = line.split(b'#', 1)[0].strip()
234 if line:
234 if line:
235 entries[line] = filename
235 entries[line] = filename
236
236
237 f.close()
237 f.close()
238 return entries
238 return entries
239
239
240 def parsettestcases(path):
240 def parsettestcases(path):
241 """read a .t test file, return a set of test case names
241 """read a .t test file, return a set of test case names
242
242
243 If path does not exist, return an empty set.
243 If path does not exist, return an empty set.
244 """
244 """
245 cases = set()
245 cases = set()
246 try:
246 try:
247 with open(path, 'rb') as f:
247 with open(path, 'rb') as f:
248 for l in f:
248 for l in f:
249 if l.startswith(b'#testcases '):
249 if l.startswith(b'#testcases '):
250 cases.update(l[11:].split())
250 cases.update(l[11:].split())
251 except IOError as ex:
251 except IOError as ex:
252 if ex.errno != errno.ENOENT:
252 if ex.errno != errno.ENOENT:
253 raise
253 raise
254 return cases
254 return cases
255
255
256 def getparser():
256 def getparser():
257 """Obtain the OptionParser used by the CLI."""
257 """Obtain the OptionParser used by the CLI."""
258 parser = optparse.OptionParser("%prog [options] [tests]")
258 parser = optparse.OptionParser("%prog [options] [tests]")
259
259
260 # keep these sorted
260 # keep these sorted
261 parser.add_option("--blacklist", action="append",
261 parser.add_option("--blacklist", action="append",
262 help="skip tests listed in the specified blacklist file")
262 help="skip tests listed in the specified blacklist file")
263 parser.add_option("--whitelist", action="append",
263 parser.add_option("--whitelist", action="append",
264 help="always run tests listed in the specified whitelist file")
264 help="always run tests listed in the specified whitelist file")
265 parser.add_option("--changed", type="string",
265 parser.add_option("--changed", type="string",
266 help="run tests that are changed in parent rev or working directory")
266 help="run tests that are changed in parent rev or working directory")
267 parser.add_option("-C", "--annotate", action="store_true",
267 parser.add_option("-C", "--annotate", action="store_true",
268 help="output files annotated with coverage")
268 help="output files annotated with coverage")
269 parser.add_option("-c", "--cover", action="store_true",
269 parser.add_option("-c", "--cover", action="store_true",
270 help="print a test coverage report")
270 help="print a test coverage report")
271 parser.add_option("--color", choices=["always", "auto", "never"],
271 parser.add_option("--color", choices=["always", "auto", "never"],
272 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
272 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
273 help="colorisation: always|auto|never (default: auto)")
273 help="colorisation: always|auto|never (default: auto)")
274 parser.add_option("-d", "--debug", action="store_true",
274 parser.add_option("-d", "--debug", action="store_true",
275 help="debug mode: write output of test scripts to console"
275 help="debug mode: write output of test scripts to console"
276 " rather than capturing and diffing it (disables timeout)")
276 " rather than capturing and diffing it (disables timeout)")
277 parser.add_option("-f", "--first", action="store_true",
277 parser.add_option("-f", "--first", action="store_true",
278 help="exit on the first test failure")
278 help="exit on the first test failure")
279 parser.add_option("-H", "--htmlcov", action="store_true",
279 parser.add_option("-H", "--htmlcov", action="store_true",
280 help="create an HTML report of the coverage of the files")
280 help="create an HTML report of the coverage of the files")
281 parser.add_option("-i", "--interactive", action="store_true",
281 parser.add_option("-i", "--interactive", action="store_true",
282 help="prompt to accept changed output")
282 help="prompt to accept changed output")
283 parser.add_option("-j", "--jobs", type="int",
283 parser.add_option("-j", "--jobs", type="int",
284 help="number of jobs to run in parallel"
284 help="number of jobs to run in parallel"
285 " (default: $%s or %d)" % defaults['jobs'])
285 " (default: $%s or %d)" % defaults['jobs'])
286 parser.add_option("--keep-tmpdir", action="store_true",
286 parser.add_option("--keep-tmpdir", action="store_true",
287 help="keep temporary directory after running tests")
287 help="keep temporary directory after running tests")
288 parser.add_option("-k", "--keywords",
288 parser.add_option("-k", "--keywords",
289 help="run tests matching keywords")
289 help="run tests matching keywords")
290 parser.add_option("--list-tests", action="store_true",
290 parser.add_option("--list-tests", action="store_true",
291 help="list tests instead of running them")
291 help="list tests instead of running them")
292 parser.add_option("-l", "--local", action="store_true",
292 parser.add_option("-l", "--local", action="store_true",
293 help="shortcut for --with-hg=<testdir>/../hg, "
293 help="shortcut for --with-hg=<testdir>/../hg, "
294 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
294 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
295 parser.add_option("--loop", action="store_true",
295 parser.add_option("--loop", action="store_true",
296 help="loop tests repeatedly")
296 help="loop tests repeatedly")
297 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
297 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
298 help="run each test N times (default=1)", default=1)
298 help="run each test N times (default=1)", default=1)
299 parser.add_option("-n", "--nodiff", action="store_true",
299 parser.add_option("-n", "--nodiff", action="store_true",
300 help="skip showing test changes")
300 help="skip showing test changes")
301 parser.add_option("--outputdir", type="string",
301 parser.add_option("--outputdir", type="string",
302 help="directory to write error logs to (default=test directory)")
302 help="directory to write error logs to (default=test directory)")
303 parser.add_option("-p", "--port", type="int",
303 parser.add_option("-p", "--port", type="int",
304 help="port on which servers should listen"
304 help="port on which servers should listen"
305 " (default: $%s or %d)" % defaults['port'])
305 " (default: $%s or %d)" % defaults['port'])
306 parser.add_option("--compiler", type="string",
306 parser.add_option("--compiler", type="string",
307 help="compiler to build with")
307 help="compiler to build with")
308 parser.add_option("--pure", action="store_true",
308 parser.add_option("--pure", action="store_true",
309 help="use pure Python code instead of C extensions")
309 help="use pure Python code instead of C extensions")
310 parser.add_option("-R", "--restart", action="store_true",
310 parser.add_option("-R", "--restart", action="store_true",
311 help="restart at last error")
311 help="restart at last error")
312 parser.add_option("-r", "--retest", action="store_true",
312 parser.add_option("-r", "--retest", action="store_true",
313 help="retest failed tests")
313 help="retest failed tests")
314 parser.add_option("-S", "--noskips", action="store_true",
314 parser.add_option("-S", "--noskips", action="store_true",
315 help="don't report skip tests verbosely")
315 help="don't report skip tests verbosely")
316 parser.add_option("--shell", type="string",
316 parser.add_option("--shell", type="string",
317 help="shell to use (default: $%s or %s)" % defaults['shell'])
317 help="shell to use (default: $%s or %s)" % defaults['shell'])
318 parser.add_option("-t", "--timeout", type="int",
318 parser.add_option("-t", "--timeout", type="int",
319 help="kill errant tests after TIMEOUT seconds"
319 help="kill errant tests after TIMEOUT seconds"
320 " (default: $%s or %d)" % defaults['timeout'])
320 " (default: $%s or %d)" % defaults['timeout'])
321 parser.add_option("--slowtimeout", type="int",
321 parser.add_option("--slowtimeout", type="int",
322 help="kill errant slow tests after SLOWTIMEOUT seconds"
322 help="kill errant slow tests after SLOWTIMEOUT seconds"
323 " (default: $%s or %d)" % defaults['slowtimeout'])
323 " (default: $%s or %d)" % defaults['slowtimeout'])
324 parser.add_option("--time", action="store_true",
324 parser.add_option("--time", action="store_true",
325 help="time how long each test takes")
325 help="time how long each test takes")
326 parser.add_option("--json", action="store_true",
326 parser.add_option("--json", action="store_true",
327 help="store test result data in 'report.json' file")
327 help="store test result data in 'report.json' file")
328 parser.add_option("--tmpdir", type="string",
328 parser.add_option("--tmpdir", type="string",
329 help="run tests in the given temporary directory"
329 help="run tests in the given temporary directory"
330 " (implies --keep-tmpdir)")
330 " (implies --keep-tmpdir)")
331 parser.add_option("-v", "--verbose", action="store_true",
331 parser.add_option("-v", "--verbose", action="store_true",
332 help="output verbose messages")
332 help="output verbose messages")
333 parser.add_option("--xunit", type="string",
333 parser.add_option("--xunit", type="string",
334 help="record xunit results at specified path")
334 help="record xunit results at specified path")
335 parser.add_option("--view", type="string",
335 parser.add_option("--view", type="string",
336 help="external diff viewer")
336 help="external diff viewer")
337 parser.add_option("--with-hg", type="string",
337 parser.add_option("--with-hg", type="string",
338 metavar="HG",
338 metavar="HG",
339 help="test using specified hg script rather than a "
339 help="test using specified hg script rather than a "
340 "temporary installation")
340 "temporary installation")
341 parser.add_option("--chg", action="store_true",
341 parser.add_option("--chg", action="store_true",
342 help="install and use chg wrapper in place of hg")
342 help="install and use chg wrapper in place of hg")
343 parser.add_option("--with-chg", metavar="CHG",
343 parser.add_option("--with-chg", metavar="CHG",
344 help="use specified chg wrapper in place of hg")
344 help="use specified chg wrapper in place of hg")
345 parser.add_option("--ipv6", action="store_true",
345 parser.add_option("--ipv6", action="store_true",
346 help="prefer IPv6 to IPv4 for network related tests")
346 help="prefer IPv6 to IPv4 for network related tests")
347 parser.add_option("-3", "--py3k-warnings", action="store_true",
347 parser.add_option("-3", "--py3k-warnings", action="store_true",
348 help="enable Py3k warnings on Python 2.7+")
348 help="enable Py3k warnings on Python 2.7+")
349 # This option should be deleted once test-check-py3-compat.t and other
349 # This option should be deleted once test-check-py3-compat.t and other
350 # Python 3 tests run with Python 3.
350 # Python 3 tests run with Python 3.
351 parser.add_option("--with-python3", metavar="PYTHON3",
351 parser.add_option("--with-python3", metavar="PYTHON3",
352 help="Python 3 interpreter (if running under Python 2)"
352 help="Python 3 interpreter (if running under Python 2)"
353 " (TEMPORARY)")
353 " (TEMPORARY)")
354 parser.add_option('--extra-config-opt', action="append",
354 parser.add_option('--extra-config-opt', action="append",
355 help='set the given config opt in the test hgrc')
355 help='set the given config opt in the test hgrc')
356 parser.add_option('--random', action="store_true",
356 parser.add_option('--random', action="store_true",
357 help='run tests in random order')
357 help='run tests in random order')
358 parser.add_option('--profile-runner', action='store_true',
358 parser.add_option('--profile-runner', action='store_true',
359 help='run statprof on run-tests')
359 help='run statprof on run-tests')
360 parser.add_option('--allow-slow-tests', action='store_true',
360 parser.add_option('--allow-slow-tests', action='store_true',
361 help='allow extremely slow tests')
361 help='allow extremely slow tests')
362 parser.add_option('--showchannels', action='store_true',
362 parser.add_option('--showchannels', action='store_true',
363 help='show scheduling channels')
363 help='show scheduling channels')
364 parser.add_option('--known-good-rev', type="string",
364 parser.add_option('--known-good-rev', type="string",
365 metavar="known_good_rev",
365 metavar="known_good_rev",
366 help=("Automatically bisect any failures using this "
366 help=("Automatically bisect any failures using this "
367 "revision as a known-good revision."))
367 "revision as a known-good revision."))
368
368
369 for option, (envvar, default) in defaults.items():
369 for option, (envvar, default) in defaults.items():
370 defaults[option] = type(default)(os.environ.get(envvar, default))
370 defaults[option] = type(default)(os.environ.get(envvar, default))
371 parser.set_defaults(**defaults)
371 parser.set_defaults(**defaults)
372
372
373 return parser
373 return parser
374
374
375 def parseargs(args, parser):
375 def parseargs(args, parser):
376 """Parse arguments with our OptionParser and validate results."""
376 """Parse arguments with our OptionParser and validate results."""
377 (options, args) = parser.parse_args(args)
377 (options, args) = parser.parse_args(args)
378
378
379 # jython is always pure
379 # jython is always pure
380 if 'java' in sys.platform or '__pypy__' in sys.modules:
380 if 'java' in sys.platform or '__pypy__' in sys.modules:
381 options.pure = True
381 options.pure = True
382
382
383 if options.with_hg:
383 if options.with_hg:
384 options.with_hg = canonpath(_bytespath(options.with_hg))
384 options.with_hg = canonpath(_bytespath(options.with_hg))
385 if not (os.path.isfile(options.with_hg) and
385 if not (os.path.isfile(options.with_hg) and
386 os.access(options.with_hg, os.X_OK)):
386 os.access(options.with_hg, os.X_OK)):
387 parser.error('--with-hg must specify an executable hg script')
387 parser.error('--with-hg must specify an executable hg script')
388 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
388 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
389 sys.stderr.write('warning: --with-hg should specify an hg script\n')
389 sys.stderr.write('warning: --with-hg should specify an hg script\n')
390 if options.local:
390 if options.local:
391 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
391 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
392 reporootdir = os.path.dirname(testdir)
392 reporootdir = os.path.dirname(testdir)
393 pathandattrs = [(b'hg', 'with_hg')]
393 pathandattrs = [(b'hg', 'with_hg')]
394 if options.chg:
394 if options.chg:
395 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
395 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
396 for relpath, attr in pathandattrs:
396 for relpath, attr in pathandattrs:
397 binpath = os.path.join(reporootdir, relpath)
397 binpath = os.path.join(reporootdir, relpath)
398 if os.name != 'nt' and not os.access(binpath, os.X_OK):
398 if os.name != 'nt' and not os.access(binpath, os.X_OK):
399 parser.error('--local specified, but %r not found or '
399 parser.error('--local specified, but %r not found or '
400 'not executable' % binpath)
400 'not executable' % binpath)
401 setattr(options, attr, binpath)
401 setattr(options, attr, binpath)
402
402
403 if (options.chg or options.with_chg) and os.name == 'nt':
403 if (options.chg or options.with_chg) and os.name == 'nt':
404 parser.error('chg does not work on %s' % os.name)
404 parser.error('chg does not work on %s' % os.name)
405 if options.with_chg:
405 if options.with_chg:
406 options.chg = False # no installation to temporary location
406 options.chg = False # no installation to temporary location
407 options.with_chg = canonpath(_bytespath(options.with_chg))
407 options.with_chg = canonpath(_bytespath(options.with_chg))
408 if not (os.path.isfile(options.with_chg) and
408 if not (os.path.isfile(options.with_chg) and
409 os.access(options.with_chg, os.X_OK)):
409 os.access(options.with_chg, os.X_OK)):
410 parser.error('--with-chg must specify a chg executable')
410 parser.error('--with-chg must specify a chg executable')
411 if options.chg and options.with_hg:
411 if options.chg and options.with_hg:
412 # chg shares installation location with hg
412 # chg shares installation location with hg
413 parser.error('--chg does not work when --with-hg is specified '
413 parser.error('--chg does not work when --with-hg is specified '
414 '(use --with-chg instead)')
414 '(use --with-chg instead)')
415
415
416 if options.color == 'always' and not pygmentspresent:
416 if options.color == 'always' and not pygmentspresent:
417 sys.stderr.write('warning: --color=always ignored because '
417 sys.stderr.write('warning: --color=always ignored because '
418 'pygments is not installed\n')
418 'pygments is not installed\n')
419
419
420 global useipv6
420 global useipv6
421 if options.ipv6:
421 if options.ipv6:
422 useipv6 = checksocketfamily('AF_INET6')
422 useipv6 = checksocketfamily('AF_INET6')
423 else:
423 else:
424 # only use IPv6 if IPv4 is unavailable and IPv6 is available
424 # only use IPv6 if IPv4 is unavailable and IPv6 is available
425 useipv6 = ((not checksocketfamily('AF_INET'))
425 useipv6 = ((not checksocketfamily('AF_INET'))
426 and checksocketfamily('AF_INET6'))
426 and checksocketfamily('AF_INET6'))
427
427
428 options.anycoverage = options.cover or options.annotate or options.htmlcov
428 options.anycoverage = options.cover or options.annotate or options.htmlcov
429 if options.anycoverage:
429 if options.anycoverage:
430 try:
430 try:
431 import coverage
431 import coverage
432 covver = version.StrictVersion(coverage.__version__).version
432 covver = version.StrictVersion(coverage.__version__).version
433 if covver < (3, 3):
433 if covver < (3, 3):
434 parser.error('coverage options require coverage 3.3 or later')
434 parser.error('coverage options require coverage 3.3 or later')
435 except ImportError:
435 except ImportError:
436 parser.error('coverage options now require the coverage package')
436 parser.error('coverage options now require the coverage package')
437
437
438 if options.anycoverage and options.local:
438 if options.anycoverage and options.local:
439 # this needs some path mangling somewhere, I guess
439 # this needs some path mangling somewhere, I guess
440 parser.error("sorry, coverage options do not work when --local "
440 parser.error("sorry, coverage options do not work when --local "
441 "is specified")
441 "is specified")
442
442
443 if options.anycoverage and options.with_hg:
443 if options.anycoverage and options.with_hg:
444 parser.error("sorry, coverage options do not work when --with-hg "
444 parser.error("sorry, coverage options do not work when --with-hg "
445 "is specified")
445 "is specified")
446
446
447 global verbose
447 global verbose
448 if options.verbose:
448 if options.verbose:
449 verbose = ''
449 verbose = ''
450
450
451 if options.tmpdir:
451 if options.tmpdir:
452 options.tmpdir = canonpath(options.tmpdir)
452 options.tmpdir = canonpath(options.tmpdir)
453
453
454 if options.jobs < 1:
454 if options.jobs < 1:
455 parser.error('--jobs must be positive')
455 parser.error('--jobs must be positive')
456 if options.interactive and options.debug:
456 if options.interactive and options.debug:
457 parser.error("-i/--interactive and -d/--debug are incompatible")
457 parser.error("-i/--interactive and -d/--debug are incompatible")
458 if options.debug:
458 if options.debug:
459 if options.timeout != defaults['timeout']:
459 if options.timeout != defaults['timeout']:
460 sys.stderr.write(
460 sys.stderr.write(
461 'warning: --timeout option ignored with --debug\n')
461 'warning: --timeout option ignored with --debug\n')
462 if options.slowtimeout != defaults['slowtimeout']:
462 if options.slowtimeout != defaults['slowtimeout']:
463 sys.stderr.write(
463 sys.stderr.write(
464 'warning: --slowtimeout option ignored with --debug\n')
464 'warning: --slowtimeout option ignored with --debug\n')
465 options.timeout = 0
465 options.timeout = 0
466 options.slowtimeout = 0
466 options.slowtimeout = 0
467 if options.py3k_warnings:
467 if options.py3k_warnings:
468 if PYTHON3:
468 if PYTHON3:
469 parser.error(
469 parser.error(
470 '--py3k-warnings can only be used on Python 2.7')
470 '--py3k-warnings can only be used on Python 2.7')
471 if options.with_python3:
471 if options.with_python3:
472 if PYTHON3:
472 if PYTHON3:
473 parser.error('--with-python3 cannot be used when executing with '
473 parser.error('--with-python3 cannot be used when executing with '
474 'Python 3')
474 'Python 3')
475
475
476 options.with_python3 = canonpath(options.with_python3)
476 options.with_python3 = canonpath(options.with_python3)
477 # Verify Python3 executable is acceptable.
477 # Verify Python3 executable is acceptable.
478 proc = subprocess.Popen([options.with_python3, b'--version'],
478 proc = subprocess.Popen([options.with_python3, b'--version'],
479 stdout=subprocess.PIPE,
479 stdout=subprocess.PIPE,
480 stderr=subprocess.STDOUT)
480 stderr=subprocess.STDOUT)
481 out, _err = proc.communicate()
481 out, _err = proc.communicate()
482 ret = proc.wait()
482 ret = proc.wait()
483 if ret != 0:
483 if ret != 0:
484 parser.error('could not determine version of python 3')
484 parser.error('could not determine version of python 3')
485 if not out.startswith('Python '):
485 if not out.startswith('Python '):
486 parser.error('unexpected output from python3 --version: %s' %
486 parser.error('unexpected output from python3 --version: %s' %
487 out)
487 out)
488 vers = version.LooseVersion(out[len('Python '):])
488 vers = version.LooseVersion(out[len('Python '):])
489 if vers < version.LooseVersion('3.5.0'):
489 if vers < version.LooseVersion('3.5.0'):
490 parser.error('--with-python3 version must be 3.5.0 or greater; '
490 parser.error('--with-python3 version must be 3.5.0 or greater; '
491 'got %s' % out)
491 'got %s' % out)
492
492
493 if options.blacklist:
493 if options.blacklist:
494 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
494 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
495 if options.whitelist:
495 if options.whitelist:
496 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
496 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
497 else:
497 else:
498 options.whitelisted = {}
498 options.whitelisted = {}
499
499
500 if options.showchannels:
500 if options.showchannels:
501 options.nodiff = True
501 options.nodiff = True
502
502
503 return (options, args)
503 return (options, args)
504
504
505 def rename(src, dst):
505 def rename(src, dst):
506 """Like os.rename(), trade atomicity and opened files friendliness
506 """Like os.rename(), trade atomicity and opened files friendliness
507 for existing destination support.
507 for existing destination support.
508 """
508 """
509 shutil.copy(src, dst)
509 shutil.copy(src, dst)
510 os.remove(src)
510 os.remove(src)
511
511
512 _unified_diff = difflib.unified_diff
512 _unified_diff = difflib.unified_diff
513 if PYTHON3:
513 if PYTHON3:
514 import functools
514 import functools
515 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
515 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
516
516
517 def getdiff(expected, output, ref, err):
517 def getdiff(expected, output, ref, err):
518 servefail = False
518 servefail = False
519 lines = []
519 lines = []
520 for line in _unified_diff(expected, output, ref, err):
520 for line in _unified_diff(expected, output, ref, err):
521 if line.startswith(b'+++') or line.startswith(b'---'):
521 if line.startswith(b'+++') or line.startswith(b'---'):
522 line = line.replace(b'\\', b'/')
522 line = line.replace(b'\\', b'/')
523 if line.endswith(b' \n'):
523 if line.endswith(b' \n'):
524 line = line[:-2] + b'\n'
524 line = line[:-2] + b'\n'
525 lines.append(line)
525 lines.append(line)
526 if not servefail and line.startswith(
526 if not servefail and line.startswith(
527 b'+ abort: child process failed to start'):
527 b'+ abort: child process failed to start'):
528 servefail = True
528 servefail = True
529
529
530 return servefail, lines
530 return servefail, lines
531
531
532 verbose = False
532 verbose = False
533 def vlog(*msg):
533 def vlog(*msg):
534 """Log only when in verbose mode."""
534 """Log only when in verbose mode."""
535 if verbose is False:
535 if verbose is False:
536 return
536 return
537
537
538 return log(*msg)
538 return log(*msg)
539
539
540 # Bytes that break XML even in a CDATA block: control characters 0-31
540 # Bytes that break XML even in a CDATA block: control characters 0-31
541 # sans \t, \n and \r
541 # sans \t, \n and \r
542 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
542 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
543
543
544 # Match feature conditionalized output lines in the form, capturing the feature
544 # Match feature conditionalized output lines in the form, capturing the feature
545 # list in group 2, and the preceeding line output in group 1:
545 # list in group 2, and the preceeding line output in group 1:
546 #
546 #
547 # output..output (feature !)\n
547 # output..output (feature !)\n
548 optline = re.compile(b'(.+) \((.+?) !\)\n$')
548 optline = re.compile(b'(.+) \((.+?) !\)\n$')
549
549
550 def cdatasafe(data):
550 def cdatasafe(data):
551 """Make a string safe to include in a CDATA block.
551 """Make a string safe to include in a CDATA block.
552
552
553 Certain control characters are illegal in a CDATA block, and
553 Certain control characters are illegal in a CDATA block, and
554 there's no way to include a ]]> in a CDATA either. This function
554 there's no way to include a ]]> in a CDATA either. This function
555 replaces illegal bytes with ? and adds a space between the ]] so
555 replaces illegal bytes with ? and adds a space between the ]] so
556 that it won't break the CDATA block.
556 that it won't break the CDATA block.
557 """
557 """
558 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
558 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
559
559
560 def log(*msg):
560 def log(*msg):
561 """Log something to stdout.
561 """Log something to stdout.
562
562
563 Arguments are strings to print.
563 Arguments are strings to print.
564 """
564 """
565 with iolock:
565 with iolock:
566 if verbose:
566 if verbose:
567 print(verbose, end=' ')
567 print(verbose, end=' ')
568 for m in msg:
568 for m in msg:
569 print(m, end=' ')
569 print(m, end=' ')
570 print()
570 print()
571 sys.stdout.flush()
571 sys.stdout.flush()
572
572
573 def terminate(proc):
573 def terminate(proc):
574 """Terminate subprocess"""
574 """Terminate subprocess"""
575 vlog('# Terminating process %d' % proc.pid)
575 vlog('# Terminating process %d' % proc.pid)
576 try:
576 try:
577 proc.terminate()
577 proc.terminate()
578 except OSError:
578 except OSError:
579 pass
579 pass
580
580
581 def killdaemons(pidfile):
581 def killdaemons(pidfile):
582 import killdaemons as killmod
582 import killdaemons as killmod
583 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
583 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
584 logfn=vlog)
584 logfn=vlog)
585
585
586 class Test(unittest.TestCase):
586 class Test(unittest.TestCase):
587 """Encapsulates a single, runnable test.
587 """Encapsulates a single, runnable test.
588
588
589 While this class conforms to the unittest.TestCase API, it differs in that
589 While this class conforms to the unittest.TestCase API, it differs in that
590 instances need to be instantiated manually. (Typically, unittest.TestCase
590 instances need to be instantiated manually. (Typically, unittest.TestCase
591 classes are instantiated automatically by scanning modules.)
591 classes are instantiated automatically by scanning modules.)
592 """
592 """
593
593
594 # Status code reserved for skipped tests (used by hghave).
594 # Status code reserved for skipped tests (used by hghave).
595 SKIPPED_STATUS = 80
595 SKIPPED_STATUS = 80
596
596
597 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
597 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
598 debug=False,
598 debug=False,
599 timeout=defaults['timeout'],
599 timeout=defaults['timeout'],
600 startport=defaults['port'], extraconfigopts=None,
600 startport=defaults['port'], extraconfigopts=None,
601 py3kwarnings=False, shell=None, hgcommand=None,
601 py3kwarnings=False, shell=None, hgcommand=None,
602 slowtimeout=defaults['slowtimeout'], usechg=False,
602 slowtimeout=defaults['slowtimeout'], usechg=False,
603 useipv6=False):
603 useipv6=False):
604 """Create a test from parameters.
604 """Create a test from parameters.
605
605
606 path is the full path to the file defining the test.
606 path is the full path to the file defining the test.
607
607
608 tmpdir is the main temporary directory to use for this test.
608 tmpdir is the main temporary directory to use for this test.
609
609
610 keeptmpdir determines whether to keep the test's temporary directory
610 keeptmpdir determines whether to keep the test's temporary directory
611 after execution. It defaults to removal (False).
611 after execution. It defaults to removal (False).
612
612
613 debug mode will make the test execute verbosely, with unfiltered
613 debug mode will make the test execute verbosely, with unfiltered
614 output.
614 output.
615
615
616 timeout controls the maximum run time of the test. It is ignored when
616 timeout controls the maximum run time of the test. It is ignored when
617 debug is True. See slowtimeout for tests with #require slow.
617 debug is True. See slowtimeout for tests with #require slow.
618
618
619 slowtimeout overrides timeout if the test has #require slow.
619 slowtimeout overrides timeout if the test has #require slow.
620
620
621 startport controls the starting port number to use for this test. Each
621 startport controls the starting port number to use for this test. Each
622 test will reserve 3 port numbers for execution. It is the caller's
622 test will reserve 3 port numbers for execution. It is the caller's
623 responsibility to allocate a non-overlapping port range to Test
623 responsibility to allocate a non-overlapping port range to Test
624 instances.
624 instances.
625
625
626 extraconfigopts is an iterable of extra hgrc config options. Values
626 extraconfigopts is an iterable of extra hgrc config options. Values
627 must have the form "key=value" (something understood by hgrc). Values
627 must have the form "key=value" (something understood by hgrc). Values
628 of the form "foo.key=value" will result in "[foo] key=value".
628 of the form "foo.key=value" will result in "[foo] key=value".
629
629
630 py3kwarnings enables Py3k warnings.
630 py3kwarnings enables Py3k warnings.
631
631
632 shell is the shell to execute tests in.
632 shell is the shell to execute tests in.
633 """
633 """
634 self.path = path
634 self.path = path
635 self.bname = os.path.basename(path)
635 self.bname = os.path.basename(path)
636 self.name = _strpath(self.bname)
636 self.name = _strpath(self.bname)
637 self._testdir = os.path.dirname(path)
637 self._testdir = os.path.dirname(path)
638 self._outputdir = outputdir
638 self._outputdir = outputdir
639 self._tmpname = os.path.basename(path)
639 self._tmpname = os.path.basename(path)
640 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
640 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
641
641
642 self._threadtmp = tmpdir
642 self._threadtmp = tmpdir
643 self._keeptmpdir = keeptmpdir
643 self._keeptmpdir = keeptmpdir
644 self._debug = debug
644 self._debug = debug
645 self._timeout = timeout
645 self._timeout = timeout
646 self._slowtimeout = slowtimeout
646 self._slowtimeout = slowtimeout
647 self._startport = startport
647 self._startport = startport
648 self._extraconfigopts = extraconfigopts or []
648 self._extraconfigopts = extraconfigopts or []
649 self._py3kwarnings = py3kwarnings
649 self._py3kwarnings = py3kwarnings
650 self._shell = _bytespath(shell)
650 self._shell = _bytespath(shell)
651 self._hgcommand = hgcommand or b'hg'
651 self._hgcommand = hgcommand or b'hg'
652 self._usechg = usechg
652 self._usechg = usechg
653 self._useipv6 = useipv6
653 self._useipv6 = useipv6
654
654
655 self._aborted = False
655 self._aborted = False
656 self._daemonpids = []
656 self._daemonpids = []
657 self._finished = None
657 self._finished = None
658 self._ret = None
658 self._ret = None
659 self._out = None
659 self._out = None
660 self._skipped = None
660 self._skipped = None
661 self._testtmp = None
661 self._testtmp = None
662 self._chgsockdir = None
662 self._chgsockdir = None
663
663
664 self._refout = self.readrefout()
664 self._refout = self.readrefout()
665
665
666 def readrefout(self):
666 def readrefout(self):
667 """read reference output"""
667 """read reference output"""
668 # If we're not in --debug mode and reference output file exists,
668 # If we're not in --debug mode and reference output file exists,
669 # check test output against it.
669 # check test output against it.
670 if self._debug:
670 if self._debug:
671 return None # to match "out is None"
671 return None # to match "out is None"
672 elif os.path.exists(self.refpath):
672 elif os.path.exists(self.refpath):
673 with open(self.refpath, 'rb') as f:
673 with open(self.refpath, 'rb') as f:
674 return f.read().splitlines(True)
674 return f.read().splitlines(True)
675 else:
675 else:
676 return []
676 return []
677
677
678 # needed to get base class __repr__ running
678 # needed to get base class __repr__ running
679 @property
679 @property
680 def _testMethodName(self):
680 def _testMethodName(self):
681 return self.name
681 return self.name
682
682
683 def __str__(self):
683 def __str__(self):
684 return self.name
684 return self.name
685
685
686 def shortDescription(self):
686 def shortDescription(self):
687 return self.name
687 return self.name
688
688
689 def setUp(self):
689 def setUp(self):
690 """Tasks to perform before run()."""
690 """Tasks to perform before run()."""
691 self._finished = False
691 self._finished = False
692 self._ret = None
692 self._ret = None
693 self._out = None
693 self._out = None
694 self._skipped = None
694 self._skipped = None
695
695
696 try:
696 try:
697 os.mkdir(self._threadtmp)
697 os.mkdir(self._threadtmp)
698 except OSError as e:
698 except OSError as e:
699 if e.errno != errno.EEXIST:
699 if e.errno != errno.EEXIST:
700 raise
700 raise
701
701
702 name = self._tmpname
702 name = self._tmpname
703 self._testtmp = os.path.join(self._threadtmp, name)
703 self._testtmp = os.path.join(self._threadtmp, name)
704 os.mkdir(self._testtmp)
704 os.mkdir(self._testtmp)
705
705
706 # Remove any previous output files.
706 # Remove any previous output files.
707 if os.path.exists(self.errpath):
707 if os.path.exists(self.errpath):
708 try:
708 try:
709 os.remove(self.errpath)
709 os.remove(self.errpath)
710 except OSError as e:
710 except OSError as e:
711 # We might have raced another test to clean up a .err
711 # We might have raced another test to clean up a .err
712 # file, so ignore ENOENT when removing a previous .err
712 # file, so ignore ENOENT when removing a previous .err
713 # file.
713 # file.
714 if e.errno != errno.ENOENT:
714 if e.errno != errno.ENOENT:
715 raise
715 raise
716
716
717 if self._usechg:
717 if self._usechg:
718 self._chgsockdir = os.path.join(self._threadtmp,
718 self._chgsockdir = os.path.join(self._threadtmp,
719 b'%s.chgsock' % name)
719 b'%s.chgsock' % name)
720 os.mkdir(self._chgsockdir)
720 os.mkdir(self._chgsockdir)
721
721
722 def run(self, result):
722 def run(self, result):
723 """Run this test and report results against a TestResult instance."""
723 """Run this test and report results against a TestResult instance."""
724 # This function is extremely similar to unittest.TestCase.run(). Once
724 # This function is extremely similar to unittest.TestCase.run(). Once
725 # we require Python 2.7 (or at least its version of unittest), this
725 # we require Python 2.7 (or at least its version of unittest), this
726 # function can largely go away.
726 # function can largely go away.
727 self._result = result
727 self._result = result
728 result.startTest(self)
728 result.startTest(self)
729 try:
729 try:
730 try:
730 try:
731 self.setUp()
731 self.setUp()
732 except (KeyboardInterrupt, SystemExit):
732 except (KeyboardInterrupt, SystemExit):
733 self._aborted = True
733 self._aborted = True
734 raise
734 raise
735 except Exception:
735 except Exception:
736 result.addError(self, sys.exc_info())
736 result.addError(self, sys.exc_info())
737 return
737 return
738
738
739 success = False
739 success = False
740 try:
740 try:
741 self.runTest()
741 self.runTest()
742 except KeyboardInterrupt:
742 except KeyboardInterrupt:
743 self._aborted = True
743 self._aborted = True
744 raise
744 raise
745 except unittest.SkipTest as e:
745 except unittest.SkipTest as e:
746 result.addSkip(self, str(e))
746 result.addSkip(self, str(e))
747 # The base class will have already counted this as a
747 # The base class will have already counted this as a
748 # test we "ran", but we want to exclude skipped tests
748 # test we "ran", but we want to exclude skipped tests
749 # from those we count towards those run.
749 # from those we count towards those run.
750 result.testsRun -= 1
750 result.testsRun -= 1
751 except self.failureException as e:
751 except self.failureException as e:
752 # This differs from unittest in that we don't capture
752 # This differs from unittest in that we don't capture
753 # the stack trace. This is for historical reasons and
753 # the stack trace. This is for historical reasons and
754 # this decision could be revisited in the future,
754 # this decision could be revisited in the future,
755 # especially for PythonTest instances.
755 # especially for PythonTest instances.
756 if result.addFailure(self, str(e)):
756 if result.addFailure(self, str(e)):
757 success = True
757 success = True
758 except Exception:
758 except Exception:
759 result.addError(self, sys.exc_info())
759 result.addError(self, sys.exc_info())
760 else:
760 else:
761 success = True
761 success = True
762
762
763 try:
763 try:
764 self.tearDown()
764 self.tearDown()
765 except (KeyboardInterrupt, SystemExit):
765 except (KeyboardInterrupt, SystemExit):
766 self._aborted = True
766 self._aborted = True
767 raise
767 raise
768 except Exception:
768 except Exception:
769 result.addError(self, sys.exc_info())
769 result.addError(self, sys.exc_info())
770 success = False
770 success = False
771
771
772 if success:
772 if success:
773 result.addSuccess(self)
773 result.addSuccess(self)
774 finally:
774 finally:
775 result.stopTest(self, interrupted=self._aborted)
775 result.stopTest(self, interrupted=self._aborted)
776
776
777 def runTest(self):
777 def runTest(self):
778 """Run this test instance.
778 """Run this test instance.
779
779
780 This will return a tuple describing the result of the test.
780 This will return a tuple describing the result of the test.
781 """
781 """
782 env = self._getenv()
782 env = self._getenv()
783 self._genrestoreenv(env)
783 self._genrestoreenv(env)
784 self._daemonpids.append(env['DAEMON_PIDS'])
784 self._daemonpids.append(env['DAEMON_PIDS'])
785 self._createhgrc(env['HGRCPATH'])
785 self._createhgrc(env['HGRCPATH'])
786
786
787 vlog('# Test', self.name)
787 vlog('# Test', self.name)
788
788
789 ret, out = self._run(env)
789 ret, out = self._run(env)
790 self._finished = True
790 self._finished = True
791 self._ret = ret
791 self._ret = ret
792 self._out = out
792 self._out = out
793
793
794 def describe(ret):
794 def describe(ret):
795 if ret < 0:
795 if ret < 0:
796 return 'killed by signal: %d' % -ret
796 return 'killed by signal: %d' % -ret
797 return 'returned error code %d' % ret
797 return 'returned error code %d' % ret
798
798
799 self._skipped = False
799 self._skipped = False
800
800
801 if ret == self.SKIPPED_STATUS:
801 if ret == self.SKIPPED_STATUS:
802 if out is None: # Debug mode, nothing to parse.
802 if out is None: # Debug mode, nothing to parse.
803 missing = ['unknown']
803 missing = ['unknown']
804 failed = None
804 failed = None
805 else:
805 else:
806 missing, failed = TTest.parsehghaveoutput(out)
806 missing, failed = TTest.parsehghaveoutput(out)
807
807
808 if not missing:
808 if not missing:
809 missing = ['skipped']
809 missing = ['skipped']
810
810
811 if failed:
811 if failed:
812 self.fail('hg have failed checking for %s' % failed[-1])
812 self.fail('hg have failed checking for %s' % failed[-1])
813 else:
813 else:
814 self._skipped = True
814 self._skipped = True
815 raise unittest.SkipTest(missing[-1])
815 raise unittest.SkipTest(missing[-1])
816 elif ret == 'timeout':
816 elif ret == 'timeout':
817 self.fail('timed out')
817 self.fail('timed out')
818 elif ret is False:
818 elif ret is False:
819 self.fail('no result code from test')
819 self.fail('no result code from test')
820 elif out != self._refout:
820 elif out != self._refout:
821 # Diff generation may rely on written .err file.
821 # Diff generation may rely on written .err file.
822 if (ret != 0 or out != self._refout) and not self._skipped \
822 if (ret != 0 or out != self._refout) and not self._skipped \
823 and not self._debug:
823 and not self._debug:
824 f = open(self.errpath, 'wb')
824 f = open(self.errpath, 'wb')
825 for line in out:
825 for line in out:
826 f.write(line)
826 f.write(line)
827 f.close()
827 f.close()
828
828
829 # The result object handles diff calculation for us.
829 # The result object handles diff calculation for us.
830 if self._result.addOutputMismatch(self, ret, out, self._refout):
830 if self._result.addOutputMismatch(self, ret, out, self._refout):
831 # change was accepted, skip failing
831 # change was accepted, skip failing
832 return
832 return
833
833
834 if ret:
834 if ret:
835 msg = 'output changed and ' + describe(ret)
835 msg = 'output changed and ' + describe(ret)
836 else:
836 else:
837 msg = 'output changed'
837 msg = 'output changed'
838
838
839 self.fail(msg)
839 self.fail(msg)
840 elif ret:
840 elif ret:
841 self.fail(describe(ret))
841 self.fail(describe(ret))
842
842
843 def tearDown(self):
843 def tearDown(self):
844 """Tasks to perform after run()."""
844 """Tasks to perform after run()."""
845 for entry in self._daemonpids:
845 for entry in self._daemonpids:
846 killdaemons(entry)
846 killdaemons(entry)
847 self._daemonpids = []
847 self._daemonpids = []
848
848
849 if self._keeptmpdir:
849 if self._keeptmpdir:
850 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
850 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
851 (self._testtmp.decode('utf-8'),
851 (self._testtmp.decode('utf-8'),
852 self._threadtmp.decode('utf-8')))
852 self._threadtmp.decode('utf-8')))
853 else:
853 else:
854 shutil.rmtree(self._testtmp, True)
854 shutil.rmtree(self._testtmp, True)
855 shutil.rmtree(self._threadtmp, True)
855 shutil.rmtree(self._threadtmp, True)
856
856
857 if self._usechg:
857 if self._usechg:
858 # chgservers will stop automatically after they find the socket
858 # chgservers will stop automatically after they find the socket
859 # files are deleted
859 # files are deleted
860 shutil.rmtree(self._chgsockdir, True)
860 shutil.rmtree(self._chgsockdir, True)
861
861
862 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
862 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
863 and not self._debug and self._out:
863 and not self._debug and self._out:
864 f = open(self.errpath, 'wb')
864 f = open(self.errpath, 'wb')
865 for line in self._out:
865 for line in self._out:
866 f.write(line)
866 f.write(line)
867 f.close()
867 f.close()
868
868
869 vlog("# Ret was:", self._ret, '(%s)' % self.name)
869 vlog("# Ret was:", self._ret, '(%s)' % self.name)
870
870
871 def _run(self, env):
871 def _run(self, env):
872 # This should be implemented in child classes to run tests.
872 # This should be implemented in child classes to run tests.
873 raise unittest.SkipTest('unknown test type')
873 raise unittest.SkipTest('unknown test type')
874
874
875 def abort(self):
875 def abort(self):
876 """Terminate execution of this test."""
876 """Terminate execution of this test."""
877 self._aborted = True
877 self._aborted = True
878
878
879 def _portmap(self, i):
879 def _portmap(self, i):
880 offset = b'' if i == 0 else b'%d' % i
880 offset = b'' if i == 0 else b'%d' % i
881 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
881 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
882
882
883 def _getreplacements(self):
883 def _getreplacements(self):
884 """Obtain a mapping of text replacements to apply to test output.
884 """Obtain a mapping of text replacements to apply to test output.
885
885
886 Test output needs to be normalized so it can be compared to expected
886 Test output needs to be normalized so it can be compared to expected
887 output. This function defines how some of that normalization will
887 output. This function defines how some of that normalization will
888 occur.
888 occur.
889 """
889 """
890 r = [
890 r = [
891 # This list should be parallel to defineport in _getenv
891 # This list should be parallel to defineport in _getenv
892 self._portmap(0),
892 self._portmap(0),
893 self._portmap(1),
893 self._portmap(1),
894 self._portmap(2),
894 self._portmap(2),
895 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
895 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
896 br'\1 (glob)'),
896 br'\1 (glob)'),
897 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
897 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
898 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
898 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
899 ]
899 ]
900 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
900 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
901
901
902 return r
902 return r
903
903
904 def _escapepath(self, p):
904 def _escapepath(self, p):
905 if os.name == 'nt':
905 if os.name == 'nt':
906 return (
906 return (
907 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
907 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
908 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
908 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
909 for c in p))
909 for c in p))
910 )
910 )
911 else:
911 else:
912 return re.escape(p)
912 return re.escape(p)
913
913
914 def _localip(self):
914 def _localip(self):
915 if self._useipv6:
915 if self._useipv6:
916 return b'::1'
916 return b'::1'
917 else:
917 else:
918 return b'127.0.0.1'
918 return b'127.0.0.1'
919
919
920 def _genrestoreenv(self, testenv):
920 def _genrestoreenv(self, testenv):
921 """Generate a script that can be used by tests to restore the original
921 """Generate a script that can be used by tests to restore the original
922 environment."""
922 environment."""
923 # Put the restoreenv script inside self._threadtmp
923 # Put the restoreenv script inside self._threadtmp
924 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
924 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
925 testenv['HGTEST_RESTOREENV'] = scriptpath
925 testenv['HGTEST_RESTOREENV'] = scriptpath
926
926
927 # Only restore environment variable names that the shell allows
927 # Only restore environment variable names that the shell allows
928 # us to export.
928 # us to export.
929 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
929 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
930
930
931 # Do not restore these variables; otherwise tests would fail.
931 # Do not restore these variables; otherwise tests would fail.
932 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
932 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
933
933
934 with open(scriptpath, 'w') as envf:
934 with open(scriptpath, 'w') as envf:
935 for name, value in origenviron.items():
935 for name, value in origenviron.items():
936 if not name_regex.match(name):
936 if not name_regex.match(name):
937 # Skip environment variables with unusual names not
937 # Skip environment variables with unusual names not
938 # allowed by most shells.
938 # allowed by most shells.
939 continue
939 continue
940 if name in reqnames:
940 if name in reqnames:
941 continue
941 continue
942 envf.write('%s=%s\n' % (name, shellquote(value)))
942 envf.write('%s=%s\n' % (name, shellquote(value)))
943
943
944 for name in testenv:
944 for name in testenv:
945 if name in origenviron or name in reqnames:
945 if name in origenviron or name in reqnames:
946 continue
946 continue
947 envf.write('unset %s\n' % (name,))
947 envf.write('unset %s\n' % (name,))
948
948
949 def _getenv(self):
949 def _getenv(self):
950 """Obtain environment variables to use during test execution."""
950 """Obtain environment variables to use during test execution."""
951 def defineport(i):
951 def defineport(i):
952 offset = '' if i == 0 else '%s' % i
952 offset = '' if i == 0 else '%s' % i
953 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
953 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
954 env = os.environ.copy()
954 env = os.environ.copy()
955 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
955 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase')
956 env['HGEMITWARNINGS'] = '1'
956 env['HGEMITWARNINGS'] = '1'
957 env['TESTTMP'] = self._testtmp
957 env['TESTTMP'] = self._testtmp
958 env['HOME'] = self._testtmp
958 env['HOME'] = self._testtmp
959 # This number should match portneeded in _getport
959 # This number should match portneeded in _getport
960 for port in xrange(3):
960 for port in xrange(3):
961 # This list should be parallel to _portmap in _getreplacements
961 # This list should be parallel to _portmap in _getreplacements
962 defineport(port)
962 defineport(port)
963 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
963 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
964 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
964 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
965 env["HGEDITOR"] = ('"' + sys.executable + '"'
965 env["HGEDITOR"] = ('"' + sys.executable + '"'
966 + ' -c "import sys; sys.exit(0)"')
966 + ' -c "import sys; sys.exit(0)"')
967 env["HGMERGE"] = "internal:merge"
967 env["HGMERGE"] = "internal:merge"
968 env["HGUSER"] = "test"
968 env["HGUSER"] = "test"
969 env["HGENCODING"] = "ascii"
969 env["HGENCODING"] = "ascii"
970 env["HGENCODINGMODE"] = "strict"
970 env["HGENCODINGMODE"] = "strict"
971 env['HGIPV6'] = str(int(self._useipv6))
971 env['HGIPV6'] = str(int(self._useipv6))
972
972
973 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
973 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
974 # IP addresses.
974 # IP addresses.
975 env['LOCALIP'] = self._localip()
975 env['LOCALIP'] = self._localip()
976
976
977 # Reset some environment variables to well-known values so that
977 # Reset some environment variables to well-known values so that
978 # the tests produce repeatable output.
978 # the tests produce repeatable output.
979 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
979 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
980 env['TZ'] = 'GMT'
980 env['TZ'] = 'GMT'
981 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
981 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
982 env['COLUMNS'] = '80'
982 env['COLUMNS'] = '80'
983 env['TERM'] = 'xterm'
983 env['TERM'] = 'xterm'
984
984
985 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
985 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
986 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
986 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
987 'NO_PROXY CHGDEBUG').split():
987 'NO_PROXY CHGDEBUG').split():
988 if k in env:
988 if k in env:
989 del env[k]
989 del env[k]
990
990
991 # unset env related to hooks
991 # unset env related to hooks
992 for k in env.keys():
992 for k in env.keys():
993 if k.startswith('HG_'):
993 if k.startswith('HG_'):
994 del env[k]
994 del env[k]
995
995
996 if self._usechg:
996 if self._usechg:
997 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
997 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
998
998
999 return env
999 return env
1000
1000
1001 def _createhgrc(self, path):
1001 def _createhgrc(self, path):
1002 """Create an hgrc file for this test."""
1002 """Create an hgrc file for this test."""
1003 hgrc = open(path, 'wb')
1003 hgrc = open(path, 'wb')
1004 hgrc.write(b'[ui]\n')
1004 hgrc.write(b'[ui]\n')
1005 hgrc.write(b'slash = True\n')
1005 hgrc.write(b'slash = True\n')
1006 hgrc.write(b'interactive = False\n')
1006 hgrc.write(b'interactive = False\n')
1007 hgrc.write(b'mergemarkers = detailed\n')
1007 hgrc.write(b'mergemarkers = detailed\n')
1008 hgrc.write(b'promptecho = True\n')
1008 hgrc.write(b'promptecho = True\n')
1009 hgrc.write(b'[defaults]\n')
1009 hgrc.write(b'[defaults]\n')
1010 hgrc.write(b'[devel]\n')
1010 hgrc.write(b'[devel]\n')
1011 hgrc.write(b'all-warnings = true\n')
1011 hgrc.write(b'all-warnings = true\n')
1012 hgrc.write(b'default-date = 0 0\n')
1012 hgrc.write(b'default-date = 0 0\n')
1013 hgrc.write(b'[largefiles]\n')
1013 hgrc.write(b'[largefiles]\n')
1014 hgrc.write(b'usercache = %s\n' %
1014 hgrc.write(b'usercache = %s\n' %
1015 (os.path.join(self._testtmp, b'.cache/largefiles')))
1015 (os.path.join(self._testtmp, b'.cache/largefiles')))
1016 hgrc.write(b'[web]\n')
1016 hgrc.write(b'[web]\n')
1017 hgrc.write(b'address = localhost\n')
1017 hgrc.write(b'address = localhost\n')
1018 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1018 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1019
1019
1020 for opt in self._extraconfigopts:
1020 for opt in self._extraconfigopts:
1021 section, key = opt.split('.', 1)
1021 section, key = opt.split('.', 1)
1022 assert '=' in key, ('extra config opt %s must '
1022 assert '=' in key, ('extra config opt %s must '
1023 'have an = for assignment' % opt)
1023 'have an = for assignment' % opt)
1024 hgrc.write(b'[%s]\n%s\n' % (section, key))
1024 hgrc.write(b'[%s]\n%s\n' % (section, key))
1025 hgrc.close()
1025 hgrc.close()
1026
1026
1027 def fail(self, msg):
1027 def fail(self, msg):
1028 # unittest differentiates between errored and failed.
1028 # unittest differentiates between errored and failed.
1029 # Failed is denoted by AssertionError (by default at least).
1029 # Failed is denoted by AssertionError (by default at least).
1030 raise AssertionError(msg)
1030 raise AssertionError(msg)
1031
1031
1032 def _runcommand(self, cmd, env, normalizenewlines=False):
1032 def _runcommand(self, cmd, env, normalizenewlines=False):
1033 """Run command in a sub-process, capturing the output (stdout and
1033 """Run command in a sub-process, capturing the output (stdout and
1034 stderr).
1034 stderr).
1035
1035
1036 Return a tuple (exitcode, output). output is None in debug mode.
1036 Return a tuple (exitcode, output). output is None in debug mode.
1037 """
1037 """
1038 if self._debug:
1038 if self._debug:
1039 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
1039 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
1040 env=env)
1040 env=env)
1041 ret = proc.wait()
1041 ret = proc.wait()
1042 return (ret, None)
1042 return (ret, None)
1043
1043
1044 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1044 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1045 def cleanup():
1045 def cleanup():
1046 terminate(proc)
1046 terminate(proc)
1047 ret = proc.wait()
1047 ret = proc.wait()
1048 if ret == 0:
1048 if ret == 0:
1049 ret = signal.SIGTERM << 8
1049 ret = signal.SIGTERM << 8
1050 killdaemons(env['DAEMON_PIDS'])
1050 killdaemons(env['DAEMON_PIDS'])
1051 return ret
1051 return ret
1052
1052
1053 output = ''
1053 output = ''
1054 proc.tochild.close()
1054 proc.tochild.close()
1055
1055
1056 try:
1056 try:
1057 output = proc.fromchild.read()
1057 output = proc.fromchild.read()
1058 except KeyboardInterrupt:
1058 except KeyboardInterrupt:
1059 vlog('# Handling keyboard interrupt')
1059 vlog('# Handling keyboard interrupt')
1060 cleanup()
1060 cleanup()
1061 raise
1061 raise
1062
1062
1063 ret = proc.wait()
1063 ret = proc.wait()
1064 if wifexited(ret):
1064 if wifexited(ret):
1065 ret = os.WEXITSTATUS(ret)
1065 ret = os.WEXITSTATUS(ret)
1066
1066
1067 if proc.timeout:
1067 if proc.timeout:
1068 ret = 'timeout'
1068 ret = 'timeout'
1069
1069
1070 if ret:
1070 if ret:
1071 killdaemons(env['DAEMON_PIDS'])
1071 killdaemons(env['DAEMON_PIDS'])
1072
1072
1073 for s, r in self._getreplacements():
1073 for s, r in self._getreplacements():
1074 output = re.sub(s, r, output)
1074 output = re.sub(s, r, output)
1075
1075
1076 if normalizenewlines:
1076 if normalizenewlines:
1077 output = output.replace('\r\n', '\n')
1077 output = output.replace('\r\n', '\n')
1078
1078
1079 return ret, output.splitlines(True)
1079 return ret, output.splitlines(True)
1080
1080
1081 class PythonTest(Test):
1081 class PythonTest(Test):
1082 """A Python-based test."""
1082 """A Python-based test."""
1083
1083
1084 @property
1084 @property
1085 def refpath(self):
1085 def refpath(self):
1086 return os.path.join(self._testdir, b'%s.out' % self.bname)
1086 return os.path.join(self._testdir, b'%s.out' % self.bname)
1087
1087
1088 def _run(self, env):
1088 def _run(self, env):
1089 py3kswitch = self._py3kwarnings and b' -3' or b''
1089 py3kswitch = self._py3kwarnings and b' -3' or b''
1090 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1090 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1091 vlog("# Running", cmd)
1091 vlog("# Running", cmd)
1092 normalizenewlines = os.name == 'nt'
1092 normalizenewlines = os.name == 'nt'
1093 result = self._runcommand(cmd, env,
1093 result = self._runcommand(cmd, env,
1094 normalizenewlines=normalizenewlines)
1094 normalizenewlines=normalizenewlines)
1095 if self._aborted:
1095 if self._aborted:
1096 raise KeyboardInterrupt()
1096 raise KeyboardInterrupt()
1097
1097
1098 return result
1098 return result
1099
1099
1100 # Some glob patterns apply only in some circumstances, so the script
1100 # Some glob patterns apply only in some circumstances, so the script
1101 # might want to remove (glob) annotations that otherwise should be
1101 # might want to remove (glob) annotations that otherwise should be
1102 # retained.
1102 # retained.
1103 checkcodeglobpats = [
1103 checkcodeglobpats = [
1104 # On Windows it looks like \ doesn't require a (glob), but we know
1104 # On Windows it looks like \ doesn't require a (glob), but we know
1105 # better.
1105 # better.
1106 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1106 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1107 re.compile(br'^moving \S+/.*[^)]$'),
1107 re.compile(br'^moving \S+/.*[^)]$'),
1108 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1108 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1109 # Not all platforms have 127.0.0.1 as loopback (though most do),
1109 # Not all platforms have 127.0.0.1 as loopback (though most do),
1110 # so we always glob that too.
1110 # so we always glob that too.
1111 re.compile(br'.*\$LOCALIP.*$'),
1111 re.compile(br'.*\$LOCALIP.*$'),
1112 ]
1112 ]
1113
1113
1114 bchr = chr
1114 bchr = chr
1115 if PYTHON3:
1115 if PYTHON3:
1116 bchr = lambda x: bytes([x])
1116 bchr = lambda x: bytes([x])
1117
1117
1118 class TTest(Test):
1118 class TTest(Test):
1119 """A "t test" is a test backed by a .t file."""
1119 """A "t test" is a test backed by a .t file."""
1120
1120
1121 SKIPPED_PREFIX = b'skipped: '
1121 SKIPPED_PREFIX = b'skipped: '
1122 FAILED_PREFIX = b'hghave check failed: '
1122 FAILED_PREFIX = b'hghave check failed: '
1123 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1123 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1124
1124
1125 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1125 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1126 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1126 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1127 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1127 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1128
1128
1129 def __init__(self, path, *args, **kwds):
1129 def __init__(self, path, *args, **kwds):
1130 # accept an extra "case" parameter
1130 # accept an extra "case" parameter
1131 case = None
1131 case = None
1132 if 'case' in kwds:
1132 if 'case' in kwds:
1133 case = kwds.pop('case')
1133 case = kwds.pop('case')
1134 self._case = case
1134 self._case = case
1135 self._allcases = parsettestcases(path)
1135 self._allcases = parsettestcases(path)
1136 super(TTest, self).__init__(path, *args, **kwds)
1136 super(TTest, self).__init__(path, *args, **kwds)
1137 if case:
1137 if case:
1138 self.name = '%s (case %s)' % (self.name, _strpath(case))
1138 self.name = '%s (case %s)' % (self.name, _strpath(case))
1139 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1139 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1140 self._tmpname += b'-%s' % case
1140 self._tmpname += b'-%s' % case
1141
1141
1142 @property
1142 @property
1143 def refpath(self):
1143 def refpath(self):
1144 return os.path.join(self._testdir, self.bname)
1144 return os.path.join(self._testdir, self.bname)
1145
1145
1146 def _run(self, env):
1146 def _run(self, env):
1147 f = open(self.path, 'rb')
1147 f = open(self.path, 'rb')
1148 lines = f.readlines()
1148 lines = f.readlines()
1149 f.close()
1149 f.close()
1150
1150
1151 # .t file is both reference output and the test input, keep reference
1151 # .t file is both reference output and the test input, keep reference
1152 # output updated with the the test input. This avoids some race
1152 # output updated with the the test input. This avoids some race
1153 # conditions where the reference output does not match the actual test.
1153 # conditions where the reference output does not match the actual test.
1154 if self._refout is not None:
1154 if self._refout is not None:
1155 self._refout = lines
1155 self._refout = lines
1156
1156
1157 salt, script, after, expected = self._parsetest(lines)
1157 salt, script, after, expected = self._parsetest(lines)
1158
1158
1159 # Write out the generated script.
1159 # Write out the generated script.
1160 fname = b'%s.sh' % self._testtmp
1160 fname = b'%s.sh' % self._testtmp
1161 f = open(fname, 'wb')
1161 f = open(fname, 'wb')
1162 for l in script:
1162 for l in script:
1163 f.write(l)
1163 f.write(l)
1164 f.close()
1164 f.close()
1165
1165
1166 cmd = b'%s "%s"' % (self._shell, fname)
1166 cmd = b'%s "%s"' % (self._shell, fname)
1167 vlog("# Running", cmd)
1167 vlog("# Running", cmd)
1168
1168
1169 exitcode, output = self._runcommand(cmd, env)
1169 exitcode, output = self._runcommand(cmd, env)
1170
1170
1171 if self._aborted:
1171 if self._aborted:
1172 raise KeyboardInterrupt()
1172 raise KeyboardInterrupt()
1173
1173
1174 # Do not merge output if skipped. Return hghave message instead.
1174 # Do not merge output if skipped. Return hghave message instead.
1175 # Similarly, with --debug, output is None.
1175 # Similarly, with --debug, output is None.
1176 if exitcode == self.SKIPPED_STATUS or output is None:
1176 if exitcode == self.SKIPPED_STATUS or output is None:
1177 return exitcode, output
1177 return exitcode, output
1178
1178
1179 return self._processoutput(exitcode, output, salt, after, expected)
1179 return self._processoutput(exitcode, output, salt, after, expected)
1180
1180
1181 def _hghave(self, reqs):
1181 def _hghave(self, reqs):
1182 # TODO do something smarter when all other uses of hghave are gone.
1182 # TODO do something smarter when all other uses of hghave are gone.
1183 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1183 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1184 tdir = runtestdir.replace(b'\\', b'/')
1184 tdir = runtestdir.replace(b'\\', b'/')
1185 proc = Popen4(b'%s -c "%s/hghave %s"' %
1185 proc = Popen4(b'%s -c "%s/hghave %s"' %
1186 (self._shell, tdir, b' '.join(reqs)),
1186 (self._shell, tdir, b' '.join(reqs)),
1187 self._testtmp, 0, self._getenv())
1187 self._testtmp, 0, self._getenv())
1188 stdout, stderr = proc.communicate()
1188 stdout, stderr = proc.communicate()
1189 ret = proc.wait()
1189 ret = proc.wait()
1190 if wifexited(ret):
1190 if wifexited(ret):
1191 ret = os.WEXITSTATUS(ret)
1191 ret = os.WEXITSTATUS(ret)
1192 if ret == 2:
1192 if ret == 2:
1193 print(stdout.decode('utf-8'))
1193 print(stdout.decode('utf-8'))
1194 sys.exit(1)
1194 sys.exit(1)
1195
1195
1196 if ret != 0:
1196 if ret != 0:
1197 return False, stdout
1197 return False, stdout
1198
1198
1199 if 'slow' in reqs:
1199 if 'slow' in reqs:
1200 self._timeout = self._slowtimeout
1200 self._timeout = self._slowtimeout
1201 return True, None
1201 return True, None
1202
1202
1203 def _iftest(self, args):
1203 def _iftest(self, args):
1204 # implements "#if"
1204 # implements "#if"
1205 reqs = []
1205 reqs = []
1206 for arg in args:
1206 for arg in args:
1207 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1207 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1208 if arg[3:] == self._case:
1208 if arg[3:] == self._case:
1209 return False
1209 return False
1210 elif arg in self._allcases:
1210 elif arg in self._allcases:
1211 if arg != self._case:
1211 if arg != self._case:
1212 return False
1212 return False
1213 else:
1213 else:
1214 reqs.append(arg)
1214 reqs.append(arg)
1215 return self._hghave(reqs)[0]
1215 return self._hghave(reqs)[0]
1216
1216
1217 def _parsetest(self, lines):
1217 def _parsetest(self, lines):
1218 # We generate a shell script which outputs unique markers to line
1218 # We generate a shell script which outputs unique markers to line
1219 # up script results with our source. These markers include input
1219 # up script results with our source. These markers include input
1220 # line number and the last return code.
1220 # line number and the last return code.
1221 salt = b"SALT%d" % time.time()
1221 salt = b"SALT%d" % time.time()
1222 def addsalt(line, inpython):
1222 def addsalt(line, inpython):
1223 if inpython:
1223 if inpython:
1224 script.append(b'%s %d 0\n' % (salt, line))
1224 script.append(b'%s %d 0\n' % (salt, line))
1225 else:
1225 else:
1226 script.append(b'echo %s %d $?\n' % (salt, line))
1226 script.append(b'echo %s %d $?\n' % (salt, line))
1227
1227
1228 script = []
1228 script = []
1229
1229
1230 # After we run the shell script, we re-unify the script output
1230 # After we run the shell script, we re-unify the script output
1231 # with non-active parts of the source, with synchronization by our
1231 # with non-active parts of the source, with synchronization by our
1232 # SALT line number markers. The after table contains the non-active
1232 # SALT line number markers. The after table contains the non-active
1233 # components, ordered by line number.
1233 # components, ordered by line number.
1234 after = {}
1234 after = {}
1235
1235
1236 # Expected shell script output.
1236 # Expected shell script output.
1237 expected = {}
1237 expected = {}
1238
1238
1239 pos = prepos = -1
1239 pos = prepos = -1
1240
1240
1241 # True or False when in a true or false conditional section
1241 # True or False when in a true or false conditional section
1242 skipping = None
1242 skipping = None
1243
1243
1244 # We keep track of whether or not we're in a Python block so we
1244 # We keep track of whether or not we're in a Python block so we
1245 # can generate the surrounding doctest magic.
1245 # can generate the surrounding doctest magic.
1246 inpython = False
1246 inpython = False
1247
1247
1248 if self._debug:
1248 if self._debug:
1249 script.append(b'set -x\n')
1249 script.append(b'set -x\n')
1250 if self._hgcommand != b'hg':
1250 if self._hgcommand != b'hg':
1251 script.append(b'alias hg="%s"\n' % self._hgcommand)
1251 script.append(b'alias hg="%s"\n' % self._hgcommand)
1252 if os.getenv('MSYSTEM'):
1252 if os.getenv('MSYSTEM'):
1253 script.append(b'alias pwd="pwd -W"\n')
1253 script.append(b'alias pwd="pwd -W"\n')
1254
1254
1255 n = 0
1255 n = 0
1256 for n, l in enumerate(lines):
1256 for n, l in enumerate(lines):
1257 if not l.endswith(b'\n'):
1257 if not l.endswith(b'\n'):
1258 l += b'\n'
1258 l += b'\n'
1259 if l.startswith(b'#require'):
1259 if l.startswith(b'#require'):
1260 lsplit = l.split()
1260 lsplit = l.split()
1261 if len(lsplit) < 2 or lsplit[0] != b'#require':
1261 if len(lsplit) < 2 or lsplit[0] != b'#require':
1262 after.setdefault(pos, []).append(' !!! invalid #require\n')
1262 after.setdefault(pos, []).append(' !!! invalid #require\n')
1263 haveresult, message = self._hghave(lsplit[1:])
1263 haveresult, message = self._hghave(lsplit[1:])
1264 if not haveresult:
1264 if not haveresult:
1265 script = [b'echo "%s"\nexit 80\n' % message]
1265 script = [b'echo "%s"\nexit 80\n' % message]
1266 break
1266 break
1267 after.setdefault(pos, []).append(l)
1267 after.setdefault(pos, []).append(l)
1268 elif l.startswith(b'#if'):
1268 elif l.startswith(b'#if'):
1269 lsplit = l.split()
1269 lsplit = l.split()
1270 if len(lsplit) < 2 or lsplit[0] != b'#if':
1270 if len(lsplit) < 2 or lsplit[0] != b'#if':
1271 after.setdefault(pos, []).append(' !!! invalid #if\n')
1271 after.setdefault(pos, []).append(' !!! invalid #if\n')
1272 if skipping is not None:
1272 if skipping is not None:
1273 after.setdefault(pos, []).append(' !!! nested #if\n')
1273 after.setdefault(pos, []).append(' !!! nested #if\n')
1274 skipping = not self._iftest(lsplit[1:])
1274 skipping = not self._iftest(lsplit[1:])
1275 after.setdefault(pos, []).append(l)
1275 after.setdefault(pos, []).append(l)
1276 elif l.startswith(b'#else'):
1276 elif l.startswith(b'#else'):
1277 if skipping is None:
1277 if skipping is None:
1278 after.setdefault(pos, []).append(' !!! missing #if\n')
1278 after.setdefault(pos, []).append(' !!! missing #if\n')
1279 skipping = not skipping
1279 skipping = not skipping
1280 after.setdefault(pos, []).append(l)
1280 after.setdefault(pos, []).append(l)
1281 elif l.startswith(b'#endif'):
1281 elif l.startswith(b'#endif'):
1282 if skipping is None:
1282 if skipping is None:
1283 after.setdefault(pos, []).append(' !!! missing #if\n')
1283 after.setdefault(pos, []).append(' !!! missing #if\n')
1284 skipping = None
1284 skipping = None
1285 after.setdefault(pos, []).append(l)
1285 after.setdefault(pos, []).append(l)
1286 elif skipping:
1286 elif skipping:
1287 after.setdefault(pos, []).append(l)
1287 after.setdefault(pos, []).append(l)
1288 elif l.startswith(b' >>> '): # python inlines
1288 elif l.startswith(b' >>> '): # python inlines
1289 after.setdefault(pos, []).append(l)
1289 after.setdefault(pos, []).append(l)
1290 prepos = pos
1290 prepos = pos
1291 pos = n
1291 pos = n
1292 if not inpython:
1292 if not inpython:
1293 # We've just entered a Python block. Add the header.
1293 # We've just entered a Python block. Add the header.
1294 inpython = True
1294 inpython = True
1295 addsalt(prepos, False) # Make sure we report the exit code.
1295 addsalt(prepos, False) # Make sure we report the exit code.
1296 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1296 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1297 addsalt(n, True)
1297 addsalt(n, True)
1298 script.append(l[2:])
1298 script.append(l[2:])
1299 elif l.startswith(b' ... '): # python inlines
1299 elif l.startswith(b' ... '): # python inlines
1300 after.setdefault(prepos, []).append(l)
1300 after.setdefault(prepos, []).append(l)
1301 script.append(l[2:])
1301 script.append(l[2:])
1302 elif l.startswith(b' $ '): # commands
1302 elif l.startswith(b' $ '): # commands
1303 if inpython:
1303 if inpython:
1304 script.append(b'EOF\n')
1304 script.append(b'EOF\n')
1305 inpython = False
1305 inpython = False
1306 after.setdefault(pos, []).append(l)
1306 after.setdefault(pos, []).append(l)
1307 prepos = pos
1307 prepos = pos
1308 pos = n
1308 pos = n
1309 addsalt(n, False)
1309 addsalt(n, False)
1310 cmd = l[4:].split()
1310 cmd = l[4:].split()
1311 if len(cmd) == 2 and cmd[0] == b'cd':
1311 if len(cmd) == 2 and cmd[0] == b'cd':
1312 l = b' $ cd %s || exit 1\n' % cmd[1]
1312 l = b' $ cd %s || exit 1\n' % cmd[1]
1313 script.append(l[4:])
1313 script.append(l[4:])
1314 elif l.startswith(b' > '): # continuations
1314 elif l.startswith(b' > '): # continuations
1315 after.setdefault(prepos, []).append(l)
1315 after.setdefault(prepos, []).append(l)
1316 script.append(l[4:])
1316 script.append(l[4:])
1317 elif l.startswith(b' '): # results
1317 elif l.startswith(b' '): # results
1318 # Queue up a list of expected results.
1318 # Queue up a list of expected results.
1319 expected.setdefault(pos, []).append(l[2:])
1319 expected.setdefault(pos, []).append(l[2:])
1320 else:
1320 else:
1321 if inpython:
1321 if inpython:
1322 script.append(b'EOF\n')
1322 script.append(b'EOF\n')
1323 inpython = False
1323 inpython = False
1324 # Non-command/result. Queue up for merged output.
1324 # Non-command/result. Queue up for merged output.
1325 after.setdefault(pos, []).append(l)
1325 after.setdefault(pos, []).append(l)
1326
1326
1327 if inpython:
1327 if inpython:
1328 script.append(b'EOF\n')
1328 script.append(b'EOF\n')
1329 if skipping is not None:
1329 if skipping is not None:
1330 after.setdefault(pos, []).append(' !!! missing #endif\n')
1330 after.setdefault(pos, []).append(' !!! missing #endif\n')
1331 addsalt(n + 1, False)
1331 addsalt(n + 1, False)
1332
1332
1333 return salt, script, after, expected
1333 return salt, script, after, expected
1334
1334
1335 def _processoutput(self, exitcode, output, salt, after, expected):
1335 def _processoutput(self, exitcode, output, salt, after, expected):
1336 # Merge the script output back into a unified test.
1336 # Merge the script output back into a unified test.
1337 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1337 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1338 if exitcode != 0:
1338 if exitcode != 0:
1339 warnonly = 3
1339 warnonly = 3
1340
1340
1341 pos = -1
1341 pos = -1
1342 postout = []
1342 postout = []
1343 for l in output:
1343 for l in output:
1344 lout, lcmd = l, None
1344 lout, lcmd = l, None
1345 if salt in l:
1345 if salt in l:
1346 lout, lcmd = l.split(salt, 1)
1346 lout, lcmd = l.split(salt, 1)
1347
1347
1348 while lout:
1348 while lout:
1349 if not lout.endswith(b'\n'):
1349 if not lout.endswith(b'\n'):
1350 lout += b' (no-eol)\n'
1350 lout += b' (no-eol)\n'
1351
1351
1352 # Find the expected output at the current position.
1352 # Find the expected output at the current position.
1353 els = [None]
1353 els = [None]
1354 if expected.get(pos, None):
1354 if expected.get(pos, None):
1355 els = expected[pos]
1355 els = expected[pos]
1356
1356
1357 i = 0
1357 i = 0
1358 optional = []
1358 optional = []
1359 while i < len(els):
1359 while i < len(els):
1360 el = els[i]
1360 el = els[i]
1361
1361
1362 r = TTest.linematch(el, lout)
1362 r = self.linematch(el, lout)
1363 if isinstance(r, str):
1363 if isinstance(r, str):
1364 if r == '+glob':
1364 if r == '+glob':
1365 lout = el[:-1] + ' (glob)\n'
1365 lout = el[:-1] + ' (glob)\n'
1366 r = '' # Warn only this line.
1366 r = '' # Warn only this line.
1367 elif r == '-glob':
1367 elif r == '-glob':
1368 lout = ''.join(el.rsplit(' (glob)', 1))
1368 lout = ''.join(el.rsplit(' (glob)', 1))
1369 r = '' # Warn only this line.
1369 r = '' # Warn only this line.
1370 elif r == "retry":
1370 elif r == "retry":
1371 postout.append(b' ' + el)
1371 postout.append(b' ' + el)
1372 els.pop(i)
1372 els.pop(i)
1373 break
1373 break
1374 else:
1374 else:
1375 log('\ninfo, unknown linematch result: %r\n' % r)
1375 log('\ninfo, unknown linematch result: %r\n' % r)
1376 r = False
1376 r = False
1377 if r:
1377 if r:
1378 els.pop(i)
1378 els.pop(i)
1379 break
1379 break
1380 if el:
1380 if el:
1381 if el.endswith(b" (?)\n"):
1381 if el.endswith(b" (?)\n"):
1382 optional.append(i)
1382 optional.append(i)
1383 else:
1383 else:
1384 m = optline.match(el)
1384 m = optline.match(el)
1385 if m:
1385 if m:
1386 conditions = [c for c in m.group(2).split(' ')]
1386 conditions = [c for c in m.group(2).split(' ')]
1387
1387
1388 if self._hghave(conditions)[0]:
1388 if not self._hghave(conditions)[0]:
1389 lout = el
1390 else:
1391 optional.append(i)
1389 optional.append(i)
1392
1390
1393 i += 1
1391 i += 1
1394
1392
1395 if r:
1393 if r:
1396 if r == "retry":
1394 if r == "retry":
1397 continue
1395 continue
1398 # clean up any optional leftovers
1396 # clean up any optional leftovers
1399 for i in optional:
1397 for i in optional:
1400 postout.append(b' ' + els[i])
1398 postout.append(b' ' + els[i])
1401 for i in reversed(optional):
1399 for i in reversed(optional):
1402 del els[i]
1400 del els[i]
1403 postout.append(b' ' + el)
1401 postout.append(b' ' + el)
1404 else:
1402 else:
1405 if self.NEEDESCAPE(lout):
1403 if self.NEEDESCAPE(lout):
1406 lout = TTest._stringescape(b'%s (esc)\n' %
1404 lout = TTest._stringescape(b'%s (esc)\n' %
1407 lout.rstrip(b'\n'))
1405 lout.rstrip(b'\n'))
1408 postout.append(b' ' + lout) # Let diff deal with it.
1406 postout.append(b' ' + lout) # Let diff deal with it.
1409 if r != '': # If line failed.
1407 if r != '': # If line failed.
1410 warnonly = 3 # for sure not
1408 warnonly = 3 # for sure not
1411 elif warnonly == 1: # Is "not yet" and line is warn only.
1409 elif warnonly == 1: # Is "not yet" and line is warn only.
1412 warnonly = 2 # Yes do warn.
1410 warnonly = 2 # Yes do warn.
1413 break
1411 break
1414 else:
1412 else:
1415 # clean up any optional leftovers
1413 # clean up any optional leftovers
1416 while expected.get(pos, None):
1414 while expected.get(pos, None):
1417 el = expected[pos].pop(0)
1415 el = expected[pos].pop(0)
1418 if el:
1416 if el:
1419 if (not optline.match(el)
1417 if not el.endswith(b" (?)\n"):
1420 and not el.endswith(b" (?)\n")):
1418 m = optline.match(el)
1421 break
1419 if m:
1420 conditions = [c for c in m.group(2).split(' ')]
1421
1422 if self._hghave(conditions)[0]:
1423 # Don't append as optional line
1424 continue
1425 else:
1426 break
1422 postout.append(b' ' + el)
1427 postout.append(b' ' + el)
1423
1428
1424 if lcmd:
1429 if lcmd:
1425 # Add on last return code.
1430 # Add on last return code.
1426 ret = int(lcmd.split()[1])
1431 ret = int(lcmd.split()[1])
1427 if ret != 0:
1432 if ret != 0:
1428 postout.append(b' [%d]\n' % ret)
1433 postout.append(b' [%d]\n' % ret)
1429 if pos in after:
1434 if pos in after:
1430 # Merge in non-active test bits.
1435 # Merge in non-active test bits.
1431 postout += after.pop(pos)
1436 postout += after.pop(pos)
1432 pos = int(lcmd.split()[0])
1437 pos = int(lcmd.split()[0])
1433
1438
1434 if pos in after:
1439 if pos in after:
1435 postout += after.pop(pos)
1440 postout += after.pop(pos)
1436
1441
1437 if warnonly == 2:
1442 if warnonly == 2:
1438 exitcode = False # Set exitcode to warned.
1443 exitcode = False # Set exitcode to warned.
1439
1444
1440 return exitcode, postout
1445 return exitcode, postout
1441
1446
1442 @staticmethod
1447 @staticmethod
1443 def rematch(el, l):
1448 def rematch(el, l):
1444 try:
1449 try:
1445 # use \Z to ensure that the regex matches to the end of the string
1450 # use \Z to ensure that the regex matches to the end of the string
1446 if os.name == 'nt':
1451 if os.name == 'nt':
1447 return re.match(el + br'\r?\n\Z', l)
1452 return re.match(el + br'\r?\n\Z', l)
1448 return re.match(el + br'\n\Z', l)
1453 return re.match(el + br'\n\Z', l)
1449 except re.error:
1454 except re.error:
1450 # el is an invalid regex
1455 # el is an invalid regex
1451 return False
1456 return False
1452
1457
1453 @staticmethod
1458 @staticmethod
1454 def globmatch(el, l):
1459 def globmatch(el, l):
1455 # The only supported special characters are * and ? plus / which also
1460 # The only supported special characters are * and ? plus / which also
1456 # matches \ on windows. Escaping of these characters is supported.
1461 # matches \ on windows. Escaping of these characters is supported.
1457 if el + b'\n' == l:
1462 if el + b'\n' == l:
1458 if os.altsep:
1463 if os.altsep:
1459 # matching on "/" is not needed for this line
1464 # matching on "/" is not needed for this line
1460 for pat in checkcodeglobpats:
1465 for pat in checkcodeglobpats:
1461 if pat.match(el):
1466 if pat.match(el):
1462 return True
1467 return True
1463 return b'-glob'
1468 return b'-glob'
1464 return True
1469 return True
1465 el = el.replace(b'$LOCALIP', b'*')
1470 el = el.replace(b'$LOCALIP', b'*')
1466 i, n = 0, len(el)
1471 i, n = 0, len(el)
1467 res = b''
1472 res = b''
1468 while i < n:
1473 while i < n:
1469 c = el[i:i + 1]
1474 c = el[i:i + 1]
1470 i += 1
1475 i += 1
1471 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1476 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1472 res += el[i - 1:i + 1]
1477 res += el[i - 1:i + 1]
1473 i += 1
1478 i += 1
1474 elif c == b'*':
1479 elif c == b'*':
1475 res += b'.*'
1480 res += b'.*'
1476 elif c == b'?':
1481 elif c == b'?':
1477 res += b'.'
1482 res += b'.'
1478 elif c == b'/' and os.altsep:
1483 elif c == b'/' and os.altsep:
1479 res += b'[/\\\\]'
1484 res += b'[/\\\\]'
1480 else:
1485 else:
1481 res += re.escape(c)
1486 res += re.escape(c)
1482 return TTest.rematch(res, l)
1487 return TTest.rematch(res, l)
1483
1488
1484 @staticmethod
1489 def linematch(self, el, l):
1485 def linematch(el, l):
1486 retry = False
1490 retry = False
1487 if el == l: # perfect match (fast)
1491 if el == l: # perfect match (fast)
1488 return True
1492 return True
1489 if el:
1493 if el:
1490 if el.endswith(b" (?)\n"):
1494 if el.endswith(b" (?)\n"):
1491 retry = "retry"
1495 retry = "retry"
1492 el = el[:-5] + b"\n"
1496 el = el[:-5] + b"\n"
1493 else:
1497 else:
1494 m = optline.match(el)
1498 m = optline.match(el)
1495 if m:
1499 if m:
1500 conditions = [c for c in m.group(2).split(' ')]
1501
1496 el = m.group(1) + b"\n"
1502 el = m.group(1) + b"\n"
1497 retry = "retry"
1503 if not self._hghave(conditions)[0]:
1504 retry = "retry" # Not required by listed features
1498
1505
1499 if el.endswith(b" (esc)\n"):
1506 if el.endswith(b" (esc)\n"):
1500 if PYTHON3:
1507 if PYTHON3:
1501 el = el[:-7].decode('unicode_escape') + '\n'
1508 el = el[:-7].decode('unicode_escape') + '\n'
1502 el = el.encode('utf-8')
1509 el = el.encode('utf-8')
1503 else:
1510 else:
1504 el = el[:-7].decode('string-escape') + '\n'
1511 el = el[:-7].decode('string-escape') + '\n'
1505 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1512 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1506 return True
1513 return True
1507 if el.endswith(b" (re)\n"):
1514 if el.endswith(b" (re)\n"):
1508 return TTest.rematch(el[:-6], l) or retry
1515 return TTest.rematch(el[:-6], l) or retry
1509 if el.endswith(b" (glob)\n"):
1516 if el.endswith(b" (glob)\n"):
1510 # ignore '(glob)' added to l by 'replacements'
1517 # ignore '(glob)' added to l by 'replacements'
1511 if l.endswith(b" (glob)\n"):
1518 if l.endswith(b" (glob)\n"):
1512 l = l[:-8] + b"\n"
1519 l = l[:-8] + b"\n"
1513 return TTest.globmatch(el[:-8], l) or retry
1520 return TTest.globmatch(el[:-8], l) or retry
1514 if os.altsep and l.replace(b'\\', b'/') == el:
1521 if os.altsep and l.replace(b'\\', b'/') == el:
1515 return b'+glob'
1522 return b'+glob'
1516 return retry
1523 return retry
1517
1524
1518 @staticmethod
1525 @staticmethod
1519 def parsehghaveoutput(lines):
1526 def parsehghaveoutput(lines):
1520 '''Parse hghave log lines.
1527 '''Parse hghave log lines.
1521
1528
1522 Return tuple of lists (missing, failed):
1529 Return tuple of lists (missing, failed):
1523 * the missing/unknown features
1530 * the missing/unknown features
1524 * the features for which existence check failed'''
1531 * the features for which existence check failed'''
1525 missing = []
1532 missing = []
1526 failed = []
1533 failed = []
1527 for line in lines:
1534 for line in lines:
1528 if line.startswith(TTest.SKIPPED_PREFIX):
1535 if line.startswith(TTest.SKIPPED_PREFIX):
1529 line = line.splitlines()[0]
1536 line = line.splitlines()[0]
1530 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1537 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1531 elif line.startswith(TTest.FAILED_PREFIX):
1538 elif line.startswith(TTest.FAILED_PREFIX):
1532 line = line.splitlines()[0]
1539 line = line.splitlines()[0]
1533 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1540 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1534
1541
1535 return missing, failed
1542 return missing, failed
1536
1543
1537 @staticmethod
1544 @staticmethod
1538 def _escapef(m):
1545 def _escapef(m):
1539 return TTest.ESCAPEMAP[m.group(0)]
1546 return TTest.ESCAPEMAP[m.group(0)]
1540
1547
1541 @staticmethod
1548 @staticmethod
1542 def _stringescape(s):
1549 def _stringescape(s):
1543 return TTest.ESCAPESUB(TTest._escapef, s)
1550 return TTest.ESCAPESUB(TTest._escapef, s)
1544
1551
1545 iolock = threading.RLock()
1552 iolock = threading.RLock()
1546
1553
1547 class TestResult(unittest._TextTestResult):
1554 class TestResult(unittest._TextTestResult):
1548 """Holds results when executing via unittest."""
1555 """Holds results when executing via unittest."""
1549 # Don't worry too much about accessing the non-public _TextTestResult.
1556 # Don't worry too much about accessing the non-public _TextTestResult.
1550 # It is relatively common in Python testing tools.
1557 # It is relatively common in Python testing tools.
1551 def __init__(self, options, *args, **kwargs):
1558 def __init__(self, options, *args, **kwargs):
1552 super(TestResult, self).__init__(*args, **kwargs)
1559 super(TestResult, self).__init__(*args, **kwargs)
1553
1560
1554 self._options = options
1561 self._options = options
1555
1562
1556 # unittest.TestResult didn't have skipped until 2.7. We need to
1563 # unittest.TestResult didn't have skipped until 2.7. We need to
1557 # polyfill it.
1564 # polyfill it.
1558 self.skipped = []
1565 self.skipped = []
1559
1566
1560 # We have a custom "ignored" result that isn't present in any Python
1567 # We have a custom "ignored" result that isn't present in any Python
1561 # unittest implementation. It is very similar to skipped. It may make
1568 # unittest implementation. It is very similar to skipped. It may make
1562 # sense to map it into skip some day.
1569 # sense to map it into skip some day.
1563 self.ignored = []
1570 self.ignored = []
1564
1571
1565 self.times = []
1572 self.times = []
1566 self._firststarttime = None
1573 self._firststarttime = None
1567 # Data stored for the benefit of generating xunit reports.
1574 # Data stored for the benefit of generating xunit reports.
1568 self.successes = []
1575 self.successes = []
1569 self.faildata = {}
1576 self.faildata = {}
1570
1577
1571 if options.color == 'auto':
1578 if options.color == 'auto':
1572 self.color = pygmentspresent and self.stream.isatty()
1579 self.color = pygmentspresent and self.stream.isatty()
1573 elif options.color == 'never':
1580 elif options.color == 'never':
1574 self.color = False
1581 self.color = False
1575 else: # 'always', for testing purposes
1582 else: # 'always', for testing purposes
1576 self.color = pygmentspresent
1583 self.color = pygmentspresent
1577
1584
1578 def addFailure(self, test, reason):
1585 def addFailure(self, test, reason):
1579 self.failures.append((test, reason))
1586 self.failures.append((test, reason))
1580
1587
1581 if self._options.first:
1588 if self._options.first:
1582 self.stop()
1589 self.stop()
1583 else:
1590 else:
1584 with iolock:
1591 with iolock:
1585 if reason == "timed out":
1592 if reason == "timed out":
1586 self.stream.write('t')
1593 self.stream.write('t')
1587 else:
1594 else:
1588 if not self._options.nodiff:
1595 if not self._options.nodiff:
1589 self.stream.write('\nERROR: %s output changed\n' % test)
1596 self.stream.write('\nERROR: %s output changed\n' % test)
1590 self.stream.write('!')
1597 self.stream.write('!')
1591
1598
1592 self.stream.flush()
1599 self.stream.flush()
1593
1600
1594 def addSuccess(self, test):
1601 def addSuccess(self, test):
1595 with iolock:
1602 with iolock:
1596 super(TestResult, self).addSuccess(test)
1603 super(TestResult, self).addSuccess(test)
1597 self.successes.append(test)
1604 self.successes.append(test)
1598
1605
1599 def addError(self, test, err):
1606 def addError(self, test, err):
1600 super(TestResult, self).addError(test, err)
1607 super(TestResult, self).addError(test, err)
1601 if self._options.first:
1608 if self._options.first:
1602 self.stop()
1609 self.stop()
1603
1610
1604 # Polyfill.
1611 # Polyfill.
1605 def addSkip(self, test, reason):
1612 def addSkip(self, test, reason):
1606 self.skipped.append((test, reason))
1613 self.skipped.append((test, reason))
1607 with iolock:
1614 with iolock:
1608 if self.showAll:
1615 if self.showAll:
1609 self.stream.writeln('skipped %s' % reason)
1616 self.stream.writeln('skipped %s' % reason)
1610 else:
1617 else:
1611 self.stream.write('s')
1618 self.stream.write('s')
1612 self.stream.flush()
1619 self.stream.flush()
1613
1620
1614 def addIgnore(self, test, reason):
1621 def addIgnore(self, test, reason):
1615 self.ignored.append((test, reason))
1622 self.ignored.append((test, reason))
1616 with iolock:
1623 with iolock:
1617 if self.showAll:
1624 if self.showAll:
1618 self.stream.writeln('ignored %s' % reason)
1625 self.stream.writeln('ignored %s' % reason)
1619 else:
1626 else:
1620 if reason not in ('not retesting', "doesn't match keyword"):
1627 if reason not in ('not retesting', "doesn't match keyword"):
1621 self.stream.write('i')
1628 self.stream.write('i')
1622 else:
1629 else:
1623 self.testsRun += 1
1630 self.testsRun += 1
1624 self.stream.flush()
1631 self.stream.flush()
1625
1632
1626 def addOutputMismatch(self, test, ret, got, expected):
1633 def addOutputMismatch(self, test, ret, got, expected):
1627 """Record a mismatch in test output for a particular test."""
1634 """Record a mismatch in test output for a particular test."""
1628 if self.shouldStop:
1635 if self.shouldStop:
1629 # don't print, some other test case already failed and
1636 # don't print, some other test case already failed and
1630 # printed, we're just stale and probably failed due to our
1637 # printed, we're just stale and probably failed due to our
1631 # temp dir getting cleaned up.
1638 # temp dir getting cleaned up.
1632 return
1639 return
1633
1640
1634 accepted = False
1641 accepted = False
1635 lines = []
1642 lines = []
1636
1643
1637 with iolock:
1644 with iolock:
1638 if self._options.nodiff:
1645 if self._options.nodiff:
1639 pass
1646 pass
1640 elif self._options.view:
1647 elif self._options.view:
1641 v = self._options.view
1648 v = self._options.view
1642 if PYTHON3:
1649 if PYTHON3:
1643 v = _bytespath(v)
1650 v = _bytespath(v)
1644 os.system(b"%s %s %s" %
1651 os.system(b"%s %s %s" %
1645 (v, test.refpath, test.errpath))
1652 (v, test.refpath, test.errpath))
1646 else:
1653 else:
1647 servefail, lines = getdiff(expected, got,
1654 servefail, lines = getdiff(expected, got,
1648 test.refpath, test.errpath)
1655 test.refpath, test.errpath)
1649 if servefail:
1656 if servefail:
1650 raise test.failureException(
1657 raise test.failureException(
1651 'server failed to start (HGPORT=%s)' % test._startport)
1658 'server failed to start (HGPORT=%s)' % test._startport)
1652 else:
1659 else:
1653 self.stream.write('\n')
1660 self.stream.write('\n')
1654 for line in lines:
1661 for line in lines:
1655 if self.color:
1662 if self.color:
1656 line = pygments.highlight(line,
1663 line = pygments.highlight(line,
1657 difflexer,
1664 difflexer,
1658 terminal256formatter)
1665 terminal256formatter)
1659 if PYTHON3:
1666 if PYTHON3:
1660 self.stream.flush()
1667 self.stream.flush()
1661 self.stream.buffer.write(line)
1668 self.stream.buffer.write(line)
1662 self.stream.buffer.flush()
1669 self.stream.buffer.flush()
1663 else:
1670 else:
1664 self.stream.write(line)
1671 self.stream.write(line)
1665 self.stream.flush()
1672 self.stream.flush()
1666
1673
1667 # handle interactive prompt without releasing iolock
1674 # handle interactive prompt without releasing iolock
1668 if self._options.interactive:
1675 if self._options.interactive:
1669 if test.readrefout() != expected:
1676 if test.readrefout() != expected:
1670 self.stream.write(
1677 self.stream.write(
1671 'Reference output has changed (run again to prompt '
1678 'Reference output has changed (run again to prompt '
1672 'changes)')
1679 'changes)')
1673 else:
1680 else:
1674 self.stream.write('Accept this change? [n] ')
1681 self.stream.write('Accept this change? [n] ')
1675 answer = sys.stdin.readline().strip()
1682 answer = sys.stdin.readline().strip()
1676 if answer.lower() in ('y', 'yes'):
1683 if answer.lower() in ('y', 'yes'):
1677 if test.path.endswith(b'.t'):
1684 if test.path.endswith(b'.t'):
1678 rename(test.errpath, test.path)
1685 rename(test.errpath, test.path)
1679 else:
1686 else:
1680 rename(test.errpath, '%s.out' % test.path)
1687 rename(test.errpath, '%s.out' % test.path)
1681 accepted = True
1688 accepted = True
1682 if not accepted:
1689 if not accepted:
1683 self.faildata[test.name] = b''.join(lines)
1690 self.faildata[test.name] = b''.join(lines)
1684
1691
1685 return accepted
1692 return accepted
1686
1693
1687 def startTest(self, test):
1694 def startTest(self, test):
1688 super(TestResult, self).startTest(test)
1695 super(TestResult, self).startTest(test)
1689
1696
1690 # os.times module computes the user time and system time spent by
1697 # os.times module computes the user time and system time spent by
1691 # child's processes along with real elapsed time taken by a process.
1698 # child's processes along with real elapsed time taken by a process.
1692 # This module has one limitation. It can only work for Linux user
1699 # This module has one limitation. It can only work for Linux user
1693 # and not for Windows.
1700 # and not for Windows.
1694 test.started = os.times()
1701 test.started = os.times()
1695 if self._firststarttime is None: # thread racy but irrelevant
1702 if self._firststarttime is None: # thread racy but irrelevant
1696 self._firststarttime = test.started[4]
1703 self._firststarttime = test.started[4]
1697
1704
1698 def stopTest(self, test, interrupted=False):
1705 def stopTest(self, test, interrupted=False):
1699 super(TestResult, self).stopTest(test)
1706 super(TestResult, self).stopTest(test)
1700
1707
1701 test.stopped = os.times()
1708 test.stopped = os.times()
1702
1709
1703 starttime = test.started
1710 starttime = test.started
1704 endtime = test.stopped
1711 endtime = test.stopped
1705 origin = self._firststarttime
1712 origin = self._firststarttime
1706 self.times.append((test.name,
1713 self.times.append((test.name,
1707 endtime[2] - starttime[2], # user space CPU time
1714 endtime[2] - starttime[2], # user space CPU time
1708 endtime[3] - starttime[3], # sys space CPU time
1715 endtime[3] - starttime[3], # sys space CPU time
1709 endtime[4] - starttime[4], # real time
1716 endtime[4] - starttime[4], # real time
1710 starttime[4] - origin, # start date in run context
1717 starttime[4] - origin, # start date in run context
1711 endtime[4] - origin, # end date in run context
1718 endtime[4] - origin, # end date in run context
1712 ))
1719 ))
1713
1720
1714 if interrupted:
1721 if interrupted:
1715 with iolock:
1722 with iolock:
1716 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1723 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1717 test.name, self.times[-1][3]))
1724 test.name, self.times[-1][3]))
1718
1725
1719 class TestSuite(unittest.TestSuite):
1726 class TestSuite(unittest.TestSuite):
1720 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1727 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1721
1728
1722 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1729 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1723 retest=False, keywords=None, loop=False, runs_per_test=1,
1730 retest=False, keywords=None, loop=False, runs_per_test=1,
1724 loadtest=None, showchannels=False,
1731 loadtest=None, showchannels=False,
1725 *args, **kwargs):
1732 *args, **kwargs):
1726 """Create a new instance that can run tests with a configuration.
1733 """Create a new instance that can run tests with a configuration.
1727
1734
1728 testdir specifies the directory where tests are executed from. This
1735 testdir specifies the directory where tests are executed from. This
1729 is typically the ``tests`` directory from Mercurial's source
1736 is typically the ``tests`` directory from Mercurial's source
1730 repository.
1737 repository.
1731
1738
1732 jobs specifies the number of jobs to run concurrently. Each test
1739 jobs specifies the number of jobs to run concurrently. Each test
1733 executes on its own thread. Tests actually spawn new processes, so
1740 executes on its own thread. Tests actually spawn new processes, so
1734 state mutation should not be an issue.
1741 state mutation should not be an issue.
1735
1742
1736 If there is only one job, it will use the main thread.
1743 If there is only one job, it will use the main thread.
1737
1744
1738 whitelist and blacklist denote tests that have been whitelisted and
1745 whitelist and blacklist denote tests that have been whitelisted and
1739 blacklisted, respectively. These arguments don't belong in TestSuite.
1746 blacklisted, respectively. These arguments don't belong in TestSuite.
1740 Instead, whitelist and blacklist should be handled by the thing that
1747 Instead, whitelist and blacklist should be handled by the thing that
1741 populates the TestSuite with tests. They are present to preserve
1748 populates the TestSuite with tests. They are present to preserve
1742 backwards compatible behavior which reports skipped tests as part
1749 backwards compatible behavior which reports skipped tests as part
1743 of the results.
1750 of the results.
1744
1751
1745 retest denotes whether to retest failed tests. This arguably belongs
1752 retest denotes whether to retest failed tests. This arguably belongs
1746 outside of TestSuite.
1753 outside of TestSuite.
1747
1754
1748 keywords denotes key words that will be used to filter which tests
1755 keywords denotes key words that will be used to filter which tests
1749 to execute. This arguably belongs outside of TestSuite.
1756 to execute. This arguably belongs outside of TestSuite.
1750
1757
1751 loop denotes whether to loop over tests forever.
1758 loop denotes whether to loop over tests forever.
1752 """
1759 """
1753 super(TestSuite, self).__init__(*args, **kwargs)
1760 super(TestSuite, self).__init__(*args, **kwargs)
1754
1761
1755 self._jobs = jobs
1762 self._jobs = jobs
1756 self._whitelist = whitelist
1763 self._whitelist = whitelist
1757 self._blacklist = blacklist
1764 self._blacklist = blacklist
1758 self._retest = retest
1765 self._retest = retest
1759 self._keywords = keywords
1766 self._keywords = keywords
1760 self._loop = loop
1767 self._loop = loop
1761 self._runs_per_test = runs_per_test
1768 self._runs_per_test = runs_per_test
1762 self._loadtest = loadtest
1769 self._loadtest = loadtest
1763 self._showchannels = showchannels
1770 self._showchannels = showchannels
1764
1771
1765 def run(self, result):
1772 def run(self, result):
1766 # We have a number of filters that need to be applied. We do this
1773 # We have a number of filters that need to be applied. We do this
1767 # here instead of inside Test because it makes the running logic for
1774 # here instead of inside Test because it makes the running logic for
1768 # Test simpler.
1775 # Test simpler.
1769 tests = []
1776 tests = []
1770 num_tests = [0]
1777 num_tests = [0]
1771 for test in self._tests:
1778 for test in self._tests:
1772 def get():
1779 def get():
1773 num_tests[0] += 1
1780 num_tests[0] += 1
1774 if getattr(test, 'should_reload', False):
1781 if getattr(test, 'should_reload', False):
1775 return self._loadtest(test, num_tests[0])
1782 return self._loadtest(test, num_tests[0])
1776 return test
1783 return test
1777 if not os.path.exists(test.path):
1784 if not os.path.exists(test.path):
1778 result.addSkip(test, "Doesn't exist")
1785 result.addSkip(test, "Doesn't exist")
1779 continue
1786 continue
1780
1787
1781 if not (self._whitelist and test.name in self._whitelist):
1788 if not (self._whitelist and test.name in self._whitelist):
1782 if self._blacklist and test.bname in self._blacklist:
1789 if self._blacklist and test.bname in self._blacklist:
1783 result.addSkip(test, 'blacklisted')
1790 result.addSkip(test, 'blacklisted')
1784 continue
1791 continue
1785
1792
1786 if self._retest and not os.path.exists(test.errpath):
1793 if self._retest and not os.path.exists(test.errpath):
1787 result.addIgnore(test, 'not retesting')
1794 result.addIgnore(test, 'not retesting')
1788 continue
1795 continue
1789
1796
1790 if self._keywords:
1797 if self._keywords:
1791 f = open(test.path, 'rb')
1798 f = open(test.path, 'rb')
1792 t = f.read().lower() + test.bname.lower()
1799 t = f.read().lower() + test.bname.lower()
1793 f.close()
1800 f.close()
1794 ignored = False
1801 ignored = False
1795 for k in self._keywords.lower().split():
1802 for k in self._keywords.lower().split():
1796 if k not in t:
1803 if k not in t:
1797 result.addIgnore(test, "doesn't match keyword")
1804 result.addIgnore(test, "doesn't match keyword")
1798 ignored = True
1805 ignored = True
1799 break
1806 break
1800
1807
1801 if ignored:
1808 if ignored:
1802 continue
1809 continue
1803 for _ in xrange(self._runs_per_test):
1810 for _ in xrange(self._runs_per_test):
1804 tests.append(get())
1811 tests.append(get())
1805
1812
1806 runtests = list(tests)
1813 runtests = list(tests)
1807 done = queue.Queue()
1814 done = queue.Queue()
1808 running = 0
1815 running = 0
1809
1816
1810 channels = [""] * self._jobs
1817 channels = [""] * self._jobs
1811
1818
1812 def job(test, result):
1819 def job(test, result):
1813 for n, v in enumerate(channels):
1820 for n, v in enumerate(channels):
1814 if not v:
1821 if not v:
1815 channel = n
1822 channel = n
1816 break
1823 break
1817 else:
1824 else:
1818 raise ValueError('Could not find output channel')
1825 raise ValueError('Could not find output channel')
1819 channels[channel] = "=" + test.name[5:].split(".")[0]
1826 channels[channel] = "=" + test.name[5:].split(".")[0]
1820 try:
1827 try:
1821 test(result)
1828 test(result)
1822 done.put(None)
1829 done.put(None)
1823 except KeyboardInterrupt:
1830 except KeyboardInterrupt:
1824 pass
1831 pass
1825 except: # re-raises
1832 except: # re-raises
1826 done.put(('!', test, 'run-test raised an error, see traceback'))
1833 done.put(('!', test, 'run-test raised an error, see traceback'))
1827 raise
1834 raise
1828 finally:
1835 finally:
1829 try:
1836 try:
1830 channels[channel] = ''
1837 channels[channel] = ''
1831 except IndexError:
1838 except IndexError:
1832 pass
1839 pass
1833
1840
1834 def stat():
1841 def stat():
1835 count = 0
1842 count = 0
1836 while channels:
1843 while channels:
1837 d = '\n%03s ' % count
1844 d = '\n%03s ' % count
1838 for n, v in enumerate(channels):
1845 for n, v in enumerate(channels):
1839 if v:
1846 if v:
1840 d += v[0]
1847 d += v[0]
1841 channels[n] = v[1:] or '.'
1848 channels[n] = v[1:] or '.'
1842 else:
1849 else:
1843 d += ' '
1850 d += ' '
1844 d += ' '
1851 d += ' '
1845 with iolock:
1852 with iolock:
1846 sys.stdout.write(d + ' ')
1853 sys.stdout.write(d + ' ')
1847 sys.stdout.flush()
1854 sys.stdout.flush()
1848 for x in xrange(10):
1855 for x in xrange(10):
1849 if channels:
1856 if channels:
1850 time.sleep(.1)
1857 time.sleep(.1)
1851 count += 1
1858 count += 1
1852
1859
1853 stoppedearly = False
1860 stoppedearly = False
1854
1861
1855 if self._showchannels:
1862 if self._showchannels:
1856 statthread = threading.Thread(target=stat, name="stat")
1863 statthread = threading.Thread(target=stat, name="stat")
1857 statthread.start()
1864 statthread.start()
1858
1865
1859 try:
1866 try:
1860 while tests or running:
1867 while tests or running:
1861 if not done.empty() or running == self._jobs or not tests:
1868 if not done.empty() or running == self._jobs or not tests:
1862 try:
1869 try:
1863 done.get(True, 1)
1870 done.get(True, 1)
1864 running -= 1
1871 running -= 1
1865 if result and result.shouldStop:
1872 if result and result.shouldStop:
1866 stoppedearly = True
1873 stoppedearly = True
1867 break
1874 break
1868 except queue.Empty:
1875 except queue.Empty:
1869 continue
1876 continue
1870 if tests and not running == self._jobs:
1877 if tests and not running == self._jobs:
1871 test = tests.pop(0)
1878 test = tests.pop(0)
1872 if self._loop:
1879 if self._loop:
1873 if getattr(test, 'should_reload', False):
1880 if getattr(test, 'should_reload', False):
1874 num_tests[0] += 1
1881 num_tests[0] += 1
1875 tests.append(
1882 tests.append(
1876 self._loadtest(test, num_tests[0]))
1883 self._loadtest(test, num_tests[0]))
1877 else:
1884 else:
1878 tests.append(test)
1885 tests.append(test)
1879 if self._jobs == 1:
1886 if self._jobs == 1:
1880 job(test, result)
1887 job(test, result)
1881 else:
1888 else:
1882 t = threading.Thread(target=job, name=test.name,
1889 t = threading.Thread(target=job, name=test.name,
1883 args=(test, result))
1890 args=(test, result))
1884 t.start()
1891 t.start()
1885 running += 1
1892 running += 1
1886
1893
1887 # If we stop early we still need to wait on started tests to
1894 # If we stop early we still need to wait on started tests to
1888 # finish. Otherwise, there is a race between the test completing
1895 # finish. Otherwise, there is a race between the test completing
1889 # and the test's cleanup code running. This could result in the
1896 # and the test's cleanup code running. This could result in the
1890 # test reporting incorrect.
1897 # test reporting incorrect.
1891 if stoppedearly:
1898 if stoppedearly:
1892 while running:
1899 while running:
1893 try:
1900 try:
1894 done.get(True, 1)
1901 done.get(True, 1)
1895 running -= 1
1902 running -= 1
1896 except queue.Empty:
1903 except queue.Empty:
1897 continue
1904 continue
1898 except KeyboardInterrupt:
1905 except KeyboardInterrupt:
1899 for test in runtests:
1906 for test in runtests:
1900 test.abort()
1907 test.abort()
1901
1908
1902 channels = []
1909 channels = []
1903
1910
1904 return result
1911 return result
1905
1912
1906 # Save the most recent 5 wall-clock runtimes of each test to a
1913 # Save the most recent 5 wall-clock runtimes of each test to a
1907 # human-readable text file named .testtimes. Tests are sorted
1914 # human-readable text file named .testtimes. Tests are sorted
1908 # alphabetically, while times for each test are listed from oldest to
1915 # alphabetically, while times for each test are listed from oldest to
1909 # newest.
1916 # newest.
1910
1917
1911 def loadtimes(outputdir):
1918 def loadtimes(outputdir):
1912 times = []
1919 times = []
1913 try:
1920 try:
1914 with open(os.path.join(outputdir, b'.testtimes-')) as fp:
1921 with open(os.path.join(outputdir, b'.testtimes-')) as fp:
1915 for line in fp:
1922 for line in fp:
1916 ts = line.split()
1923 ts = line.split()
1917 times.append((ts[0], [float(t) for t in ts[1:]]))
1924 times.append((ts[0], [float(t) for t in ts[1:]]))
1918 except IOError as err:
1925 except IOError as err:
1919 if err.errno != errno.ENOENT:
1926 if err.errno != errno.ENOENT:
1920 raise
1927 raise
1921 return times
1928 return times
1922
1929
1923 def savetimes(outputdir, result):
1930 def savetimes(outputdir, result):
1924 saved = dict(loadtimes(outputdir))
1931 saved = dict(loadtimes(outputdir))
1925 maxruns = 5
1932 maxruns = 5
1926 skipped = set([str(t[0]) for t in result.skipped])
1933 skipped = set([str(t[0]) for t in result.skipped])
1927 for tdata in result.times:
1934 for tdata in result.times:
1928 test, real = tdata[0], tdata[3]
1935 test, real = tdata[0], tdata[3]
1929 if test not in skipped:
1936 if test not in skipped:
1930 ts = saved.setdefault(test, [])
1937 ts = saved.setdefault(test, [])
1931 ts.append(real)
1938 ts.append(real)
1932 ts[:] = ts[-maxruns:]
1939 ts[:] = ts[-maxruns:]
1933
1940
1934 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1941 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
1935 dir=outputdir, text=True)
1942 dir=outputdir, text=True)
1936 with os.fdopen(fd, 'w') as fp:
1943 with os.fdopen(fd, 'w') as fp:
1937 for name, ts in sorted(saved.items()):
1944 for name, ts in sorted(saved.items()):
1938 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1945 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1939 timepath = os.path.join(outputdir, b'.testtimes')
1946 timepath = os.path.join(outputdir, b'.testtimes')
1940 try:
1947 try:
1941 os.unlink(timepath)
1948 os.unlink(timepath)
1942 except OSError:
1949 except OSError:
1943 pass
1950 pass
1944 try:
1951 try:
1945 os.rename(tmpname, timepath)
1952 os.rename(tmpname, timepath)
1946 except OSError:
1953 except OSError:
1947 pass
1954 pass
1948
1955
1949 class TextTestRunner(unittest.TextTestRunner):
1956 class TextTestRunner(unittest.TextTestRunner):
1950 """Custom unittest test runner that uses appropriate settings."""
1957 """Custom unittest test runner that uses appropriate settings."""
1951
1958
1952 def __init__(self, runner, *args, **kwargs):
1959 def __init__(self, runner, *args, **kwargs):
1953 super(TextTestRunner, self).__init__(*args, **kwargs)
1960 super(TextTestRunner, self).__init__(*args, **kwargs)
1954
1961
1955 self._runner = runner
1962 self._runner = runner
1956
1963
1957 def listtests(self, test):
1964 def listtests(self, test):
1958 result = TestResult(self._runner.options, self.stream,
1965 result = TestResult(self._runner.options, self.stream,
1959 self.descriptions, 0)
1966 self.descriptions, 0)
1960 test = sorted(test, key=lambda t: t.name)
1967 test = sorted(test, key=lambda t: t.name)
1961 for t in test:
1968 for t in test:
1962 print(t.name)
1969 print(t.name)
1963 result.addSuccess(t)
1970 result.addSuccess(t)
1964
1971
1965 if self._runner.options.xunit:
1972 if self._runner.options.xunit:
1966 with open(self._runner.options.xunit, "wb") as xuf:
1973 with open(self._runner.options.xunit, "wb") as xuf:
1967 self._writexunit(result, xuf)
1974 self._writexunit(result, xuf)
1968
1975
1969 if self._runner.options.json:
1976 if self._runner.options.json:
1970 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
1977 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
1971 with open(jsonpath, 'w') as fp:
1978 with open(jsonpath, 'w') as fp:
1972 self._writejson(result, fp)
1979 self._writejson(result, fp)
1973
1980
1974 return result
1981 return result
1975
1982
1976 def run(self, test):
1983 def run(self, test):
1977 result = TestResult(self._runner.options, self.stream,
1984 result = TestResult(self._runner.options, self.stream,
1978 self.descriptions, self.verbosity)
1985 self.descriptions, self.verbosity)
1979
1986
1980 test(result)
1987 test(result)
1981
1988
1982 failed = len(result.failures)
1989 failed = len(result.failures)
1983 skipped = len(result.skipped)
1990 skipped = len(result.skipped)
1984 ignored = len(result.ignored)
1991 ignored = len(result.ignored)
1985
1992
1986 with iolock:
1993 with iolock:
1987 self.stream.writeln('')
1994 self.stream.writeln('')
1988
1995
1989 if not self._runner.options.noskips:
1996 if not self._runner.options.noskips:
1990 for test, msg in result.skipped:
1997 for test, msg in result.skipped:
1991 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1998 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1992 for test, msg in result.failures:
1999 for test, msg in result.failures:
1993 self.stream.writeln('Failed %s: %s' % (test.name, msg))
2000 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1994 for test, msg in result.errors:
2001 for test, msg in result.errors:
1995 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2002 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1996
2003
1997 if self._runner.options.xunit:
2004 if self._runner.options.xunit:
1998 with open(self._runner.options.xunit, "wb") as xuf:
2005 with open(self._runner.options.xunit, "wb") as xuf:
1999 self._writexunit(result, xuf)
2006 self._writexunit(result, xuf)
2000
2007
2001 if self._runner.options.json:
2008 if self._runner.options.json:
2002 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2009 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2003 with open(jsonpath, 'w') as fp:
2010 with open(jsonpath, 'w') as fp:
2004 self._writejson(result, fp)
2011 self._writejson(result, fp)
2005
2012
2006 self._runner._checkhglib('Tested')
2013 self._runner._checkhglib('Tested')
2007
2014
2008 savetimes(self._runner._outputdir, result)
2015 savetimes(self._runner._outputdir, result)
2009
2016
2010 if failed and self._runner.options.known_good_rev:
2017 if failed and self._runner.options.known_good_rev:
2011 def nooutput(args):
2018 def nooutput(args):
2012 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2019 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2013 stdout=subprocess.PIPE)
2020 stdout=subprocess.PIPE)
2014 p.stdout.read()
2021 p.stdout.read()
2015 p.wait()
2022 p.wait()
2016 for test, msg in result.failures:
2023 for test, msg in result.failures:
2017 nooutput(['hg', 'bisect', '--reset']),
2024 nooutput(['hg', 'bisect', '--reset']),
2018 nooutput(['hg', 'bisect', '--bad', '.'])
2025 nooutput(['hg', 'bisect', '--bad', '.'])
2019 nooutput(['hg', 'bisect', '--good',
2026 nooutput(['hg', 'bisect', '--good',
2020 self._runner.options.known_good_rev])
2027 self._runner.options.known_good_rev])
2021 # TODO: we probably need to forward some options
2028 # TODO: we probably need to forward some options
2022 # that alter hg's behavior inside the tests.
2029 # that alter hg's behavior inside the tests.
2023 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
2030 rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
2024 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
2031 sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
2025 stderr=subprocess.STDOUT,
2032 stderr=subprocess.STDOUT,
2026 stdout=subprocess.PIPE)
2033 stdout=subprocess.PIPE)
2027 data = sub.stdout.read()
2034 data = sub.stdout.read()
2028 sub.wait()
2035 sub.wait()
2029 m = re.search(
2036 m = re.search(
2030 (r'\nThe first (?P<goodbad>bad|good) revision '
2037 (r'\nThe first (?P<goodbad>bad|good) revision '
2031 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2038 r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2032 r'summary: +(?P<summary>[^\n]+)\n'),
2039 r'summary: +(?P<summary>[^\n]+)\n'),
2033 data, (re.MULTILINE | re.DOTALL))
2040 data, (re.MULTILINE | re.DOTALL))
2034 if m is None:
2041 if m is None:
2035 self.stream.writeln(
2042 self.stream.writeln(
2036 'Failed to identify failure point for %s' % test)
2043 'Failed to identify failure point for %s' % test)
2037 continue
2044 continue
2038 dat = m.groupdict()
2045 dat = m.groupdict()
2039 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
2046 verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
2040 self.stream.writeln(
2047 self.stream.writeln(
2041 '%s %s by %s (%s)' % (
2048 '%s %s by %s (%s)' % (
2042 test, verb, dat['node'], dat['summary']))
2049 test, verb, dat['node'], dat['summary']))
2043 self.stream.writeln(
2050 self.stream.writeln(
2044 '# Ran %d tests, %d skipped, %d failed.'
2051 '# Ran %d tests, %d skipped, %d failed.'
2045 % (result.testsRun, skipped + ignored, failed))
2052 % (result.testsRun, skipped + ignored, failed))
2046 if failed:
2053 if failed:
2047 self.stream.writeln('python hash seed: %s' %
2054 self.stream.writeln('python hash seed: %s' %
2048 os.environ['PYTHONHASHSEED'])
2055 os.environ['PYTHONHASHSEED'])
2049 if self._runner.options.time:
2056 if self._runner.options.time:
2050 self.printtimes(result.times)
2057 self.printtimes(result.times)
2051 self.stream.flush()
2058 self.stream.flush()
2052
2059
2053 return result
2060 return result
2054
2061
2055 def printtimes(self, times):
2062 def printtimes(self, times):
2056 # iolock held by run
2063 # iolock held by run
2057 self.stream.writeln('# Producing time report')
2064 self.stream.writeln('# Producing time report')
2058 times.sort(key=lambda t: (t[3]))
2065 times.sort(key=lambda t: (t[3]))
2059 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2066 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2060 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2067 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2061 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2068 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2062 for tdata in times:
2069 for tdata in times:
2063 test = tdata[0]
2070 test = tdata[0]
2064 cuser, csys, real, start, end = tdata[1:6]
2071 cuser, csys, real, start, end = tdata[1:6]
2065 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2072 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2066
2073
2067 @staticmethod
2074 @staticmethod
2068 def _writexunit(result, outf):
2075 def _writexunit(result, outf):
2069 # See http://llg.cubic.org/docs/junit/ for a reference.
2076 # See http://llg.cubic.org/docs/junit/ for a reference.
2070 timesd = dict((t[0], t[3]) for t in result.times)
2077 timesd = dict((t[0], t[3]) for t in result.times)
2071 doc = minidom.Document()
2078 doc = minidom.Document()
2072 s = doc.createElement('testsuite')
2079 s = doc.createElement('testsuite')
2073 s.setAttribute('name', 'run-tests')
2080 s.setAttribute('name', 'run-tests')
2074 s.setAttribute('tests', str(result.testsRun))
2081 s.setAttribute('tests', str(result.testsRun))
2075 s.setAttribute('errors', "0") # TODO
2082 s.setAttribute('errors', "0") # TODO
2076 s.setAttribute('failures', str(len(result.failures)))
2083 s.setAttribute('failures', str(len(result.failures)))
2077 s.setAttribute('skipped', str(len(result.skipped) +
2084 s.setAttribute('skipped', str(len(result.skipped) +
2078 len(result.ignored)))
2085 len(result.ignored)))
2079 doc.appendChild(s)
2086 doc.appendChild(s)
2080 for tc in result.successes:
2087 for tc in result.successes:
2081 t = doc.createElement('testcase')
2088 t = doc.createElement('testcase')
2082 t.setAttribute('name', tc.name)
2089 t.setAttribute('name', tc.name)
2083 tctime = timesd.get(tc.name)
2090 tctime = timesd.get(tc.name)
2084 if tctime is not None:
2091 if tctime is not None:
2085 t.setAttribute('time', '%.3f' % tctime)
2092 t.setAttribute('time', '%.3f' % tctime)
2086 s.appendChild(t)
2093 s.appendChild(t)
2087 for tc, err in sorted(result.faildata.items()):
2094 for tc, err in sorted(result.faildata.items()):
2088 t = doc.createElement('testcase')
2095 t = doc.createElement('testcase')
2089 t.setAttribute('name', tc)
2096 t.setAttribute('name', tc)
2090 tctime = timesd.get(tc)
2097 tctime = timesd.get(tc)
2091 if tctime is not None:
2098 if tctime is not None:
2092 t.setAttribute('time', '%.3f' % tctime)
2099 t.setAttribute('time', '%.3f' % tctime)
2093 # createCDATASection expects a unicode or it will
2100 # createCDATASection expects a unicode or it will
2094 # convert using default conversion rules, which will
2101 # convert using default conversion rules, which will
2095 # fail if string isn't ASCII.
2102 # fail if string isn't ASCII.
2096 err = cdatasafe(err).decode('utf-8', 'replace')
2103 err = cdatasafe(err).decode('utf-8', 'replace')
2097 cd = doc.createCDATASection(err)
2104 cd = doc.createCDATASection(err)
2098 # Use 'failure' here instead of 'error' to match errors = 0,
2105 # Use 'failure' here instead of 'error' to match errors = 0,
2099 # failures = len(result.failures) in the testsuite element.
2106 # failures = len(result.failures) in the testsuite element.
2100 failelem = doc.createElement('failure')
2107 failelem = doc.createElement('failure')
2101 failelem.setAttribute('message', 'output changed')
2108 failelem.setAttribute('message', 'output changed')
2102 failelem.setAttribute('type', 'output-mismatch')
2109 failelem.setAttribute('type', 'output-mismatch')
2103 failelem.appendChild(cd)
2110 failelem.appendChild(cd)
2104 t.appendChild(failelem)
2111 t.appendChild(failelem)
2105 s.appendChild(t)
2112 s.appendChild(t)
2106 for tc, message in result.skipped:
2113 for tc, message in result.skipped:
2107 # According to the schema, 'skipped' has no attributes. So store
2114 # According to the schema, 'skipped' has no attributes. So store
2108 # the skip message as a text node instead.
2115 # the skip message as a text node instead.
2109 t = doc.createElement('testcase')
2116 t = doc.createElement('testcase')
2110 t.setAttribute('name', tc.name)
2117 t.setAttribute('name', tc.name)
2111 message = cdatasafe(message).decode('utf-8', 'replace')
2118 message = cdatasafe(message).decode('utf-8', 'replace')
2112 cd = doc.createCDATASection(message)
2119 cd = doc.createCDATASection(message)
2113 skipelem = doc.createElement('skipped')
2120 skipelem = doc.createElement('skipped')
2114 skipelem.appendChild(cd)
2121 skipelem.appendChild(cd)
2115 t.appendChild(skipelem)
2122 t.appendChild(skipelem)
2116 s.appendChild(t)
2123 s.appendChild(t)
2117 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2124 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2118
2125
2119 @staticmethod
2126 @staticmethod
2120 def _writejson(result, outf):
2127 def _writejson(result, outf):
2121 timesd = {}
2128 timesd = {}
2122 for tdata in result.times:
2129 for tdata in result.times:
2123 test = tdata[0]
2130 test = tdata[0]
2124 timesd[test] = tdata[1:]
2131 timesd[test] = tdata[1:]
2125
2132
2126 outcome = {}
2133 outcome = {}
2127 groups = [('success', ((tc, None)
2134 groups = [('success', ((tc, None)
2128 for tc in result.successes)),
2135 for tc in result.successes)),
2129 ('failure', result.failures),
2136 ('failure', result.failures),
2130 ('skip', result.skipped)]
2137 ('skip', result.skipped)]
2131 for res, testcases in groups:
2138 for res, testcases in groups:
2132 for tc, __ in testcases:
2139 for tc, __ in testcases:
2133 if tc.name in timesd:
2140 if tc.name in timesd:
2134 diff = result.faildata.get(tc.name, b'')
2141 diff = result.faildata.get(tc.name, b'')
2135 try:
2142 try:
2136 diff = diff.decode('unicode_escape')
2143 diff = diff.decode('unicode_escape')
2137 except UnicodeDecodeError as e:
2144 except UnicodeDecodeError as e:
2138 diff = '%r decoding diff, sorry' % e
2145 diff = '%r decoding diff, sorry' % e
2139 tres = {'result': res,
2146 tres = {'result': res,
2140 'time': ('%0.3f' % timesd[tc.name][2]),
2147 'time': ('%0.3f' % timesd[tc.name][2]),
2141 'cuser': ('%0.3f' % timesd[tc.name][0]),
2148 'cuser': ('%0.3f' % timesd[tc.name][0]),
2142 'csys': ('%0.3f' % timesd[tc.name][1]),
2149 'csys': ('%0.3f' % timesd[tc.name][1]),
2143 'start': ('%0.3f' % timesd[tc.name][3]),
2150 'start': ('%0.3f' % timesd[tc.name][3]),
2144 'end': ('%0.3f' % timesd[tc.name][4]),
2151 'end': ('%0.3f' % timesd[tc.name][4]),
2145 'diff': diff,
2152 'diff': diff,
2146 }
2153 }
2147 else:
2154 else:
2148 # blacklisted test
2155 # blacklisted test
2149 tres = {'result': res}
2156 tres = {'result': res}
2150
2157
2151 outcome[tc.name] = tres
2158 outcome[tc.name] = tres
2152 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2159 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2153 separators=(',', ': '))
2160 separators=(',', ': '))
2154 outf.writelines(("testreport =", jsonout))
2161 outf.writelines(("testreport =", jsonout))
2155
2162
2156 class TestRunner(object):
2163 class TestRunner(object):
2157 """Holds context for executing tests.
2164 """Holds context for executing tests.
2158
2165
2159 Tests rely on a lot of state. This object holds it for them.
2166 Tests rely on a lot of state. This object holds it for them.
2160 """
2167 """
2161
2168
2162 # Programs required to run tests.
2169 # Programs required to run tests.
2163 REQUIREDTOOLS = [
2170 REQUIREDTOOLS = [
2164 b'diff',
2171 b'diff',
2165 b'grep',
2172 b'grep',
2166 b'unzip',
2173 b'unzip',
2167 b'gunzip',
2174 b'gunzip',
2168 b'bunzip2',
2175 b'bunzip2',
2169 b'sed',
2176 b'sed',
2170 ]
2177 ]
2171
2178
2172 # Maps file extensions to test class.
2179 # Maps file extensions to test class.
2173 TESTTYPES = [
2180 TESTTYPES = [
2174 (b'.py', PythonTest),
2181 (b'.py', PythonTest),
2175 (b'.t', TTest),
2182 (b'.t', TTest),
2176 ]
2183 ]
2177
2184
2178 def __init__(self):
2185 def __init__(self):
2179 self.options = None
2186 self.options = None
2180 self._hgroot = None
2187 self._hgroot = None
2181 self._testdir = None
2188 self._testdir = None
2182 self._outputdir = None
2189 self._outputdir = None
2183 self._hgtmp = None
2190 self._hgtmp = None
2184 self._installdir = None
2191 self._installdir = None
2185 self._bindir = None
2192 self._bindir = None
2186 self._tmpbinddir = None
2193 self._tmpbinddir = None
2187 self._pythondir = None
2194 self._pythondir = None
2188 self._coveragefile = None
2195 self._coveragefile = None
2189 self._createdfiles = []
2196 self._createdfiles = []
2190 self._hgcommand = None
2197 self._hgcommand = None
2191 self._hgpath = None
2198 self._hgpath = None
2192 self._portoffset = 0
2199 self._portoffset = 0
2193 self._ports = {}
2200 self._ports = {}
2194
2201
2195 def run(self, args, parser=None):
2202 def run(self, args, parser=None):
2196 """Run the test suite."""
2203 """Run the test suite."""
2197 oldmask = os.umask(0o22)
2204 oldmask = os.umask(0o22)
2198 try:
2205 try:
2199 parser = parser or getparser()
2206 parser = parser or getparser()
2200 options, args = parseargs(args, parser)
2207 options, args = parseargs(args, parser)
2201 # positional arguments are paths to test files to run, so
2208 # positional arguments are paths to test files to run, so
2202 # we make sure they're all bytestrings
2209 # we make sure they're all bytestrings
2203 args = [_bytespath(a) for a in args]
2210 args = [_bytespath(a) for a in args]
2204 self.options = options
2211 self.options = options
2205
2212
2206 self._checktools()
2213 self._checktools()
2207 testdescs = self.findtests(args)
2214 testdescs = self.findtests(args)
2208 if options.profile_runner:
2215 if options.profile_runner:
2209 import statprof
2216 import statprof
2210 statprof.start()
2217 statprof.start()
2211 result = self._run(testdescs)
2218 result = self._run(testdescs)
2212 if options.profile_runner:
2219 if options.profile_runner:
2213 statprof.stop()
2220 statprof.stop()
2214 statprof.display()
2221 statprof.display()
2215 return result
2222 return result
2216
2223
2217 finally:
2224 finally:
2218 os.umask(oldmask)
2225 os.umask(oldmask)
2219
2226
2220 def _run(self, testdescs):
2227 def _run(self, testdescs):
2221 if self.options.random:
2228 if self.options.random:
2222 random.shuffle(testdescs)
2229 random.shuffle(testdescs)
2223 else:
2230 else:
2224 # keywords for slow tests
2231 # keywords for slow tests
2225 slow = {b'svn': 10,
2232 slow = {b'svn': 10,
2226 b'cvs': 10,
2233 b'cvs': 10,
2227 b'hghave': 10,
2234 b'hghave': 10,
2228 b'largefiles-update': 10,
2235 b'largefiles-update': 10,
2229 b'run-tests': 10,
2236 b'run-tests': 10,
2230 b'corruption': 10,
2237 b'corruption': 10,
2231 b'race': 10,
2238 b'race': 10,
2232 b'i18n': 10,
2239 b'i18n': 10,
2233 b'check': 100,
2240 b'check': 100,
2234 b'gendoc': 100,
2241 b'gendoc': 100,
2235 b'contrib-perf': 200,
2242 b'contrib-perf': 200,
2236 }
2243 }
2237 perf = {}
2244 perf = {}
2238 def sortkey(f):
2245 def sortkey(f):
2239 # run largest tests first, as they tend to take the longest
2246 # run largest tests first, as they tend to take the longest
2240 f = f['path']
2247 f = f['path']
2241 try:
2248 try:
2242 return perf[f]
2249 return perf[f]
2243 except KeyError:
2250 except KeyError:
2244 try:
2251 try:
2245 val = -os.stat(f).st_size
2252 val = -os.stat(f).st_size
2246 except OSError as e:
2253 except OSError as e:
2247 if e.errno != errno.ENOENT:
2254 if e.errno != errno.ENOENT:
2248 raise
2255 raise
2249 perf[f] = -1e9 # file does not exist, tell early
2256 perf[f] = -1e9 # file does not exist, tell early
2250 return -1e9
2257 return -1e9
2251 for kw, mul in slow.items():
2258 for kw, mul in slow.items():
2252 if kw in f:
2259 if kw in f:
2253 val *= mul
2260 val *= mul
2254 if f.endswith(b'.py'):
2261 if f.endswith(b'.py'):
2255 val /= 10.0
2262 val /= 10.0
2256 perf[f] = val / 1000.0
2263 perf[f] = val / 1000.0
2257 return perf[f]
2264 return perf[f]
2258 testdescs.sort(key=sortkey)
2265 testdescs.sort(key=sortkey)
2259
2266
2260 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2267 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2261 os, 'getcwdb', os.getcwd)()
2268 os, 'getcwdb', os.getcwd)()
2262 if self.options.outputdir:
2269 if self.options.outputdir:
2263 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2270 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2264 else:
2271 else:
2265 self._outputdir = self._testdir
2272 self._outputdir = self._testdir
2266
2273
2267 if 'PYTHONHASHSEED' not in os.environ:
2274 if 'PYTHONHASHSEED' not in os.environ:
2268 # use a random python hash seed all the time
2275 # use a random python hash seed all the time
2269 # we do the randomness ourself to know what seed is used
2276 # we do the randomness ourself to know what seed is used
2270 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2277 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2271
2278
2272 if self.options.tmpdir:
2279 if self.options.tmpdir:
2273 self.options.keep_tmpdir = True
2280 self.options.keep_tmpdir = True
2274 tmpdir = _bytespath(self.options.tmpdir)
2281 tmpdir = _bytespath(self.options.tmpdir)
2275 if os.path.exists(tmpdir):
2282 if os.path.exists(tmpdir):
2276 # Meaning of tmpdir has changed since 1.3: we used to create
2283 # Meaning of tmpdir has changed since 1.3: we used to create
2277 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2284 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2278 # tmpdir already exists.
2285 # tmpdir already exists.
2279 print("error: temp dir %r already exists" % tmpdir)
2286 print("error: temp dir %r already exists" % tmpdir)
2280 return 1
2287 return 1
2281
2288
2282 # Automatically removing tmpdir sounds convenient, but could
2289 # Automatically removing tmpdir sounds convenient, but could
2283 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2290 # really annoy anyone in the habit of using "--tmpdir=/tmp"
2284 # or "--tmpdir=$HOME".
2291 # or "--tmpdir=$HOME".
2285 #vlog("# Removing temp dir", tmpdir)
2292 #vlog("# Removing temp dir", tmpdir)
2286 #shutil.rmtree(tmpdir)
2293 #shutil.rmtree(tmpdir)
2287 os.makedirs(tmpdir)
2294 os.makedirs(tmpdir)
2288 else:
2295 else:
2289 d = None
2296 d = None
2290 if os.name == 'nt':
2297 if os.name == 'nt':
2291 # without this, we get the default temp dir location, but
2298 # without this, we get the default temp dir location, but
2292 # in all lowercase, which causes troubles with paths (issue3490)
2299 # in all lowercase, which causes troubles with paths (issue3490)
2293 d = osenvironb.get(b'TMP', None)
2300 d = osenvironb.get(b'TMP', None)
2294 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2301 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2295
2302
2296 self._hgtmp = osenvironb[b'HGTMP'] = (
2303 self._hgtmp = osenvironb[b'HGTMP'] = (
2297 os.path.realpath(tmpdir))
2304 os.path.realpath(tmpdir))
2298
2305
2299 if self.options.with_hg:
2306 if self.options.with_hg:
2300 self._installdir = None
2307 self._installdir = None
2301 whg = self.options.with_hg
2308 whg = self.options.with_hg
2302 self._bindir = os.path.dirname(os.path.realpath(whg))
2309 self._bindir = os.path.dirname(os.path.realpath(whg))
2303 assert isinstance(self._bindir, bytes)
2310 assert isinstance(self._bindir, bytes)
2304 self._hgcommand = os.path.basename(whg)
2311 self._hgcommand = os.path.basename(whg)
2305 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2312 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2306 os.makedirs(self._tmpbindir)
2313 os.makedirs(self._tmpbindir)
2307
2314
2308 # This looks redundant with how Python initializes sys.path from
2315 # This looks redundant with how Python initializes sys.path from
2309 # the location of the script being executed. Needed because the
2316 # the location of the script being executed. Needed because the
2310 # "hg" specified by --with-hg is not the only Python script
2317 # "hg" specified by --with-hg is not the only Python script
2311 # executed in the test suite that needs to import 'mercurial'
2318 # executed in the test suite that needs to import 'mercurial'
2312 # ... which means it's not really redundant at all.
2319 # ... which means it's not really redundant at all.
2313 self._pythondir = self._bindir
2320 self._pythondir = self._bindir
2314 else:
2321 else:
2315 self._installdir = os.path.join(self._hgtmp, b"install")
2322 self._installdir = os.path.join(self._hgtmp, b"install")
2316 self._bindir = os.path.join(self._installdir, b"bin")
2323 self._bindir = os.path.join(self._installdir, b"bin")
2317 self._hgcommand = b'hg'
2324 self._hgcommand = b'hg'
2318 self._tmpbindir = self._bindir
2325 self._tmpbindir = self._bindir
2319 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2326 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2320
2327
2321 # set CHGHG, then replace "hg" command by "chg"
2328 # set CHGHG, then replace "hg" command by "chg"
2322 chgbindir = self._bindir
2329 chgbindir = self._bindir
2323 if self.options.chg or self.options.with_chg:
2330 if self.options.chg or self.options.with_chg:
2324 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2331 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2325 else:
2332 else:
2326 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2333 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2327 if self.options.chg:
2334 if self.options.chg:
2328 self._hgcommand = b'chg'
2335 self._hgcommand = b'chg'
2329 elif self.options.with_chg:
2336 elif self.options.with_chg:
2330 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2337 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2331 self._hgcommand = os.path.basename(self.options.with_chg)
2338 self._hgcommand = os.path.basename(self.options.with_chg)
2332
2339
2333 osenvironb[b"BINDIR"] = self._bindir
2340 osenvironb[b"BINDIR"] = self._bindir
2334 osenvironb[b"PYTHON"] = PYTHON
2341 osenvironb[b"PYTHON"] = PYTHON
2335
2342
2336 if self.options.with_python3:
2343 if self.options.with_python3:
2337 osenvironb[b'PYTHON3'] = self.options.with_python3
2344 osenvironb[b'PYTHON3'] = self.options.with_python3
2338
2345
2339 fileb = _bytespath(__file__)
2346 fileb = _bytespath(__file__)
2340 runtestdir = os.path.abspath(os.path.dirname(fileb))
2347 runtestdir = os.path.abspath(os.path.dirname(fileb))
2341 osenvironb[b'RUNTESTDIR'] = runtestdir
2348 osenvironb[b'RUNTESTDIR'] = runtestdir
2342 if PYTHON3:
2349 if PYTHON3:
2343 sepb = _bytespath(os.pathsep)
2350 sepb = _bytespath(os.pathsep)
2344 else:
2351 else:
2345 sepb = os.pathsep
2352 sepb = os.pathsep
2346 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2353 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2347 if os.path.islink(__file__):
2354 if os.path.islink(__file__):
2348 # test helper will likely be at the end of the symlink
2355 # test helper will likely be at the end of the symlink
2349 realfile = os.path.realpath(fileb)
2356 realfile = os.path.realpath(fileb)
2350 realdir = os.path.abspath(os.path.dirname(realfile))
2357 realdir = os.path.abspath(os.path.dirname(realfile))
2351 path.insert(2, realdir)
2358 path.insert(2, realdir)
2352 if chgbindir != self._bindir:
2359 if chgbindir != self._bindir:
2353 path.insert(1, chgbindir)
2360 path.insert(1, chgbindir)
2354 if self._testdir != runtestdir:
2361 if self._testdir != runtestdir:
2355 path = [self._testdir] + path
2362 path = [self._testdir] + path
2356 if self._tmpbindir != self._bindir:
2363 if self._tmpbindir != self._bindir:
2357 path = [self._tmpbindir] + path
2364 path = [self._tmpbindir] + path
2358 osenvironb[b"PATH"] = sepb.join(path)
2365 osenvironb[b"PATH"] = sepb.join(path)
2359
2366
2360 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2367 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2361 # can run .../tests/run-tests.py test-foo where test-foo
2368 # can run .../tests/run-tests.py test-foo where test-foo
2362 # adds an extension to HGRC. Also include run-test.py directory to
2369 # adds an extension to HGRC. Also include run-test.py directory to
2363 # import modules like heredoctest.
2370 # import modules like heredoctest.
2364 pypath = [self._pythondir, self._testdir, runtestdir]
2371 pypath = [self._pythondir, self._testdir, runtestdir]
2365 # We have to augment PYTHONPATH, rather than simply replacing
2372 # We have to augment PYTHONPATH, rather than simply replacing
2366 # it, in case external libraries are only available via current
2373 # it, in case external libraries are only available via current
2367 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2374 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2368 # are in /opt/subversion.)
2375 # are in /opt/subversion.)
2369 oldpypath = osenvironb.get(IMPL_PATH)
2376 oldpypath = osenvironb.get(IMPL_PATH)
2370 if oldpypath:
2377 if oldpypath:
2371 pypath.append(oldpypath)
2378 pypath.append(oldpypath)
2372 osenvironb[IMPL_PATH] = sepb.join(pypath)
2379 osenvironb[IMPL_PATH] = sepb.join(pypath)
2373
2380
2374 if self.options.pure:
2381 if self.options.pure:
2375 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2382 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2376 os.environ["HGMODULEPOLICY"] = "py"
2383 os.environ["HGMODULEPOLICY"] = "py"
2377
2384
2378 if self.options.allow_slow_tests:
2385 if self.options.allow_slow_tests:
2379 os.environ["HGTEST_SLOW"] = "slow"
2386 os.environ["HGTEST_SLOW"] = "slow"
2380 elif 'HGTEST_SLOW' in os.environ:
2387 elif 'HGTEST_SLOW' in os.environ:
2381 del os.environ['HGTEST_SLOW']
2388 del os.environ['HGTEST_SLOW']
2382
2389
2383 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2390 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2384
2391
2385 vlog("# Using TESTDIR", self._testdir)
2392 vlog("# Using TESTDIR", self._testdir)
2386 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2393 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2387 vlog("# Using HGTMP", self._hgtmp)
2394 vlog("# Using HGTMP", self._hgtmp)
2388 vlog("# Using PATH", os.environ["PATH"])
2395 vlog("# Using PATH", os.environ["PATH"])
2389 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2396 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2390 vlog("# Writing to directory", self._outputdir)
2397 vlog("# Writing to directory", self._outputdir)
2391
2398
2392 try:
2399 try:
2393 return self._runtests(testdescs) or 0
2400 return self._runtests(testdescs) or 0
2394 finally:
2401 finally:
2395 time.sleep(.1)
2402 time.sleep(.1)
2396 self._cleanup()
2403 self._cleanup()
2397
2404
2398 def findtests(self, args):
2405 def findtests(self, args):
2399 """Finds possible test files from arguments.
2406 """Finds possible test files from arguments.
2400
2407
2401 If you wish to inject custom tests into the test harness, this would
2408 If you wish to inject custom tests into the test harness, this would
2402 be a good function to monkeypatch or override in a derived class.
2409 be a good function to monkeypatch or override in a derived class.
2403 """
2410 """
2404 if not args:
2411 if not args:
2405 if self.options.changed:
2412 if self.options.changed:
2406 proc = Popen4('hg st --rev "%s" -man0 .' %
2413 proc = Popen4('hg st --rev "%s" -man0 .' %
2407 self.options.changed, None, 0)
2414 self.options.changed, None, 0)
2408 stdout, stderr = proc.communicate()
2415 stdout, stderr = proc.communicate()
2409 args = stdout.strip(b'\0').split(b'\0')
2416 args = stdout.strip(b'\0').split(b'\0')
2410 else:
2417 else:
2411 args = os.listdir(b'.')
2418 args = os.listdir(b'.')
2412
2419
2413 tests = []
2420 tests = []
2414 for t in args:
2421 for t in args:
2415 if not (os.path.basename(t).startswith(b'test-')
2422 if not (os.path.basename(t).startswith(b'test-')
2416 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2423 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2417 continue
2424 continue
2418 if t.endswith(b'.t'):
2425 if t.endswith(b'.t'):
2419 # .t file may contain multiple test cases
2426 # .t file may contain multiple test cases
2420 cases = sorted(parsettestcases(t))
2427 cases = sorted(parsettestcases(t))
2421 if cases:
2428 if cases:
2422 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2429 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2423 else:
2430 else:
2424 tests.append({'path': t})
2431 tests.append({'path': t})
2425 else:
2432 else:
2426 tests.append({'path': t})
2433 tests.append({'path': t})
2427 return tests
2434 return tests
2428
2435
2429 def _runtests(self, testdescs):
2436 def _runtests(self, testdescs):
2430 def _reloadtest(test, i):
2437 def _reloadtest(test, i):
2431 # convert a test back to its description dict
2438 # convert a test back to its description dict
2432 desc = {'path': test.path}
2439 desc = {'path': test.path}
2433 case = getattr(test, '_case', None)
2440 case = getattr(test, '_case', None)
2434 if case:
2441 if case:
2435 desc['case'] = case
2442 desc['case'] = case
2436 return self._gettest(desc, i)
2443 return self._gettest(desc, i)
2437
2444
2438 try:
2445 try:
2439 if self.options.restart:
2446 if self.options.restart:
2440 orig = list(testdescs)
2447 orig = list(testdescs)
2441 while testdescs:
2448 while testdescs:
2442 desc = testdescs[0]
2449 desc = testdescs[0]
2443 # desc['path'] is a relative path
2450 # desc['path'] is a relative path
2444 if 'case' in desc:
2451 if 'case' in desc:
2445 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2452 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2446 else:
2453 else:
2447 errpath = b'%s.err' % desc['path']
2454 errpath = b'%s.err' % desc['path']
2448 errpath = os.path.join(self._outputdir, errpath)
2455 errpath = os.path.join(self._outputdir, errpath)
2449 if os.path.exists(errpath):
2456 if os.path.exists(errpath):
2450 break
2457 break
2451 testdescs.pop(0)
2458 testdescs.pop(0)
2452 if not testdescs:
2459 if not testdescs:
2453 print("running all tests")
2460 print("running all tests")
2454 testdescs = orig
2461 testdescs = orig
2455
2462
2456 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2463 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2457
2464
2458 failed = False
2465 failed = False
2459 kws = self.options.keywords
2466 kws = self.options.keywords
2460 if kws is not None and PYTHON3:
2467 if kws is not None and PYTHON3:
2461 kws = kws.encode('utf-8')
2468 kws = kws.encode('utf-8')
2462
2469
2463 suite = TestSuite(self._testdir,
2470 suite = TestSuite(self._testdir,
2464 jobs=self.options.jobs,
2471 jobs=self.options.jobs,
2465 whitelist=self.options.whitelisted,
2472 whitelist=self.options.whitelisted,
2466 blacklist=self.options.blacklist,
2473 blacklist=self.options.blacklist,
2467 retest=self.options.retest,
2474 retest=self.options.retest,
2468 keywords=kws,
2475 keywords=kws,
2469 loop=self.options.loop,
2476 loop=self.options.loop,
2470 runs_per_test=self.options.runs_per_test,
2477 runs_per_test=self.options.runs_per_test,
2471 showchannels=self.options.showchannels,
2478 showchannels=self.options.showchannels,
2472 tests=tests, loadtest=_reloadtest)
2479 tests=tests, loadtest=_reloadtest)
2473 verbosity = 1
2480 verbosity = 1
2474 if self.options.verbose:
2481 if self.options.verbose:
2475 verbosity = 2
2482 verbosity = 2
2476 runner = TextTestRunner(self, verbosity=verbosity)
2483 runner = TextTestRunner(self, verbosity=verbosity)
2477
2484
2478 if self.options.list_tests:
2485 if self.options.list_tests:
2479 result = runner.listtests(suite)
2486 result = runner.listtests(suite)
2480 else:
2487 else:
2481 if self._installdir:
2488 if self._installdir:
2482 self._installhg()
2489 self._installhg()
2483 self._checkhglib("Testing")
2490 self._checkhglib("Testing")
2484 else:
2491 else:
2485 self._usecorrectpython()
2492 self._usecorrectpython()
2486 if self.options.chg:
2493 if self.options.chg:
2487 assert self._installdir
2494 assert self._installdir
2488 self._installchg()
2495 self._installchg()
2489
2496
2490 result = runner.run(suite)
2497 result = runner.run(suite)
2491
2498
2492 if result.failures:
2499 if result.failures:
2493 failed = True
2500 failed = True
2494
2501
2495 if self.options.anycoverage:
2502 if self.options.anycoverage:
2496 self._outputcoverage()
2503 self._outputcoverage()
2497 except KeyboardInterrupt:
2504 except KeyboardInterrupt:
2498 failed = True
2505 failed = True
2499 print("\ninterrupted!")
2506 print("\ninterrupted!")
2500
2507
2501 if failed:
2508 if failed:
2502 return 1
2509 return 1
2503
2510
2504 def _getport(self, count):
2511 def _getport(self, count):
2505 port = self._ports.get(count) # do we have a cached entry?
2512 port = self._ports.get(count) # do we have a cached entry?
2506 if port is None:
2513 if port is None:
2507 portneeded = 3
2514 portneeded = 3
2508 # above 100 tries we just give up and let test reports failure
2515 # above 100 tries we just give up and let test reports failure
2509 for tries in xrange(100):
2516 for tries in xrange(100):
2510 allfree = True
2517 allfree = True
2511 port = self.options.port + self._portoffset
2518 port = self.options.port + self._portoffset
2512 for idx in xrange(portneeded):
2519 for idx in xrange(portneeded):
2513 if not checkportisavailable(port + idx):
2520 if not checkportisavailable(port + idx):
2514 allfree = False
2521 allfree = False
2515 break
2522 break
2516 self._portoffset += portneeded
2523 self._portoffset += portneeded
2517 if allfree:
2524 if allfree:
2518 break
2525 break
2519 self._ports[count] = port
2526 self._ports[count] = port
2520 return port
2527 return port
2521
2528
2522 def _gettest(self, testdesc, count):
2529 def _gettest(self, testdesc, count):
2523 """Obtain a Test by looking at its filename.
2530 """Obtain a Test by looking at its filename.
2524
2531
2525 Returns a Test instance. The Test may not be runnable if it doesn't
2532 Returns a Test instance. The Test may not be runnable if it doesn't
2526 map to a known type.
2533 map to a known type.
2527 """
2534 """
2528 path = testdesc['path']
2535 path = testdesc['path']
2529 lctest = path.lower()
2536 lctest = path.lower()
2530 testcls = Test
2537 testcls = Test
2531
2538
2532 for ext, cls in self.TESTTYPES:
2539 for ext, cls in self.TESTTYPES:
2533 if lctest.endswith(ext):
2540 if lctest.endswith(ext):
2534 testcls = cls
2541 testcls = cls
2535 break
2542 break
2536
2543
2537 refpath = os.path.join(self._testdir, path)
2544 refpath = os.path.join(self._testdir, path)
2538 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2545 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2539
2546
2540 # extra keyword parameters. 'case' is used by .t tests
2547 # extra keyword parameters. 'case' is used by .t tests
2541 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2548 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2542
2549
2543 t = testcls(refpath, self._outputdir, tmpdir,
2550 t = testcls(refpath, self._outputdir, tmpdir,
2544 keeptmpdir=self.options.keep_tmpdir,
2551 keeptmpdir=self.options.keep_tmpdir,
2545 debug=self.options.debug,
2552 debug=self.options.debug,
2546 timeout=self.options.timeout,
2553 timeout=self.options.timeout,
2547 startport=self._getport(count),
2554 startport=self._getport(count),
2548 extraconfigopts=self.options.extra_config_opt,
2555 extraconfigopts=self.options.extra_config_opt,
2549 py3kwarnings=self.options.py3k_warnings,
2556 py3kwarnings=self.options.py3k_warnings,
2550 shell=self.options.shell,
2557 shell=self.options.shell,
2551 hgcommand=self._hgcommand,
2558 hgcommand=self._hgcommand,
2552 usechg=bool(self.options.with_chg or self.options.chg),
2559 usechg=bool(self.options.with_chg or self.options.chg),
2553 useipv6=useipv6, **kwds)
2560 useipv6=useipv6, **kwds)
2554 t.should_reload = True
2561 t.should_reload = True
2555 return t
2562 return t
2556
2563
2557 def _cleanup(self):
2564 def _cleanup(self):
2558 """Clean up state from this test invocation."""
2565 """Clean up state from this test invocation."""
2559 if self.options.keep_tmpdir:
2566 if self.options.keep_tmpdir:
2560 return
2567 return
2561
2568
2562 vlog("# Cleaning up HGTMP", self._hgtmp)
2569 vlog("# Cleaning up HGTMP", self._hgtmp)
2563 shutil.rmtree(self._hgtmp, True)
2570 shutil.rmtree(self._hgtmp, True)
2564 for f in self._createdfiles:
2571 for f in self._createdfiles:
2565 try:
2572 try:
2566 os.remove(f)
2573 os.remove(f)
2567 except OSError:
2574 except OSError:
2568 pass
2575 pass
2569
2576
2570 def _usecorrectpython(self):
2577 def _usecorrectpython(self):
2571 """Configure the environment to use the appropriate Python in tests."""
2578 """Configure the environment to use the appropriate Python in tests."""
2572 # Tests must use the same interpreter as us or bad things will happen.
2579 # Tests must use the same interpreter as us or bad things will happen.
2573 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2580 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2574 if getattr(os, 'symlink', None):
2581 if getattr(os, 'symlink', None):
2575 vlog("# Making python executable in test path a symlink to '%s'" %
2582 vlog("# Making python executable in test path a symlink to '%s'" %
2576 sys.executable)
2583 sys.executable)
2577 mypython = os.path.join(self._tmpbindir, pyexename)
2584 mypython = os.path.join(self._tmpbindir, pyexename)
2578 try:
2585 try:
2579 if os.readlink(mypython) == sys.executable:
2586 if os.readlink(mypython) == sys.executable:
2580 return
2587 return
2581 os.unlink(mypython)
2588 os.unlink(mypython)
2582 except OSError as err:
2589 except OSError as err:
2583 if err.errno != errno.ENOENT:
2590 if err.errno != errno.ENOENT:
2584 raise
2591 raise
2585 if self._findprogram(pyexename) != sys.executable:
2592 if self._findprogram(pyexename) != sys.executable:
2586 try:
2593 try:
2587 os.symlink(sys.executable, mypython)
2594 os.symlink(sys.executable, mypython)
2588 self._createdfiles.append(mypython)
2595 self._createdfiles.append(mypython)
2589 except OSError as err:
2596 except OSError as err:
2590 # child processes may race, which is harmless
2597 # child processes may race, which is harmless
2591 if err.errno != errno.EEXIST:
2598 if err.errno != errno.EEXIST:
2592 raise
2599 raise
2593 else:
2600 else:
2594 exedir, exename = os.path.split(sys.executable)
2601 exedir, exename = os.path.split(sys.executable)
2595 vlog("# Modifying search path to find %s as %s in '%s'" %
2602 vlog("# Modifying search path to find %s as %s in '%s'" %
2596 (exename, pyexename, exedir))
2603 (exename, pyexename, exedir))
2597 path = os.environ['PATH'].split(os.pathsep)
2604 path = os.environ['PATH'].split(os.pathsep)
2598 while exedir in path:
2605 while exedir in path:
2599 path.remove(exedir)
2606 path.remove(exedir)
2600 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2607 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2601 if not self._findprogram(pyexename):
2608 if not self._findprogram(pyexename):
2602 print("WARNING: Cannot find %s in search path" % pyexename)
2609 print("WARNING: Cannot find %s in search path" % pyexename)
2603
2610
2604 def _installhg(self):
2611 def _installhg(self):
2605 """Install hg into the test environment.
2612 """Install hg into the test environment.
2606
2613
2607 This will also configure hg with the appropriate testing settings.
2614 This will also configure hg with the appropriate testing settings.
2608 """
2615 """
2609 vlog("# Performing temporary installation of HG")
2616 vlog("# Performing temporary installation of HG")
2610 installerrs = os.path.join(self._hgtmp, b"install.err")
2617 installerrs = os.path.join(self._hgtmp, b"install.err")
2611 compiler = ''
2618 compiler = ''
2612 if self.options.compiler:
2619 if self.options.compiler:
2613 compiler = '--compiler ' + self.options.compiler
2620 compiler = '--compiler ' + self.options.compiler
2614 if self.options.pure:
2621 if self.options.pure:
2615 pure = b"--pure"
2622 pure = b"--pure"
2616 else:
2623 else:
2617 pure = b""
2624 pure = b""
2618
2625
2619 # Run installer in hg root
2626 # Run installer in hg root
2620 script = os.path.realpath(sys.argv[0])
2627 script = os.path.realpath(sys.argv[0])
2621 exe = sys.executable
2628 exe = sys.executable
2622 if PYTHON3:
2629 if PYTHON3:
2623 compiler = _bytespath(compiler)
2630 compiler = _bytespath(compiler)
2624 script = _bytespath(script)
2631 script = _bytespath(script)
2625 exe = _bytespath(exe)
2632 exe = _bytespath(exe)
2626 hgroot = os.path.dirname(os.path.dirname(script))
2633 hgroot = os.path.dirname(os.path.dirname(script))
2627 self._hgroot = hgroot
2634 self._hgroot = hgroot
2628 os.chdir(hgroot)
2635 os.chdir(hgroot)
2629 nohome = b'--home=""'
2636 nohome = b'--home=""'
2630 if os.name == 'nt':
2637 if os.name == 'nt':
2631 # The --home="" trick works only on OS where os.sep == '/'
2638 # The --home="" trick works only on OS where os.sep == '/'
2632 # because of a distutils convert_path() fast-path. Avoid it at
2639 # because of a distutils convert_path() fast-path. Avoid it at
2633 # least on Windows for now, deal with .pydistutils.cfg bugs
2640 # least on Windows for now, deal with .pydistutils.cfg bugs
2634 # when they happen.
2641 # when they happen.
2635 nohome = b''
2642 nohome = b''
2636 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2643 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2637 b' build %(compiler)s --build-base="%(base)s"'
2644 b' build %(compiler)s --build-base="%(base)s"'
2638 b' install --force --prefix="%(prefix)s"'
2645 b' install --force --prefix="%(prefix)s"'
2639 b' --install-lib="%(libdir)s"'
2646 b' --install-lib="%(libdir)s"'
2640 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2647 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2641 % {b'exe': exe, b'pure': pure,
2648 % {b'exe': exe, b'pure': pure,
2642 b'compiler': compiler,
2649 b'compiler': compiler,
2643 b'base': os.path.join(self._hgtmp, b"build"),
2650 b'base': os.path.join(self._hgtmp, b"build"),
2644 b'prefix': self._installdir, b'libdir': self._pythondir,
2651 b'prefix': self._installdir, b'libdir': self._pythondir,
2645 b'bindir': self._bindir,
2652 b'bindir': self._bindir,
2646 b'nohome': nohome, b'logfile': installerrs})
2653 b'nohome': nohome, b'logfile': installerrs})
2647
2654
2648 # setuptools requires install directories to exist.
2655 # setuptools requires install directories to exist.
2649 def makedirs(p):
2656 def makedirs(p):
2650 try:
2657 try:
2651 os.makedirs(p)
2658 os.makedirs(p)
2652 except OSError as e:
2659 except OSError as e:
2653 if e.errno != errno.EEXIST:
2660 if e.errno != errno.EEXIST:
2654 raise
2661 raise
2655 makedirs(self._pythondir)
2662 makedirs(self._pythondir)
2656 makedirs(self._bindir)
2663 makedirs(self._bindir)
2657
2664
2658 vlog("# Running", cmd)
2665 vlog("# Running", cmd)
2659 if os.system(cmd) == 0:
2666 if os.system(cmd) == 0:
2660 if not self.options.verbose:
2667 if not self.options.verbose:
2661 try:
2668 try:
2662 os.remove(installerrs)
2669 os.remove(installerrs)
2663 except OSError as e:
2670 except OSError as e:
2664 if e.errno != errno.ENOENT:
2671 if e.errno != errno.ENOENT:
2665 raise
2672 raise
2666 else:
2673 else:
2667 f = open(installerrs, 'rb')
2674 f = open(installerrs, 'rb')
2668 for line in f:
2675 for line in f:
2669 if PYTHON3:
2676 if PYTHON3:
2670 sys.stdout.buffer.write(line)
2677 sys.stdout.buffer.write(line)
2671 else:
2678 else:
2672 sys.stdout.write(line)
2679 sys.stdout.write(line)
2673 f.close()
2680 f.close()
2674 sys.exit(1)
2681 sys.exit(1)
2675 os.chdir(self._testdir)
2682 os.chdir(self._testdir)
2676
2683
2677 self._usecorrectpython()
2684 self._usecorrectpython()
2678
2685
2679 if self.options.py3k_warnings and not self.options.anycoverage:
2686 if self.options.py3k_warnings and not self.options.anycoverage:
2680 vlog("# Updating hg command to enable Py3k Warnings switch")
2687 vlog("# Updating hg command to enable Py3k Warnings switch")
2681 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2688 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2682 lines = [line.rstrip() for line in f]
2689 lines = [line.rstrip() for line in f]
2683 lines[0] += ' -3'
2690 lines[0] += ' -3'
2684 f.close()
2691 f.close()
2685 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2692 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2686 for line in lines:
2693 for line in lines:
2687 f.write(line + '\n')
2694 f.write(line + '\n')
2688 f.close()
2695 f.close()
2689
2696
2690 hgbat = os.path.join(self._bindir, b'hg.bat')
2697 hgbat = os.path.join(self._bindir, b'hg.bat')
2691 if os.path.isfile(hgbat):
2698 if os.path.isfile(hgbat):
2692 # hg.bat expects to be put in bin/scripts while run-tests.py
2699 # hg.bat expects to be put in bin/scripts while run-tests.py
2693 # installation layout put it in bin/ directly. Fix it
2700 # installation layout put it in bin/ directly. Fix it
2694 f = open(hgbat, 'rb')
2701 f = open(hgbat, 'rb')
2695 data = f.read()
2702 data = f.read()
2696 f.close()
2703 f.close()
2697 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2704 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2698 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2705 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2699 b'"%~dp0python" "%~dp0hg" %*')
2706 b'"%~dp0python" "%~dp0hg" %*')
2700 f = open(hgbat, 'wb')
2707 f = open(hgbat, 'wb')
2701 f.write(data)
2708 f.write(data)
2702 f.close()
2709 f.close()
2703 else:
2710 else:
2704 print('WARNING: cannot fix hg.bat reference to python.exe')
2711 print('WARNING: cannot fix hg.bat reference to python.exe')
2705
2712
2706 if self.options.anycoverage:
2713 if self.options.anycoverage:
2707 custom = os.path.join(self._testdir, 'sitecustomize.py')
2714 custom = os.path.join(self._testdir, 'sitecustomize.py')
2708 target = os.path.join(self._pythondir, 'sitecustomize.py')
2715 target = os.path.join(self._pythondir, 'sitecustomize.py')
2709 vlog('# Installing coverage trigger to %s' % target)
2716 vlog('# Installing coverage trigger to %s' % target)
2710 shutil.copyfile(custom, target)
2717 shutil.copyfile(custom, target)
2711 rc = os.path.join(self._testdir, '.coveragerc')
2718 rc = os.path.join(self._testdir, '.coveragerc')
2712 vlog('# Installing coverage rc to %s' % rc)
2719 vlog('# Installing coverage rc to %s' % rc)
2713 os.environ['COVERAGE_PROCESS_START'] = rc
2720 os.environ['COVERAGE_PROCESS_START'] = rc
2714 covdir = os.path.join(self._installdir, '..', 'coverage')
2721 covdir = os.path.join(self._installdir, '..', 'coverage')
2715 try:
2722 try:
2716 os.mkdir(covdir)
2723 os.mkdir(covdir)
2717 except OSError as e:
2724 except OSError as e:
2718 if e.errno != errno.EEXIST:
2725 if e.errno != errno.EEXIST:
2719 raise
2726 raise
2720
2727
2721 os.environ['COVERAGE_DIR'] = covdir
2728 os.environ['COVERAGE_DIR'] = covdir
2722
2729
2723 def _checkhglib(self, verb):
2730 def _checkhglib(self, verb):
2724 """Ensure that the 'mercurial' package imported by python is
2731 """Ensure that the 'mercurial' package imported by python is
2725 the one we expect it to be. If not, print a warning to stderr."""
2732 the one we expect it to be. If not, print a warning to stderr."""
2726 if ((self._bindir == self._pythondir) and
2733 if ((self._bindir == self._pythondir) and
2727 (self._bindir != self._tmpbindir)):
2734 (self._bindir != self._tmpbindir)):
2728 # The pythondir has been inferred from --with-hg flag.
2735 # The pythondir has been inferred from --with-hg flag.
2729 # We cannot expect anything sensible here.
2736 # We cannot expect anything sensible here.
2730 return
2737 return
2731 expecthg = os.path.join(self._pythondir, b'mercurial')
2738 expecthg = os.path.join(self._pythondir, b'mercurial')
2732 actualhg = self._gethgpath()
2739 actualhg = self._gethgpath()
2733 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2740 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2734 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2741 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2735 ' (expected %s)\n'
2742 ' (expected %s)\n'
2736 % (verb, actualhg, expecthg))
2743 % (verb, actualhg, expecthg))
2737 def _gethgpath(self):
2744 def _gethgpath(self):
2738 """Return the path to the mercurial package that is actually found by
2745 """Return the path to the mercurial package that is actually found by
2739 the current Python interpreter."""
2746 the current Python interpreter."""
2740 if self._hgpath is not None:
2747 if self._hgpath is not None:
2741 return self._hgpath
2748 return self._hgpath
2742
2749
2743 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2750 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2744 cmd = cmd % PYTHON
2751 cmd = cmd % PYTHON
2745 if PYTHON3:
2752 if PYTHON3:
2746 cmd = _strpath(cmd)
2753 cmd = _strpath(cmd)
2747 pipe = os.popen(cmd)
2754 pipe = os.popen(cmd)
2748 try:
2755 try:
2749 self._hgpath = _bytespath(pipe.read().strip())
2756 self._hgpath = _bytespath(pipe.read().strip())
2750 finally:
2757 finally:
2751 pipe.close()
2758 pipe.close()
2752
2759
2753 return self._hgpath
2760 return self._hgpath
2754
2761
2755 def _installchg(self):
2762 def _installchg(self):
2756 """Install chg into the test environment"""
2763 """Install chg into the test environment"""
2757 vlog('# Performing temporary installation of CHG')
2764 vlog('# Performing temporary installation of CHG')
2758 assert os.path.dirname(self._bindir) == self._installdir
2765 assert os.path.dirname(self._bindir) == self._installdir
2759 assert self._hgroot, 'must be called after _installhg()'
2766 assert self._hgroot, 'must be called after _installhg()'
2760 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2767 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2761 % {b'make': 'make', # TODO: switch by option or environment?
2768 % {b'make': 'make', # TODO: switch by option or environment?
2762 b'prefix': self._installdir})
2769 b'prefix': self._installdir})
2763 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2770 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2764 vlog("# Running", cmd)
2771 vlog("# Running", cmd)
2765 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2772 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2766 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2773 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2767 stderr=subprocess.STDOUT)
2774 stderr=subprocess.STDOUT)
2768 out, _err = proc.communicate()
2775 out, _err = proc.communicate()
2769 if proc.returncode != 0:
2776 if proc.returncode != 0:
2770 if PYTHON3:
2777 if PYTHON3:
2771 sys.stdout.buffer.write(out)
2778 sys.stdout.buffer.write(out)
2772 else:
2779 else:
2773 sys.stdout.write(out)
2780 sys.stdout.write(out)
2774 sys.exit(1)
2781 sys.exit(1)
2775
2782
2776 def _outputcoverage(self):
2783 def _outputcoverage(self):
2777 """Produce code coverage output."""
2784 """Produce code coverage output."""
2778 import coverage
2785 import coverage
2779 coverage = coverage.coverage
2786 coverage = coverage.coverage
2780
2787
2781 vlog('# Producing coverage report')
2788 vlog('# Producing coverage report')
2782 # chdir is the easiest way to get short, relative paths in the
2789 # chdir is the easiest way to get short, relative paths in the
2783 # output.
2790 # output.
2784 os.chdir(self._hgroot)
2791 os.chdir(self._hgroot)
2785 covdir = os.path.join(self._installdir, '..', 'coverage')
2792 covdir = os.path.join(self._installdir, '..', 'coverage')
2786 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2793 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2787
2794
2788 # Map install directory paths back to source directory.
2795 # Map install directory paths back to source directory.
2789 cov.config.paths['srcdir'] = ['.', self._pythondir]
2796 cov.config.paths['srcdir'] = ['.', self._pythondir]
2790
2797
2791 cov.combine()
2798 cov.combine()
2792
2799
2793 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2800 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2794 cov.report(ignore_errors=True, omit=omit)
2801 cov.report(ignore_errors=True, omit=omit)
2795
2802
2796 if self.options.htmlcov:
2803 if self.options.htmlcov:
2797 htmldir = os.path.join(self._outputdir, 'htmlcov')
2804 htmldir = os.path.join(self._outputdir, 'htmlcov')
2798 cov.html_report(directory=htmldir, omit=omit)
2805 cov.html_report(directory=htmldir, omit=omit)
2799 if self.options.annotate:
2806 if self.options.annotate:
2800 adir = os.path.join(self._outputdir, 'annotated')
2807 adir = os.path.join(self._outputdir, 'annotated')
2801 if not os.path.isdir(adir):
2808 if not os.path.isdir(adir):
2802 os.mkdir(adir)
2809 os.mkdir(adir)
2803 cov.annotate(directory=adir, omit=omit)
2810 cov.annotate(directory=adir, omit=omit)
2804
2811
2805 def _findprogram(self, program):
2812 def _findprogram(self, program):
2806 """Search PATH for a executable program"""
2813 """Search PATH for a executable program"""
2807 dpb = _bytespath(os.defpath)
2814 dpb = _bytespath(os.defpath)
2808 sepb = _bytespath(os.pathsep)
2815 sepb = _bytespath(os.pathsep)
2809 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2816 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2810 name = os.path.join(p, program)
2817 name = os.path.join(p, program)
2811 if os.name == 'nt' or os.access(name, os.X_OK):
2818 if os.name == 'nt' or os.access(name, os.X_OK):
2812 return name
2819 return name
2813 return None
2820 return None
2814
2821
2815 def _checktools(self):
2822 def _checktools(self):
2816 """Ensure tools required to run tests are present."""
2823 """Ensure tools required to run tests are present."""
2817 for p in self.REQUIREDTOOLS:
2824 for p in self.REQUIREDTOOLS:
2818 if os.name == 'nt' and not p.endswith('.exe'):
2825 if os.name == 'nt' and not p.endswith('.exe'):
2819 p += '.exe'
2826 p += '.exe'
2820 found = self._findprogram(p)
2827 found = self._findprogram(p)
2821 if found:
2828 if found:
2822 vlog("# Found prerequisite", p, "at", found)
2829 vlog("# Found prerequisite", p, "at", found)
2823 else:
2830 else:
2824 print("WARNING: Did not find prerequisite tool: %s " %
2831 print("WARNING: Did not find prerequisite tool: %s " %
2825 p.decode("utf-8"))
2832 p.decode("utf-8"))
2826
2833
2827 if __name__ == '__main__':
2834 if __name__ == '__main__':
2828 runner = TestRunner()
2835 runner = TestRunner()
2829
2836
2830 try:
2837 try:
2831 import msvcrt
2838 import msvcrt
2832 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2839 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2833 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2840 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2834 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2841 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2835 except ImportError:
2842 except ImportError:
2836 pass
2843 pass
2837
2844
2838 sys.exit(runner.run(sys.argv[1:]))
2845 sys.exit(runner.run(sys.argv[1:]))
@@ -1,667 +1,666 b''
1 #require serve ssl
1 #require serve ssl
2
2
3 Proper https client requires the built-in ssl from Python 2.6.
3 Proper https client requires the built-in ssl from Python 2.6.
4
4
5 Make server certificates:
5 Make server certificates:
6
6
7 $ CERTSDIR="$TESTDIR/sslcerts"
7 $ CERTSDIR="$TESTDIR/sslcerts"
8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
8 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
9 $ PRIV=`pwd`/server.pem
9 $ PRIV=`pwd`/server.pem
10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
10 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-not-yet.pem" > server-not-yet.pem
11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
11 $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub-expired.pem" > server-expired.pem
12
12
13 $ hg init test
13 $ hg init test
14 $ cd test
14 $ cd test
15 $ echo foo>foo
15 $ echo foo>foo
16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
16 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
17 $ echo foo>foo.d/foo
17 $ echo foo>foo.d/foo
18 $ echo bar>foo.d/bAr.hg.d/BaR
18 $ echo bar>foo.d/bAr.hg.d/BaR
19 $ echo bar>foo.d/baR.d.hg/bAR
19 $ echo bar>foo.d/baR.d.hg/bAR
20 $ hg commit -A -m 1
20 $ hg commit -A -m 1
21 adding foo
21 adding foo
22 adding foo.d/bAr.hg.d/BaR
22 adding foo.d/bAr.hg.d/BaR
23 adding foo.d/baR.d.hg/bAR
23 adding foo.d/baR.d.hg/bAR
24 adding foo.d/foo
24 adding foo.d/foo
25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
25 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
26 $ cat ../hg0.pid >> $DAEMON_PIDS
26 $ cat ../hg0.pid >> $DAEMON_PIDS
27
27
28 cacert not found
28 cacert not found
29
29
30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
30 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
31 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
31 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
32 abort: could not find web.cacerts: no-such.pem
32 abort: could not find web.cacerts: no-such.pem
33 [255]
33 [255]
34
34
35 Test server address cannot be reused
35 Test server address cannot be reused
36
36
37 #if windows
37 #if windows
38 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
38 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
39 abort: cannot start server at 'localhost:$HGPORT': * (glob)
39 abort: cannot start server at 'localhost:$HGPORT': * (glob)
40 [255]
40 [255]
41 #else
41 #else
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
42 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
43 abort: cannot start server at 'localhost:$HGPORT': Address already in use
43 abort: cannot start server at 'localhost:$HGPORT': Address already in use
44 [255]
44 [255]
45 #endif
45 #endif
46 $ cd ..
46 $ cd ..
47
47
48 Our test cert is not signed by a trusted CA. It should fail to verify if
48 Our test cert is not signed by a trusted CA. It should fail to verify if
49 we are able to load CA certs.
49 we are able to load CA certs.
50
50
51 #if sslcontext defaultcacerts no-defaultcacertsloaded
51 #if sslcontext defaultcacerts no-defaultcacertsloaded
52 $ hg clone https://localhost:$HGPORT/ copy-pull
52 $ hg clone https://localhost:$HGPORT/ copy-pull
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
53 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
54 abort: error: *certificate verify failed* (glob)
54 abort: error: *certificate verify failed* (glob)
55 [255]
55 [255]
56 #endif
56 #endif
57
57
58 #if no-sslcontext defaultcacerts
58 #if no-sslcontext defaultcacerts
59 $ hg clone https://localhost:$HGPORT/ copy-pull
59 $ hg clone https://localhost:$HGPORT/ copy-pull
60 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
60 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
61 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
61 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
62 abort: error: *certificate verify failed* (glob)
62 abort: error: *certificate verify failed* (glob)
63 [255]
63 [255]
64 #endif
64 #endif
65
65
66 #if no-sslcontext windows
66 #if no-sslcontext windows
67 $ hg clone https://localhost:$HGPORT/ copy-pull
67 $ hg clone https://localhost:$HGPORT/ copy-pull
68 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
68 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
69 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
69 (unable to load Windows CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
70 abort: error: *certificate verify failed* (glob)
70 abort: error: *certificate verify failed* (glob)
71 [255]
71 [255]
72 #endif
72 #endif
73
73
74 #if no-sslcontext osx
74 #if no-sslcontext osx
75 $ hg clone https://localhost:$HGPORT/ copy-pull
75 $ hg clone https://localhost:$HGPORT/ copy-pull
76 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
76 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
77 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
77 (unable to load CA certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message)
78 abort: localhost certificate error: no certificate received
78 abort: localhost certificate error: no certificate received
79 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
79 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
80 [255]
80 [255]
81 #endif
81 #endif
82
82
83 #if defaultcacertsloaded
83 #if defaultcacertsloaded
84 $ hg clone https://localhost:$HGPORT/ copy-pull
84 $ hg clone https://localhost:$HGPORT/ copy-pull
85 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
85 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
86 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
86 (using CA certificates from *; if you see this message, your Mercurial install is not properly configured; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
87 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
87 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
88 abort: error: *certificate verify failed* (glob)
88 abort: error: *certificate verify failed* (glob)
89 [255]
89 [255]
90 #endif
90 #endif
91
91
92 #if no-defaultcacerts
92 #if no-defaultcacerts
93 $ hg clone https://localhost:$HGPORT/ copy-pull
93 $ hg clone https://localhost:$HGPORT/ copy-pull
94 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
94 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
95 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
95 (unable to load * certificates; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this message) (glob) (?)
96 abort: localhost certificate error: no certificate received
96 abort: localhost certificate error: no certificate received
97 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
97 (set hostsecurity.localhost:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
98 [255]
98 [255]
99 #endif
99 #endif
100
100
101 Specifying a per-host certificate file that doesn't exist will abort. The full
101 Specifying a per-host certificate file that doesn't exist will abort. The full
102 C:/path/to/msysroot will print on Windows.
102 C:/path/to/msysroot will print on Windows.
103
103
104 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
104 $ hg --config hostsecurity.localhost:verifycertsfile=/does/not/exist clone https://localhost:$HGPORT/
105 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
105 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
106 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: */does/not/exist (glob)
106 abort: path specified by hostsecurity.localhost:verifycertsfile does not exist: */does/not/exist (glob)
107 [255]
107 [255]
108
108
109 A malformed per-host certificate file will raise an error
109 A malformed per-host certificate file will raise an error
110
110
111 $ echo baddata > badca.pem
111 $ echo baddata > badca.pem
112 #if sslcontext
112 #if sslcontext
113 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
113 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
114 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
114 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
115 abort: error loading CA file badca.pem: * (glob)
115 abort: error loading CA file badca.pem: * (glob)
116 (file is empty or malformed?)
116 (file is empty or malformed?)
117 [255]
117 [255]
118 #else
118 #else
119 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
119 $ hg --config hostsecurity.localhost:verifycertsfile=badca.pem clone https://localhost:$HGPORT/
120 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
120 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
121 abort: error: * (glob)
121 abort: error: * (glob)
122 [255]
122 [255]
123 #endif
123 #endif
124
124
125 A per-host certificate mismatching the server will fail verification
125 A per-host certificate mismatching the server will fail verification
126
126
127 (modern ssl is able to discern whether the loaded cert is a CA cert)
127 (modern ssl is able to discern whether the loaded cert is a CA cert)
128 #if sslcontext
128 #if sslcontext
129 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
129 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
130 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
130 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
131 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
131 (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
132 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
132 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
133 abort: error: *certificate verify failed* (glob)
133 abort: error: *certificate verify failed* (glob)
134 [255]
134 [255]
135 #else
135 #else
136 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
136 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/client-cert.pem" clone https://localhost:$HGPORT/
137 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
137 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
138 abort: error: *certificate verify failed* (glob)
138 abort: error: *certificate verify failed* (glob)
139 [255]
139 [255]
140 #endif
140 #endif
141
141
142 A per-host certificate matching the server's cert will be accepted
142 A per-host certificate matching the server's cert will be accepted
143
143
144 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
144 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" clone -U https://localhost:$HGPORT/ perhostgood1
145 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
145 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
146 requesting all changes
146 requesting all changes
147 adding changesets
147 adding changesets
148 adding manifests
148 adding manifests
149 adding file changes
149 adding file changes
150 added 1 changesets with 4 changes to 4 files
150 added 1 changesets with 4 changes to 4 files
151
151
152 A per-host certificate with multiple certs and one matching will be accepted
152 A per-host certificate with multiple certs and one matching will be accepted
153
153
154 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
154 $ cat "$CERTSDIR/client-cert.pem" "$CERTSDIR/pub.pem" > perhost.pem
155 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
155 $ hg --config hostsecurity.localhost:verifycertsfile=perhost.pem clone -U https://localhost:$HGPORT/ perhostgood2
156 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
156 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
157 requesting all changes
157 requesting all changes
158 adding changesets
158 adding changesets
159 adding manifests
159 adding manifests
160 adding file changes
160 adding file changes
161 added 1 changesets with 4 changes to 4 files
161 added 1 changesets with 4 changes to 4 files
162
162
163 Defining both per-host certificate and a fingerprint will print a warning
163 Defining both per-host certificate and a fingerprint will print a warning
164
164
165 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
165 $ hg --config hostsecurity.localhost:verifycertsfile="$CERTSDIR/pub.pem" --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 clone -U https://localhost:$HGPORT/ caandfingerwarning
166 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
166 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
167 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
167 (hostsecurity.localhost:verifycertsfile ignored when host fingerprints defined; using host fingerprints for verification)
168 requesting all changes
168 requesting all changes
169 adding changesets
169 adding changesets
170 adding manifests
170 adding manifests
171 adding file changes
171 adding file changes
172 added 1 changesets with 4 changes to 4 files
172 added 1 changesets with 4 changes to 4 files
173
173
174 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
174 $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
175
175
176 Inability to verify peer certificate will result in abort
176 Inability to verify peer certificate will result in abort
177
177
178 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
178 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLECACERTS
179 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
179 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
180 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
180 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
181 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
181 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
182 [255]
182 [255]
183
183
184 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
184 $ hg clone --insecure https://localhost:$HGPORT/ copy-pull
185 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
185 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
186 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
186 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
187 requesting all changes
187 requesting all changes
188 adding changesets
188 adding changesets
189 adding manifests
189 adding manifests
190 adding file changes
190 adding file changes
191 added 1 changesets with 4 changes to 4 files
191 added 1 changesets with 4 changes to 4 files
192 updating to branch default
192 updating to branch default
193 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 $ hg verify -R copy-pull
194 $ hg verify -R copy-pull
195 checking changesets
195 checking changesets
196 checking manifests
196 checking manifests
197 crosschecking files in changesets and manifests
197 crosschecking files in changesets and manifests
198 checking files
198 checking files
199 4 files, 1 changesets, 4 total revisions
199 4 files, 1 changesets, 4 total revisions
200 $ cd test
200 $ cd test
201 $ echo bar > bar
201 $ echo bar > bar
202 $ hg commit -A -d '1 0' -m 2
202 $ hg commit -A -d '1 0' -m 2
203 adding bar
203 adding bar
204 $ cd ..
204 $ cd ..
205
205
206 pull without cacert
206 pull without cacert
207
207
208 $ cd copy-pull
208 $ cd copy-pull
209 $ cat >> .hg/hgrc <<EOF
209 $ cat >> .hg/hgrc <<EOF
210 > [hooks]
210 > [hooks]
211 > changegroup = sh -c "printenv.py changegroup"
211 > changegroup = sh -c "printenv.py changegroup"
212 > EOF
212 > EOF
213 $ hg pull $DISABLECACERTS
213 $ hg pull $DISABLECACERTS
214 pulling from https://localhost:$HGPORT/
214 pulling from https://localhost:$HGPORT/
215 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
215 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
216 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
216 abort: unable to verify security of localhost (no loaded CA certificates); refusing to connect
217 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
217 (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
218 [255]
218 [255]
219
219
220 $ hg pull --insecure
220 $ hg pull --insecure
221 pulling from https://localhost:$HGPORT/
221 pulling from https://localhost:$HGPORT/
222 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
222 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
223 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
223 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
224 searching for changes
224 searching for changes
225 adding changesets
225 adding changesets
226 adding manifests
226 adding manifests
227 adding file changes
227 adding file changes
228 added 1 changesets with 1 changes to 1 files
228 added 1 changesets with 1 changes to 1 files
229 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=https://localhost:$HGPORT/
229 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=https://localhost:$HGPORT/
230 (run 'hg update' to get a working copy)
230 (run 'hg update' to get a working copy)
231 $ cd ..
231 $ cd ..
232
232
233 cacert configured in local repo
233 cacert configured in local repo
234
234
235 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
235 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
236 $ echo "[web]" >> copy-pull/.hg/hgrc
236 $ echo "[web]" >> copy-pull/.hg/hgrc
237 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
237 $ echo "cacerts=$CERTSDIR/pub.pem" >> copy-pull/.hg/hgrc
238 $ hg -R copy-pull pull
238 $ hg -R copy-pull pull
239 pulling from https://localhost:$HGPORT/
239 pulling from https://localhost:$HGPORT/
240 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
240 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
241 searching for changes
241 searching for changes
242 no changes found
242 no changes found
243 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
243 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
244
244
245 cacert configured globally, also testing expansion of environment
245 cacert configured globally, also testing expansion of environment
246 variables in the filename
246 variables in the filename
247
247
248 $ echo "[web]" >> $HGRCPATH
248 $ echo "[web]" >> $HGRCPATH
249 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
249 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
250 $ P="$CERTSDIR" hg -R copy-pull pull
250 $ P="$CERTSDIR" hg -R copy-pull pull
251 pulling from https://localhost:$HGPORT/
251 pulling from https://localhost:$HGPORT/
252 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
252 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
253 searching for changes
253 searching for changes
254 no changes found
254 no changes found
255 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
255 $ P="$CERTSDIR" hg -R copy-pull pull --insecure
256 pulling from https://localhost:$HGPORT/
256 pulling from https://localhost:$HGPORT/
257 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
257 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
258 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
258 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
259 searching for changes
259 searching for changes
260 no changes found
260 no changes found
261
261
262 empty cacert file
262 empty cacert file
263
263
264 $ touch emptycafile
264 $ touch emptycafile
265
265
266 #if sslcontext
266 #if sslcontext
267 $ hg --config web.cacerts=emptycafile -R copy-pull pull
267 $ hg --config web.cacerts=emptycafile -R copy-pull pull
268 pulling from https://localhost:$HGPORT/
268 pulling from https://localhost:$HGPORT/
269 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
269 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
270 abort: error loading CA file emptycafile: * (glob)
270 abort: error loading CA file emptycafile: * (glob)
271 (file is empty or malformed?)
271 (file is empty or malformed?)
272 [255]
272 [255]
273 #else
273 #else
274 $ hg --config web.cacerts=emptycafile -R copy-pull pull
274 $ hg --config web.cacerts=emptycafile -R copy-pull pull
275 pulling from https://localhost:$HGPORT/
275 pulling from https://localhost:$HGPORT/
276 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
276 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
277 abort: error: * (glob)
277 abort: error: * (glob)
278 [255]
278 [255]
279 #endif
279 #endif
280
280
281 cacert mismatch
281 cacert mismatch
282
282
283 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
283 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
284 > https://$LOCALIP:$HGPORT/
284 > https://$LOCALIP:$HGPORT/
285 pulling from https://*:$HGPORT/ (glob)
285 pulling from https://*:$HGPORT/ (glob)
286 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
286 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
287 abort: $LOCALIP certificate error: certificate is for localhost (glob)
287 abort: $LOCALIP certificate error: certificate is for localhost (glob)
288 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
288 (set hostsecurity.$LOCALIP:certfingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e config setting or use --insecure to connect insecurely)
289 [255]
289 [255]
290 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
290 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub.pem" \
291 > https://$LOCALIP:$HGPORT/ --insecure
291 > https://$LOCALIP:$HGPORT/ --insecure
292 pulling from https://*:$HGPORT/ (glob)
292 pulling from https://*:$HGPORT/ (glob)
293 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
293 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
294 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
294 warning: connection security to $LOCALIP is disabled per current settings; communication is susceptible to eavesdropping and tampering (glob)
295 searching for changes
295 searching for changes
296 no changes found
296 no changes found
297 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
297 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem"
298 pulling from https://localhost:$HGPORT/
298 pulling from https://localhost:$HGPORT/
299 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
299 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
300 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
300 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
301 abort: error: *certificate verify failed* (glob)
301 abort: error: *certificate verify failed* (glob)
302 [255]
302 [255]
303 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
303 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-other.pem" \
304 > --insecure
304 > --insecure
305 pulling from https://localhost:$HGPORT/
305 pulling from https://localhost:$HGPORT/
306 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
306 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
307 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
307 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
308 searching for changes
308 searching for changes
309 no changes found
309 no changes found
310
310
311 Test server cert which isn't valid yet
311 Test server cert which isn't valid yet
312
312
313 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
313 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
314 $ cat hg1.pid >> $DAEMON_PIDS
314 $ cat hg1.pid >> $DAEMON_PIDS
315 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
315 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-not-yet.pem" \
316 > https://localhost:$HGPORT1/
316 > https://localhost:$HGPORT1/
317 pulling from https://localhost:$HGPORT1/
317 pulling from https://localhost:$HGPORT1/
318 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
318 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
319 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
319 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
320 abort: error: *certificate verify failed* (glob)
320 abort: error: *certificate verify failed* (glob)
321 [255]
321 [255]
322
322
323 Test server cert which no longer is valid
323 Test server cert which no longer is valid
324
324
325 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
325 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
326 $ cat hg2.pid >> $DAEMON_PIDS
326 $ cat hg2.pid >> $DAEMON_PIDS
327 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
327 $ hg -R copy-pull pull --config web.cacerts="$CERTSDIR/pub-expired.pem" \
328 > https://localhost:$HGPORT2/
328 > https://localhost:$HGPORT2/
329 pulling from https://localhost:$HGPORT2/
329 pulling from https://localhost:$HGPORT2/
330 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
330 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
331 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
331 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
332 abort: error: *certificate verify failed* (glob)
332 abort: error: *certificate verify failed* (glob)
333 [255]
333 [255]
334
334
335 Disabling the TLS 1.0 warning works
335 Disabling the TLS 1.0 warning works
336 $ hg -R copy-pull id https://localhost:$HGPORT/ \
336 $ hg -R copy-pull id https://localhost:$HGPORT/ \
337 > --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 \
337 > --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 \
338 > --config hostsecurity.disabletls10warning=true
338 > --config hostsecurity.disabletls10warning=true
339 5fed3813f7f5
339 5fed3813f7f5
340
340
341 Error message for setting ciphers is different depending on SSLContext support
341 Error message for setting ciphers is different depending on SSLContext support
342
342
343 #if no-sslcontext
343 #if no-sslcontext
344 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
344 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
345 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
345 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
346 abort: *No cipher can be selected. (glob)
346 abort: *No cipher can be selected. (glob)
347 [255]
347 [255]
348
348
349 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
349 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
350 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
350 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info
351 5fed3813f7f5
351 5fed3813f7f5
352 #endif
352 #endif
353
353
354 #if sslcontext
354 #if sslcontext
355 Setting ciphers to an invalid value aborts
355 Setting ciphers to an invalid value aborts
356 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
356 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
357 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
357 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
358 abort: could not set ciphers: No cipher can be selected.
358 abort: could not set ciphers: No cipher can be selected.
359 (change cipher string (invalid) in config)
359 (change cipher string (invalid) in config)
360 [255]
360 [255]
361
361
362 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
362 $ P="$CERTSDIR" hg --config hostsecurity.localhost:ciphers=invalid -R copy-pull id https://localhost:$HGPORT/
363 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
363 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
364 abort: could not set ciphers: No cipher can be selected.
364 abort: could not set ciphers: No cipher can be selected.
365 (change cipher string (invalid) in config)
365 (change cipher string (invalid) in config)
366 [255]
366 [255]
367
367
368 Changing the cipher string works
368 Changing the cipher string works
369
369
370 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
370 $ P="$CERTSDIR" hg --config hostsecurity.ciphers=HIGH -R copy-pull id https://localhost:$HGPORT/
371 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
371 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
372 5fed3813f7f5
372 5fed3813f7f5
373 #endif
373 #endif
374
374
375 Fingerprints
375 Fingerprints
376
376
377 - works without cacerts (hostfingerprints)
377 - works without cacerts (hostfingerprints)
378 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
378 $ hg -R copy-pull id https://localhost:$HGPORT/ --insecure --config hostfingerprints.localhost=ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
379 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
379 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
380 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
380 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
381 5fed3813f7f5
381 5fed3813f7f5
382
382
383 - works without cacerts (hostsecurity)
383 - works without cacerts (hostsecurity)
384 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
384 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
385 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
385 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
386 5fed3813f7f5
386 5fed3813f7f5
387
387
388 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
388 $ hg -R copy-pull id https://localhost:$HGPORT/ --config hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e
389 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
389 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
390 5fed3813f7f5
390 5fed3813f7f5
391
391
392 - multiple fingerprints specified and first matches
392 - multiple fingerprints specified and first matches
393 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
393 $ hg --config 'hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
394 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
394 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
395 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
395 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
396 5fed3813f7f5
396 5fed3813f7f5
397
397
398 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
398 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03, sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
399 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
399 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
400 5fed3813f7f5
400 5fed3813f7f5
401
401
402 - multiple fingerprints specified and last matches
402 - multiple fingerprints specified and last matches
403 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
403 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/ --insecure
404 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
404 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
405 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
405 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
406 5fed3813f7f5
406 5fed3813f7f5
407
407
408 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
408 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03' -R copy-pull id https://localhost:$HGPORT/
409 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
409 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
410 5fed3813f7f5
410 5fed3813f7f5
411
411
412 - multiple fingerprints specified and none match
412 - multiple fingerprints specified and none match
413
413
414 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
414 $ hg --config 'hostfingerprints.localhost=deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/ --insecure
415 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
415 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
416 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
416 abort: certificate for localhost has unexpected fingerprint ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
417 (check hostfingerprint configuration)
417 (check hostfingerprint configuration)
418 [255]
418 [255]
419
419
420 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
420 $ hg --config 'hostsecurity.localhost:fingerprints=sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef, sha1:aeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' -R copy-pull id https://localhost:$HGPORT/
421 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
421 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
422 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
422 abort: certificate for localhost has unexpected fingerprint sha1:ec:d8:7c:d6:b3:86:d0:4f:c1:b8:b4:1c:9d:8f:5e:16:8e:ef:1c:03
423 (check hostsecurity configuration)
423 (check hostsecurity configuration)
424 [255]
424 [255]
425
425
426 - fails when cert doesn't match hostname (port is ignored)
426 - fails when cert doesn't match hostname (port is ignored)
427 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
427 $ hg -R copy-pull id https://localhost:$HGPORT1/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
428 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
428 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
429 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
429 abort: certificate for localhost has unexpected fingerprint f4:2f:5a:0c:3e:52:5b:db:e7:24:a8:32:1d:18:97:6d:69:b5:87:84
430 (check hostfingerprint configuration)
430 (check hostfingerprint configuration)
431 [255]
431 [255]
432
432
433
433
434 - ignores that certificate doesn't match hostname
434 - ignores that certificate doesn't match hostname
435 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
435 $ hg -R copy-pull id https://$LOCALIP:$HGPORT/ --config hostfingerprints.$LOCALIP=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03
436 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
436 warning: connecting to $LOCALIP using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
437 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: $LOCALIP:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
437 (SHA-1 fingerprint for $LOCALIP found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: $LOCALIP:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
438 5fed3813f7f5
438 5fed3813f7f5
439
439
440 Ports used by next test. Kill servers.
440 Ports used by next test. Kill servers.
441
441
442 $ killdaemons.py hg0.pid
442 $ killdaemons.py hg0.pid
443 $ killdaemons.py hg1.pid
443 $ killdaemons.py hg1.pid
444 $ killdaemons.py hg2.pid
444 $ killdaemons.py hg2.pid
445
445
446 #if sslcontext tls1.2
446 #if sslcontext tls1.2
447 Start servers running supported TLS versions
447 Start servers running supported TLS versions
448
448
449 $ cd test
449 $ cd test
450 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
450 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
451 > --config devel.serverexactprotocol=tls1.0
451 > --config devel.serverexactprotocol=tls1.0
452 $ cat ../hg0.pid >> $DAEMON_PIDS
452 $ cat ../hg0.pid >> $DAEMON_PIDS
453 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
453 $ hg serve -p $HGPORT1 -d --pid-file=../hg1.pid --certificate=$PRIV \
454 > --config devel.serverexactprotocol=tls1.1
454 > --config devel.serverexactprotocol=tls1.1
455 $ cat ../hg1.pid >> $DAEMON_PIDS
455 $ cat ../hg1.pid >> $DAEMON_PIDS
456 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
456 $ hg serve -p $HGPORT2 -d --pid-file=../hg2.pid --certificate=$PRIV \
457 > --config devel.serverexactprotocol=tls1.2
457 > --config devel.serverexactprotocol=tls1.2
458 $ cat ../hg2.pid >> $DAEMON_PIDS
458 $ cat ../hg2.pid >> $DAEMON_PIDS
459 $ cd ..
459 $ cd ..
460
460
461 Clients talking same TLS versions work
461 Clients talking same TLS versions work
462
462
463 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
463 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.0 id https://localhost:$HGPORT/
464 5fed3813f7f5
464 5fed3813f7f5
465 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
465 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT1/
466 5fed3813f7f5
466 5fed3813f7f5
467 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
467 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT2/
468 5fed3813f7f5
468 5fed3813f7f5
469
469
470 Clients requiring newer TLS version than what server supports fail
470 Clients requiring newer TLS version than what server supports fail
471
471
472 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
472 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
473 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
473 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
474 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
474 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
475 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
475 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
476 abort: error: *unsupported protocol* (glob)
476 abort: error: *unsupported protocol* (glob)
477 [255]
477 [255]
478
478
479 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
479 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.1 id https://localhost:$HGPORT/
480 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
480 (could not negotiate a common security protocol (tls1.1+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
481 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
481 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
482 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
482 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
483 abort: error: *unsupported protocol* (glob)
483 abort: error: *unsupported protocol* (glob)
484 [255]
484 [255]
485 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
485 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT/
486 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
486 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
487 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
487 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
488 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
488 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
489 abort: error: *unsupported protocol* (glob)
489 abort: error: *unsupported protocol* (glob)
490 [255]
490 [255]
491 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
491 $ P="$CERTSDIR" hg --config hostsecurity.minimumprotocol=tls1.2 id https://localhost:$HGPORT1/
492 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
492 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
493 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
493 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
494 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
494 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
495 abort: error: *unsupported protocol* (glob)
495 abort: error: *unsupported protocol* (glob)
496 [255]
496 [255]
497
497
498 --insecure will allow TLS 1.0 connections and override configs
498 --insecure will allow TLS 1.0 connections and override configs
499
499
500 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
500 $ hg --config hostsecurity.minimumprotocol=tls1.2 id --insecure https://localhost:$HGPORT1/
501 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
501 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
502 5fed3813f7f5
502 5fed3813f7f5
503
503
504 The per-host config option overrides the default
504 The per-host config option overrides the default
505
505
506 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
506 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
507 > --config hostsecurity.minimumprotocol=tls1.2 \
507 > --config hostsecurity.minimumprotocol=tls1.2 \
508 > --config hostsecurity.localhost:minimumprotocol=tls1.0
508 > --config hostsecurity.localhost:minimumprotocol=tls1.0
509 5fed3813f7f5
509 5fed3813f7f5
510
510
511 The per-host config option by itself works
511 The per-host config option by itself works
512
512
513 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
513 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
514 > --config hostsecurity.localhost:minimumprotocol=tls1.2
514 > --config hostsecurity.localhost:minimumprotocol=tls1.2
515 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
515 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
516 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
516 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
517 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
517 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
518 abort: error: *unsupported protocol* (glob)
518 abort: error: *unsupported protocol* (glob)
519 [255]
519 [255]
520
520
521 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
521 .hg/hgrc file [hostsecurity] settings are applied to remote ui instances (issue5305)
522
522
523 $ cat >> copy-pull/.hg/hgrc << EOF
523 $ cat >> copy-pull/.hg/hgrc << EOF
524 > [hostsecurity]
524 > [hostsecurity]
525 > localhost:minimumprotocol=tls1.2
525 > localhost:minimumprotocol=tls1.2
526 > EOF
526 > EOF
527 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
527 $ P="$CERTSDIR" hg -R copy-pull id https://localhost:$HGPORT/
528 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
528 (could not negotiate a common security protocol (tls1.2+) with localhost; the likely cause is Mercurial is configured to be more secure than the server can support)
529 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
529 (consider contacting the operator of this server and ask them to support modern TLS protocol versions; or, set hostsecurity.localhost:minimumprotocol=tls1.0 to allow use of legacy, less secure protocols when communicating with this server)
530 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
530 (see https://mercurial-scm.org/wiki/SecureConnections for more info)
531 abort: error: *unsupported protocol* (glob)
531 abort: error: *unsupported protocol* (glob)
532 [255]
532 [255]
533
533
534 $ killdaemons.py hg0.pid
534 $ killdaemons.py hg0.pid
535 $ killdaemons.py hg1.pid
535 $ killdaemons.py hg1.pid
536 $ killdaemons.py hg2.pid
536 $ killdaemons.py hg2.pid
537 #endif
537 #endif
538
538
539 Prepare for connecting through proxy
539 Prepare for connecting through proxy
540
540
541 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
541 $ hg serve -R test -p $HGPORT -d --pid-file=hg0.pid --certificate=$PRIV
542 $ cat hg0.pid >> $DAEMON_PIDS
542 $ cat hg0.pid >> $DAEMON_PIDS
543 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
543 $ hg serve -R test -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
544 $ cat hg2.pid >> $DAEMON_PIDS
544 $ cat hg2.pid >> $DAEMON_PIDS
545 tinyproxy.py doesn't fully detach, so killing it may result in extra output
545 tinyproxy.py doesn't fully detach, so killing it may result in extra output
546 from the shell. So don't kill it.
546 from the shell. So don't kill it.
547 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
547 $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
548 $ while [ ! -f proxy.pid ]; do sleep 0; done
548 $ while [ ! -f proxy.pid ]; do sleep 0; done
549 $ cat proxy.pid >> $DAEMON_PIDS
549 $ cat proxy.pid >> $DAEMON_PIDS
550
550
551 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
551 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
552 $ echo "always=True" >> copy-pull/.hg/hgrc
552 $ echo "always=True" >> copy-pull/.hg/hgrc
553 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
553 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
554 $ echo "localhost =" >> copy-pull/.hg/hgrc
554 $ echo "localhost =" >> copy-pull/.hg/hgrc
555
555
556 Test unvalidated https through proxy
556 Test unvalidated https through proxy
557
557
558 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
558 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure
559 pulling from https://localhost:$HGPORT/
559 pulling from https://localhost:$HGPORT/
560 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
560 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
561 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
561 warning: connection security to localhost is disabled per current settings; communication is susceptible to eavesdropping and tampering
562 searching for changes
562 searching for changes
563 no changes found
563 no changes found
564
564
565 Test https with cacert and fingerprint through proxy
565 Test https with cacert and fingerprint through proxy
566
566
567 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
567 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
568 > --config web.cacerts="$CERTSDIR/pub.pem"
568 > --config web.cacerts="$CERTSDIR/pub.pem"
569 pulling from https://localhost:$HGPORT/
569 pulling from https://localhost:$HGPORT/
570 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
570 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
571 searching for changes
571 searching for changes
572 no changes found
572 no changes found
573 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
573 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://localhost:$HGPORT/ --config hostfingerprints.localhost=ecd87cd6b386d04fc1b8b41c9d8f5e168eef1c03 --trace
574 pulling from https://*:$HGPORT/ (glob)
574 pulling from https://*:$HGPORT/ (glob)
575 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
575 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
576 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
576 (SHA-1 fingerprint for localhost found in legacy [hostfingerprints] section; if you trust this fingerprint, remove the old SHA-1 fingerprint from [hostfingerprints] and add the following entry to the new [hostsecurity] section: localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e)
577 searching for changes
577 searching for changes
578 no changes found
578 no changes found
579
579
580 Test https with cert problems through proxy
580 Test https with cert problems through proxy
581
581
582 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
582 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
583 > --config web.cacerts="$CERTSDIR/pub-other.pem"
583 > --config web.cacerts="$CERTSDIR/pub-other.pem"
584 pulling from https://localhost:$HGPORT/
584 pulling from https://localhost:$HGPORT/
585 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
585 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
586 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
586 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
587 abort: error: *certificate verify failed* (glob)
587 abort: error: *certificate verify failed* (glob)
588 [255]
588 [255]
589 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
589 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull \
590 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
590 > --config web.cacerts="$CERTSDIR/pub-expired.pem" https://localhost:$HGPORT2/
591 pulling from https://localhost:$HGPORT2/
591 pulling from https://localhost:$HGPORT2/
592 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
592 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
593 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
593 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
594 abort: error: *certificate verify failed* (glob)
594 abort: error: *certificate verify failed* (glob)
595 [255]
595 [255]
596
596
597
597
598 $ killdaemons.py hg0.pid
598 $ killdaemons.py hg0.pid
599
599
600 #if sslcontext
600 #if sslcontext
601
601
602 $ cd test
602 $ cd test
603
603
604 Missing certificate file(s) are detected
604 Missing certificate file(s) are detected
605
605
606 $ hg serve -p $HGPORT --certificate=/missing/certificate \
606 $ hg serve -p $HGPORT --certificate=/missing/certificate \
607 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
607 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
608 abort: referenced certificate file (*/missing/certificate) does not exist (glob)
608 abort: referenced certificate file (*/missing/certificate) does not exist (glob)
609 [255]
609 [255]
610
610
611 $ hg serve -p $HGPORT --certificate=$PRIV \
611 $ hg serve -p $HGPORT --certificate=$PRIV \
612 > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
612 > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
613 abort: referenced certificate file (*/missing/cafile) does not exist (glob)
613 abort: referenced certificate file (*/missing/cafile) does not exist (glob)
614 [255]
614 [255]
615
615
616 Start hgweb that requires client certificates:
616 Start hgweb that requires client certificates:
617
617
618 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
618 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
619 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
619 > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
620 $ cat ../hg0.pid >> $DAEMON_PIDS
620 $ cat ../hg0.pid >> $DAEMON_PIDS
621 $ cd ..
621 $ cd ..
622
622
623 without client certificate:
623 without client certificate:
624
624
625 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
625 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
626 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
626 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
627 (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
628 abort: error: *handshake failure* (glob)
627 abort: error: *handshake failure* (glob)
629 [255]
628 [255]
630
629
631 with client certificate:
630 with client certificate:
632
631
633 $ cat << EOT >> $HGRCPATH
632 $ cat << EOT >> $HGRCPATH
634 > [auth]
633 > [auth]
635 > l.prefix = localhost
634 > l.prefix = localhost
636 > l.cert = $CERTSDIR/client-cert.pem
635 > l.cert = $CERTSDIR/client-cert.pem
637 > l.key = $CERTSDIR/client-key.pem
636 > l.key = $CERTSDIR/client-key.pem
638 > EOT
637 > EOT
639
638
640 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
639 $ P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
641 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
640 > --config auth.l.key="$CERTSDIR/client-key-decrypted.pem"
642 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
641 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
643 5fed3813f7f5
642 5fed3813f7f5
644
643
645 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
644 $ printf '1234\n' | env P="$CERTSDIR" hg id https://localhost:$HGPORT/ \
646 > --config ui.interactive=True --config ui.nontty=True
645 > --config ui.interactive=True --config ui.nontty=True
647 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
646 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
648 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
647 passphrase for */client-key.pem: 5fed3813f7f5 (glob)
649
648
650 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
649 $ env P="$CERTSDIR" hg id https://localhost:$HGPORT/
651 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
650 warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
652 abort: error: * (glob)
651 abort: error: * (glob)
653 [255]
652 [255]
654
653
655 Missing certficate and key files result in error
654 Missing certficate and key files result in error
656
655
657 $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
656 $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
658 abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
657 abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
659 (restore missing file or fix references in Mercurial config)
658 (restore missing file or fix references in Mercurial config)
660 [255]
659 [255]
661
660
662 $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
661 $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
663 abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
662 abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
664 (restore missing file or fix references in Mercurial config)
663 (restore missing file or fix references in Mercurial config)
665 [255]
664 [255]
666
665
667 #endif
666 #endif
@@ -1,99 +1,100 b''
1 """test line matching with some failing examples and some which warn
1 """test line matching with some failing examples and some which warn
2
2
3 run-test.t only checks positive matches and can not see warnings
3 run-test.t only checks positive matches and can not see warnings
4 (both by design)
4 (both by design)
5 """
5 """
6 from __future__ import absolute_import, print_function
6 from __future__ import absolute_import, print_function
7
7
8 import doctest
8 import doctest
9 import os
9 import os
10 import re
10 import re
11 # this is hack to make sure no escape characters are inserted into the output
11 # this is hack to make sure no escape characters are inserted into the output
12 if 'TERM' in os.environ:
12 if 'TERM' in os.environ:
13 del os.environ['TERM']
13 del os.environ['TERM']
14 run_tests = __import__('run-tests')
14 run_tests = __import__('run-tests')
15
15
16 def prn(ex):
16 def prn(ex):
17 m = ex.args[0]
17 m = ex.args[0]
18 if isinstance(m, str):
18 if isinstance(m, str):
19 print(m)
19 print(m)
20 else:
20 else:
21 print(m.decode('utf-8'))
21 print(m.decode('utf-8'))
22
22
23 def lm(expected, output):
23 def lm(expected, output):
24 r"""check if output matches expected
24 r"""check if output matches expected
25
25
26 does it generally work?
26 does it generally work?
27 >>> lm(b'H*e (glob)\n', b'Here\n')
27 >>> lm(b'H*e (glob)\n', b'Here\n')
28 True
28 True
29
29
30 fail on bad test data
30 fail on bad test data
31 >>> try: lm(b'a\n',b'a')
31 >>> try: lm(b'a\n',b'a')
32 ... except AssertionError as ex: print(ex)
32 ... except AssertionError as ex: print(ex)
33 missing newline
33 missing newline
34 >>> try: lm(b'single backslash\n', b'single \backslash\n')
34 >>> try: lm(b'single backslash\n', b'single \backslash\n')
35 ... except AssertionError as ex: prn(ex)
35 ... except AssertionError as ex: prn(ex)
36 single backslash or unknown char
36 single backslash or unknown char
37 """
37 """
38 assert (expected.endswith(b'\n')
38 assert (expected.endswith(b'\n')
39 and output.endswith(b'\n')), 'missing newline'
39 and output.endswith(b'\n')), 'missing newline'
40 assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
40 assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
41 b'single backslash or unknown char'
41 b'single backslash or unknown char'
42 match = run_tests.TTest.linematch(expected, output)
42 test = run_tests.TTest('test-run-test.t', '.', '.')
43 match = test.linematch(expected, output)
43 if isinstance(match, str):
44 if isinstance(match, str):
44 return 'special: ' + match
45 return 'special: ' + match
45 elif isinstance(match, bytes):
46 elif isinstance(match, bytes):
46 return 'special: ' + match.decode('utf-8')
47 return 'special: ' + match.decode('utf-8')
47 else:
48 else:
48 return bool(match) # do not return match object
49 return bool(match) # do not return match object
49
50
50 def wintests():
51 def wintests():
51 r"""test matching like running on windows
52 r"""test matching like running on windows
52
53
53 enable windows matching on any os
54 enable windows matching on any os
54 >>> _osaltsep = os.altsep
55 >>> _osaltsep = os.altsep
55 >>> os.altsep = True
56 >>> os.altsep = True
56
57
57 valid match on windows
58 valid match on windows
58 >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n')
59 >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n')
59 True
60 True
60
61
61 direct matching, glob unnecessary
62 direct matching, glob unnecessary
62 >>> lm(b'g/b (glob)\n', b'g/b\n')
63 >>> lm(b'g/b (glob)\n', b'g/b\n')
63 'special: -glob'
64 'special: -glob'
64
65
65 missing glob
66 missing glob
66 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n')
67 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n')
67 'special: +glob'
68 'special: +glob'
68
69
69 restore os.altsep
70 restore os.altsep
70 >>> os.altsep = _osaltsep
71 >>> os.altsep = _osaltsep
71 """
72 """
72 pass
73 pass
73
74
74 def otherostests():
75 def otherostests():
75 r"""test matching like running on non-windows os
76 r"""test matching like running on non-windows os
76
77
77 disable windows matching on any os
78 disable windows matching on any os
78 >>> _osaltsep = os.altsep
79 >>> _osaltsep = os.altsep
79 >>> os.altsep = False
80 >>> os.altsep = False
80
81
81 backslash does not match slash
82 backslash does not match slash
82 >>> lm(b'h/a* (glob)\n', b'h\\ab\n')
83 >>> lm(b'h/a* (glob)\n', b'h\\ab\n')
83 False
84 False
84
85
85 direct matching glob can not be recognized
86 direct matching glob can not be recognized
86 >>> lm(b'h/b (glob)\n', b'h/b\n')
87 >>> lm(b'h/b (glob)\n', b'h/b\n')
87 True
88 True
88
89
89 missing glob can not not be recognized
90 missing glob can not not be recognized
90 >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n')
91 >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n')
91 False
92 False
92
93
93 restore os.altsep
94 restore os.altsep
94 >>> os.altsep = _osaltsep
95 >>> os.altsep = _osaltsep
95 """
96 """
96 pass
97 pass
97
98
98 if __name__ == '__main__':
99 if __name__ == '__main__':
99 doctest.testmod()
100 doctest.testmod()
@@ -1,1328 +1,1379 b''
1 This file tests the behavior of run-tests.py itself.
1 This file tests the behavior of run-tests.py itself.
2
2
3 Avoid interference from actual test env:
3 Avoid interference from actual test env:
4
4
5 $ . "$TESTDIR/helper-runtests.sh"
5 $ . "$TESTDIR/helper-runtests.sh"
6
6
7 Smoke test with install
7 Smoke test with install
8 ============
8 ============
9
9
10 $ run-tests.py $HGTEST_RUN_TESTS_PURE -l
10 $ run-tests.py $HGTEST_RUN_TESTS_PURE -l
11
11
12 # Ran 0 tests, 0 skipped, 0 failed.
12 # Ran 0 tests, 0 skipped, 0 failed.
13
13
14 Define a helper to avoid the install step
14 Define a helper to avoid the install step
15 =============
15 =============
16 $ rt()
16 $ rt()
17 > {
17 > {
18 > run-tests.py --with-hg=`which hg` "$@"
18 > run-tests.py --with-hg=`which hg` "$@"
19 > }
19 > }
20
20
21 error paths
21 error paths
22
22
23 #if symlink
23 #if symlink
24 $ ln -s `which true` hg
24 $ ln -s `which true` hg
25 $ run-tests.py --with-hg=./hg
25 $ run-tests.py --with-hg=./hg
26 warning: --with-hg should specify an hg script
26 warning: --with-hg should specify an hg script
27
27
28 # Ran 0 tests, 0 skipped, 0 failed.
28 # Ran 0 tests, 0 skipped, 0 failed.
29 $ rm hg
29 $ rm hg
30 #endif
30 #endif
31
31
32 #if execbit
32 #if execbit
33 $ touch hg
33 $ touch hg
34 $ run-tests.py --with-hg=./hg
34 $ run-tests.py --with-hg=./hg
35 Usage: run-tests.py [options] [tests]
35 Usage: run-tests.py [options] [tests]
36
36
37 run-tests.py: error: --with-hg must specify an executable hg script
37 run-tests.py: error: --with-hg must specify an executable hg script
38 [2]
38 [2]
39 $ rm hg
39 $ rm hg
40 #endif
40 #endif
41
41
42 Features for testing optional lines
42 Features for testing optional lines
43 ===================================
43 ===================================
44
44
45 $ cat > hghaveaddon.py <<EOF
45 $ cat > hghaveaddon.py <<EOF
46 > import hghave
46 > import hghave
47 > @hghave.check("custom", "custom hghave feature")
47 > @hghave.check("custom", "custom hghave feature")
48 > def has_custom():
48 > def has_custom():
49 > return True
49 > return True
50 > @hghave.check("missing", "missing hghave feature")
50 > @hghave.check("missing", "missing hghave feature")
51 > def has_missing():
51 > def has_missing():
52 > return False
52 > return False
53 > EOF
53 > EOF
54
54
55 an empty test
55 an empty test
56 =======================
56 =======================
57
57
58 $ touch test-empty.t
58 $ touch test-empty.t
59 $ rt
59 $ rt
60 .
60 .
61 # Ran 1 tests, 0 skipped, 0 failed.
61 # Ran 1 tests, 0 skipped, 0 failed.
62 $ rm test-empty.t
62 $ rm test-empty.t
63
63
64 a succesful test
64 a succesful test
65 =======================
65 =======================
66
66
67 $ cat > test-success.t << EOF
67 $ cat > test-success.t << EOF
68 > $ echo babar
68 > $ echo babar
69 > babar
69 > babar
70 > $ echo xyzzy
70 > $ echo xyzzy
71 > dont_print (?)
71 > dont_print (?)
72 > nothing[42]line (re) (?)
72 > nothing[42]line (re) (?)
73 > never*happens (glob) (?)
73 > never*happens (glob) (?)
74 > more_nothing (?)
74 > more_nothing (?)
75 > xyzzy
75 > xyzzy
76 > nor this (?)
76 > nor this (?)
77 > $ printf 'abc\ndef\nxyz\n'
77 > $ printf 'abc\ndef\nxyz\n'
78 > 123 (?)
78 > 123 (?)
79 > abc
79 > abc
80 > def (?)
80 > def (?)
81 > 456 (?)
81 > 456 (?)
82 > xyz
82 > xyz
83 > $ printf 'zyx\nwvu\ntsr\n'
83 > $ printf 'zyx\nwvu\ntsr\n'
84 > abc (?)
84 > abc (?)
85 > zyx (custom !)
85 > zyx (custom !)
86 > wvu
86 > wvu
87 > no_print (no-custom !)
87 > no_print (no-custom !)
88 > tsr (no-missing !)
88 > tsr (no-missing !)
89 > missing (missing !)
89 > missing (missing !)
90 > EOF
90 > EOF
91
91
92 $ rt
92 $ rt
93 .
93 .
94 # Ran 1 tests, 0 skipped, 0 failed.
94 # Ran 1 tests, 0 skipped, 0 failed.
95
95
96 failing test
96 failing test
97 ==================
97 ==================
98
98
99 test churn with globs
99 test churn with globs
100 $ cat > test-failure.t <<EOF
100 $ cat > test-failure.t <<EOF
101 > $ echo "bar-baz"; echo "bar-bad"
101 > $ echo "bar-baz"; echo "bar-bad"
102 > bar*bad (glob)
102 > bar*bad (glob)
103 > bar*baz (glob)
103 > bar*baz (glob)
104 > EOF
104 > EOF
105 $ rt test-failure.t
105 $ rt test-failure.t
106
106
107 --- $TESTTMP/test-failure.t
107 --- $TESTTMP/test-failure.t
108 +++ $TESTTMP/test-failure.t.err
108 +++ $TESTTMP/test-failure.t.err
109 @@ -1,3 +1,3 @@
109 @@ -1,3 +1,3 @@
110 $ echo "bar-baz"; echo "bar-bad"
110 $ echo "bar-baz"; echo "bar-bad"
111 + bar*baz (glob)
111 + bar*baz (glob)
112 bar*bad (glob)
112 bar*bad (glob)
113 - bar*baz (glob)
113 - bar*baz (glob)
114
114
115 ERROR: test-failure.t output changed
115 ERROR: test-failure.t output changed
116 !
116 !
117 Failed test-failure.t: output changed
117 Failed test-failure.t: output changed
118 # Ran 1 tests, 0 skipped, 1 failed.
118 # Ran 1 tests, 0 skipped, 1 failed.
119 python hash seed: * (glob)
119 python hash seed: * (glob)
120 [1]
120 [1]
121
121
122 test diff colorisation
122 test diff colorisation
123
123
124 #if no-windows pygments
124 #if no-windows pygments
125 $ rt test-failure.t --color always
125 $ rt test-failure.t --color always
126
126
127 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
127 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
128 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
128 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
129 \x1b[38;5;90;01m@@ -1,3 +1,3 @@\x1b[39;00m (esc)
129 \x1b[38;5;90;01m@@ -1,3 +1,3 @@\x1b[39;00m (esc)
130 $ echo "bar-baz"; echo "bar-bad"
130 $ echo "bar-baz"; echo "bar-bad"
131 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
131 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
132 bar*bad (glob)
132 bar*bad (glob)
133 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
133 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
134
134
135 ERROR: test-failure.t output changed
135 ERROR: test-failure.t output changed
136 !
136 !
137 Failed test-failure.t: output changed
137 Failed test-failure.t: output changed
138 # Ran 1 tests, 0 skipped, 1 failed.
138 # Ran 1 tests, 0 skipped, 1 failed.
139 python hash seed: * (glob)
139 python hash seed: * (glob)
140 [1]
140 [1]
141
141
142 $ rt test-failure.t 2> tmp.log
142 $ rt test-failure.t 2> tmp.log
143 [1]
143 [1]
144 $ cat tmp.log
144 $ cat tmp.log
145
145
146 --- $TESTTMP/test-failure.t
146 --- $TESTTMP/test-failure.t
147 +++ $TESTTMP/test-failure.t.err
147 +++ $TESTTMP/test-failure.t.err
148 @@ -1,3 +1,3 @@
148 @@ -1,3 +1,3 @@
149 $ echo "bar-baz"; echo "bar-bad"
149 $ echo "bar-baz"; echo "bar-bad"
150 + bar*baz (glob)
150 + bar*baz (glob)
151 bar*bad (glob)
151 bar*bad (glob)
152 - bar*baz (glob)
152 - bar*baz (glob)
153
153
154 ERROR: test-failure.t output changed
154 ERROR: test-failure.t output changed
155 !
155 !
156 Failed test-failure.t: output changed
156 Failed test-failure.t: output changed
157 # Ran 1 tests, 0 skipped, 1 failed.
157 # Ran 1 tests, 0 skipped, 1 failed.
158 python hash seed: * (glob)
158 python hash seed: * (glob)
159 #endif
159 #endif
160
160
161 $ cat > test-failure.t << EOF
162 > $ true
163 > should go away (true !)
164 > $ true
165 > should stay (false !)
166 >
167 > Should remove first line, not second or third
168 > $ echo 'testing'
169 > baz*foo (glob) (true !)
170 > foobar*foo (glob) (false !)
171 > te*ting (glob) (true !)
172 >
173 > Should keep first two lines, remove third and last
174 > $ echo 'testing'
175 > test.ng (re) (true !)
176 > foo.ar (re) (false !)
177 > b.r (re) (true !)
178 > missing (?)
179 > awol (true !)
180 > EOF
181 $ rt test-failure.t
182
183 --- $TESTTMP/test-failure.t
184 +++ $TESTTMP/test-failure.t.err
185 @@ -1,11 +1,9 @@
186 $ true
187 - should go away (true !)
188 $ true
189 should stay (false !)
190
191 Should remove first line, not second or third
192 $ echo 'testing'
193 - baz*foo (glob) (true !)
194 foobar*foo (glob) (false !)
195 te*ting (glob) (true !)
196
197 @@ -13,6 +11,4 @@
198 $ echo 'testing'
199 test.ng (re) (true !)
200 foo.ar (re) (false !)
201 - b.r (re) (true !)
202 missing (?)
203 - awol (true !)
204
205 ERROR: test-failure.t output changed
206 !
207 Failed test-failure.t: output changed
208 # Ran 1 tests, 0 skipped, 1 failed.
209 python hash seed: * (glob)
210 [1]
211
161 basic failing test
212 basic failing test
162 $ cat > test-failure.t << EOF
213 $ cat > test-failure.t << EOF
163 > $ echo babar
214 > $ echo babar
164 > rataxes
215 > rataxes
165 > This is a noop statement so that
216 > This is a noop statement so that
166 > this test is still more bytes than success.
217 > this test is still more bytes than success.
167 > pad pad pad pad............................................................
218 > pad pad pad pad............................................................
168 > pad pad pad pad............................................................
219 > pad pad pad pad............................................................
169 > pad pad pad pad............................................................
220 > pad pad pad pad............................................................
170 > pad pad pad pad............................................................
221 > pad pad pad pad............................................................
171 > pad pad pad pad............................................................
222 > pad pad pad pad............................................................
172 > pad pad pad pad............................................................
223 > pad pad pad pad............................................................
173 > EOF
224 > EOF
174
225
175 >>> fh = open('test-failure-unicode.t', 'wb')
226 >>> fh = open('test-failure-unicode.t', 'wb')
176 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
227 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
177 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
228 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
178
229
179 $ rt
230 $ rt
180
231
181 --- $TESTTMP/test-failure.t
232 --- $TESTTMP/test-failure.t
182 +++ $TESTTMP/test-failure.t.err
233 +++ $TESTTMP/test-failure.t.err
183 @@ -1,5 +1,5 @@
234 @@ -1,5 +1,5 @@
184 $ echo babar
235 $ echo babar
185 - rataxes
236 - rataxes
186 + babar
237 + babar
187 This is a noop statement so that
238 This is a noop statement so that
188 this test is still more bytes than success.
239 this test is still more bytes than success.
189 pad pad pad pad............................................................
240 pad pad pad pad............................................................
190
241
191 ERROR: test-failure.t output changed
242 ERROR: test-failure.t output changed
192 !.
243 !.
193 --- $TESTTMP/test-failure-unicode.t
244 --- $TESTTMP/test-failure-unicode.t
194 +++ $TESTTMP/test-failure-unicode.t.err
245 +++ $TESTTMP/test-failure-unicode.t.err
195 @@ -1,2 +1,2 @@
246 @@ -1,2 +1,2 @@
196 $ echo babar\xce\xb1 (esc)
247 $ echo babar\xce\xb1 (esc)
197 - l\xce\xb5\xce\xb5t (esc)
248 - l\xce\xb5\xce\xb5t (esc)
198 + babar\xce\xb1 (esc)
249 + babar\xce\xb1 (esc)
199
250
200 ERROR: test-failure-unicode.t output changed
251 ERROR: test-failure-unicode.t output changed
201 !
252 !
202 Failed test-failure.t: output changed
253 Failed test-failure.t: output changed
203 Failed test-failure-unicode.t: output changed
254 Failed test-failure-unicode.t: output changed
204 # Ran 3 tests, 0 skipped, 2 failed.
255 # Ran 3 tests, 0 skipped, 2 failed.
205 python hash seed: * (glob)
256 python hash seed: * (glob)
206 [1]
257 [1]
207
258
208 test --outputdir
259 test --outputdir
209 $ mkdir output
260 $ mkdir output
210 $ rt --outputdir output
261 $ rt --outputdir output
211
262
212 --- $TESTTMP/test-failure.t
263 --- $TESTTMP/test-failure.t
213 +++ $TESTTMP/output/test-failure.t.err
264 +++ $TESTTMP/output/test-failure.t.err
214 @@ -1,5 +1,5 @@
265 @@ -1,5 +1,5 @@
215 $ echo babar
266 $ echo babar
216 - rataxes
267 - rataxes
217 + babar
268 + babar
218 This is a noop statement so that
269 This is a noop statement so that
219 this test is still more bytes than success.
270 this test is still more bytes than success.
220 pad pad pad pad............................................................
271 pad pad pad pad............................................................
221
272
222 ERROR: test-failure.t output changed
273 ERROR: test-failure.t output changed
223 !.
274 !.
224 --- $TESTTMP/test-failure-unicode.t
275 --- $TESTTMP/test-failure-unicode.t
225 +++ $TESTTMP/output/test-failure-unicode.t.err
276 +++ $TESTTMP/output/test-failure-unicode.t.err
226 @@ -1,2 +1,2 @@
277 @@ -1,2 +1,2 @@
227 $ echo babar\xce\xb1 (esc)
278 $ echo babar\xce\xb1 (esc)
228 - l\xce\xb5\xce\xb5t (esc)
279 - l\xce\xb5\xce\xb5t (esc)
229 + babar\xce\xb1 (esc)
280 + babar\xce\xb1 (esc)
230
281
231 ERROR: test-failure-unicode.t output changed
282 ERROR: test-failure-unicode.t output changed
232 !
283 !
233 Failed test-failure.t: output changed
284 Failed test-failure.t: output changed
234 Failed test-failure-unicode.t: output changed
285 Failed test-failure-unicode.t: output changed
235 # Ran 3 tests, 0 skipped, 2 failed.
286 # Ran 3 tests, 0 skipped, 2 failed.
236 python hash seed: * (glob)
287 python hash seed: * (glob)
237 [1]
288 [1]
238 $ ls -a output
289 $ ls -a output
239 .
290 .
240 ..
291 ..
241 .testtimes
292 .testtimes
242 test-failure-unicode.t.err
293 test-failure-unicode.t.err
243 test-failure.t.err
294 test-failure.t.err
244
295
245 test --xunit support
296 test --xunit support
246 $ rt --xunit=xunit.xml
297 $ rt --xunit=xunit.xml
247
298
248 --- $TESTTMP/test-failure.t
299 --- $TESTTMP/test-failure.t
249 +++ $TESTTMP/test-failure.t.err
300 +++ $TESTTMP/test-failure.t.err
250 @@ -1,5 +1,5 @@
301 @@ -1,5 +1,5 @@
251 $ echo babar
302 $ echo babar
252 - rataxes
303 - rataxes
253 + babar
304 + babar
254 This is a noop statement so that
305 This is a noop statement so that
255 this test is still more bytes than success.
306 this test is still more bytes than success.
256 pad pad pad pad............................................................
307 pad pad pad pad............................................................
257
308
258 ERROR: test-failure.t output changed
309 ERROR: test-failure.t output changed
259 !.
310 !.
260 --- $TESTTMP/test-failure-unicode.t
311 --- $TESTTMP/test-failure-unicode.t
261 +++ $TESTTMP/test-failure-unicode.t.err
312 +++ $TESTTMP/test-failure-unicode.t.err
262 @@ -1,2 +1,2 @@
313 @@ -1,2 +1,2 @@
263 $ echo babar\xce\xb1 (esc)
314 $ echo babar\xce\xb1 (esc)
264 - l\xce\xb5\xce\xb5t (esc)
315 - l\xce\xb5\xce\xb5t (esc)
265 + babar\xce\xb1 (esc)
316 + babar\xce\xb1 (esc)
266
317
267 ERROR: test-failure-unicode.t output changed
318 ERROR: test-failure-unicode.t output changed
268 !
319 !
269 Failed test-failure.t: output changed
320 Failed test-failure.t: output changed
270 Failed test-failure-unicode.t: output changed
321 Failed test-failure-unicode.t: output changed
271 # Ran 3 tests, 0 skipped, 2 failed.
322 # Ran 3 tests, 0 skipped, 2 failed.
272 python hash seed: * (glob)
323 python hash seed: * (glob)
273 [1]
324 [1]
274 $ cat xunit.xml
325 $ cat xunit.xml
275 <?xml version="1.0" encoding="utf-8"?>
326 <?xml version="1.0" encoding="utf-8"?>
276 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
327 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
277 <testcase name="test-success.t" time="*"/> (glob)
328 <testcase name="test-success.t" time="*"/> (glob)
278 <testcase name="test-failure-unicode.t" time="*"> (glob)
329 <testcase name="test-failure-unicode.t" time="*"> (glob)
279 <failure message="output changed" type="output-mismatch">
330 <failure message="output changed" type="output-mismatch">
280 <![CDATA[--- $TESTTMP/test-failure-unicode.t
331 <![CDATA[--- $TESTTMP/test-failure-unicode.t
281 +++ $TESTTMP/test-failure-unicode.t.err
332 +++ $TESTTMP/test-failure-unicode.t.err
282 @@ -1,2 +1,2 @@
333 @@ -1,2 +1,2 @@
283 $ echo babar\xce\xb1 (esc)
334 $ echo babar\xce\xb1 (esc)
284 - l\xce\xb5\xce\xb5t (esc)
335 - l\xce\xb5\xce\xb5t (esc)
285 + babar\xce\xb1 (esc)
336 + babar\xce\xb1 (esc)
286 ]]> </failure>
337 ]]> </failure>
287 </testcase>
338 </testcase>
288 <testcase name="test-failure.t" time="*"> (glob)
339 <testcase name="test-failure.t" time="*"> (glob)
289 <failure message="output changed" type="output-mismatch">
340 <failure message="output changed" type="output-mismatch">
290 <![CDATA[--- $TESTTMP/test-failure.t
341 <![CDATA[--- $TESTTMP/test-failure.t
291 +++ $TESTTMP/test-failure.t.err
342 +++ $TESTTMP/test-failure.t.err
292 @@ -1,5 +1,5 @@
343 @@ -1,5 +1,5 @@
293 $ echo babar
344 $ echo babar
294 - rataxes
345 - rataxes
295 + babar
346 + babar
296 This is a noop statement so that
347 This is a noop statement so that
297 this test is still more bytes than success.
348 this test is still more bytes than success.
298 pad pad pad pad............................................................
349 pad pad pad pad............................................................
299 ]]> </failure>
350 ]]> </failure>
300 </testcase>
351 </testcase>
301 </testsuite>
352 </testsuite>
302
353
303 $ cat .testtimes
354 $ cat .testtimes
304 test-failure-unicode.t * (glob)
355 test-failure-unicode.t * (glob)
305 test-failure.t * (glob)
356 test-failure.t * (glob)
306 test-success.t * (glob)
357 test-success.t * (glob)
307
358
308 $ rt --list-tests
359 $ rt --list-tests
309 test-failure-unicode.t
360 test-failure-unicode.t
310 test-failure.t
361 test-failure.t
311 test-success.t
362 test-success.t
312
363
313 $ rt --list-tests --json
364 $ rt --list-tests --json
314 test-failure-unicode.t
365 test-failure-unicode.t
315 test-failure.t
366 test-failure.t
316 test-success.t
367 test-success.t
317 $ cat report.json
368 $ cat report.json
318 testreport ={
369 testreport ={
319 "test-failure-unicode.t": {
370 "test-failure-unicode.t": {
320 "result": "success"
371 "result": "success"
321 },
372 },
322 "test-failure.t": {
373 "test-failure.t": {
323 "result": "success"
374 "result": "success"
324 },
375 },
325 "test-success.t": {
376 "test-success.t": {
326 "result": "success"
377 "result": "success"
327 }
378 }
328 } (no-eol)
379 } (no-eol)
329
380
330 $ rt --list-tests --xunit=xunit.xml
381 $ rt --list-tests --xunit=xunit.xml
331 test-failure-unicode.t
382 test-failure-unicode.t
332 test-failure.t
383 test-failure.t
333 test-success.t
384 test-success.t
334 $ cat xunit.xml
385 $ cat xunit.xml
335 <?xml version="1.0" encoding="utf-8"?>
386 <?xml version="1.0" encoding="utf-8"?>
336 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
387 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
337 <testcase name="test-failure-unicode.t"/>
388 <testcase name="test-failure-unicode.t"/>
338 <testcase name="test-failure.t"/>
389 <testcase name="test-failure.t"/>
339 <testcase name="test-success.t"/>
390 <testcase name="test-success.t"/>
340 </testsuite>
391 </testsuite>
341
392
342 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
393 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
343 test-failure-unicode.t
394 test-failure-unicode.t
344 test-failure.t
395 test-failure.t
345 $ cat output/report.json
396 $ cat output/report.json
346 testreport ={
397 testreport ={
347 "test-failure-unicode.t": {
398 "test-failure-unicode.t": {
348 "result": "success"
399 "result": "success"
349 },
400 },
350 "test-failure.t": {
401 "test-failure.t": {
351 "result": "success"
402 "result": "success"
352 }
403 }
353 } (no-eol)
404 } (no-eol)
354 $ cat xunit.xml
405 $ cat xunit.xml
355 <?xml version="1.0" encoding="utf-8"?>
406 <?xml version="1.0" encoding="utf-8"?>
356 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
407 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
357 <testcase name="test-failure-unicode.t"/>
408 <testcase name="test-failure-unicode.t"/>
358 <testcase name="test-failure.t"/>
409 <testcase name="test-failure.t"/>
359 </testsuite>
410 </testsuite>
360
411
361 $ rm test-failure-unicode.t
412 $ rm test-failure-unicode.t
362
413
363 test for --retest
414 test for --retest
364 ====================
415 ====================
365
416
366 $ rt --retest
417 $ rt --retest
367
418
368 --- $TESTTMP/test-failure.t
419 --- $TESTTMP/test-failure.t
369 +++ $TESTTMP/test-failure.t.err
420 +++ $TESTTMP/test-failure.t.err
370 @@ -1,5 +1,5 @@
421 @@ -1,5 +1,5 @@
371 $ echo babar
422 $ echo babar
372 - rataxes
423 - rataxes
373 + babar
424 + babar
374 This is a noop statement so that
425 This is a noop statement so that
375 this test is still more bytes than success.
426 this test is still more bytes than success.
376 pad pad pad pad............................................................
427 pad pad pad pad............................................................
377
428
378 ERROR: test-failure.t output changed
429 ERROR: test-failure.t output changed
379 !
430 !
380 Failed test-failure.t: output changed
431 Failed test-failure.t: output changed
381 # Ran 2 tests, 1 skipped, 1 failed.
432 # Ran 2 tests, 1 skipped, 1 failed.
382 python hash seed: * (glob)
433 python hash seed: * (glob)
383 [1]
434 [1]
384
435
385 --retest works with --outputdir
436 --retest works with --outputdir
386 $ rm -r output
437 $ rm -r output
387 $ mkdir output
438 $ mkdir output
388 $ mv test-failure.t.err output
439 $ mv test-failure.t.err output
389 $ rt --retest --outputdir output
440 $ rt --retest --outputdir output
390
441
391 --- $TESTTMP/test-failure.t
442 --- $TESTTMP/test-failure.t
392 +++ $TESTTMP/output/test-failure.t.err
443 +++ $TESTTMP/output/test-failure.t.err
393 @@ -1,5 +1,5 @@
444 @@ -1,5 +1,5 @@
394 $ echo babar
445 $ echo babar
395 - rataxes
446 - rataxes
396 + babar
447 + babar
397 This is a noop statement so that
448 This is a noop statement so that
398 this test is still more bytes than success.
449 this test is still more bytes than success.
399 pad pad pad pad............................................................
450 pad pad pad pad............................................................
400
451
401 ERROR: test-failure.t output changed
452 ERROR: test-failure.t output changed
402 !
453 !
403 Failed test-failure.t: output changed
454 Failed test-failure.t: output changed
404 # Ran 2 tests, 1 skipped, 1 failed.
455 # Ran 2 tests, 1 skipped, 1 failed.
405 python hash seed: * (glob)
456 python hash seed: * (glob)
406 [1]
457 [1]
407
458
408 Selecting Tests To Run
459 Selecting Tests To Run
409 ======================
460 ======================
410
461
411 successful
462 successful
412
463
413 $ rt test-success.t
464 $ rt test-success.t
414 .
465 .
415 # Ran 1 tests, 0 skipped, 0 failed.
466 # Ran 1 tests, 0 skipped, 0 failed.
416
467
417 success w/ keyword
468 success w/ keyword
418 $ rt -k xyzzy
469 $ rt -k xyzzy
419 .
470 .
420 # Ran 2 tests, 1 skipped, 0 failed.
471 # Ran 2 tests, 1 skipped, 0 failed.
421
472
422 failed
473 failed
423
474
424 $ rt test-failure.t
475 $ rt test-failure.t
425
476
426 --- $TESTTMP/test-failure.t
477 --- $TESTTMP/test-failure.t
427 +++ $TESTTMP/test-failure.t.err
478 +++ $TESTTMP/test-failure.t.err
428 @@ -1,5 +1,5 @@
479 @@ -1,5 +1,5 @@
429 $ echo babar
480 $ echo babar
430 - rataxes
481 - rataxes
431 + babar
482 + babar
432 This is a noop statement so that
483 This is a noop statement so that
433 this test is still more bytes than success.
484 this test is still more bytes than success.
434 pad pad pad pad............................................................
485 pad pad pad pad............................................................
435
486
436 ERROR: test-failure.t output changed
487 ERROR: test-failure.t output changed
437 !
488 !
438 Failed test-failure.t: output changed
489 Failed test-failure.t: output changed
439 # Ran 1 tests, 0 skipped, 1 failed.
490 # Ran 1 tests, 0 skipped, 1 failed.
440 python hash seed: * (glob)
491 python hash seed: * (glob)
441 [1]
492 [1]
442
493
443 failure w/ keyword
494 failure w/ keyword
444 $ rt -k rataxes
495 $ rt -k rataxes
445
496
446 --- $TESTTMP/test-failure.t
497 --- $TESTTMP/test-failure.t
447 +++ $TESTTMP/test-failure.t.err
498 +++ $TESTTMP/test-failure.t.err
448 @@ -1,5 +1,5 @@
499 @@ -1,5 +1,5 @@
449 $ echo babar
500 $ echo babar
450 - rataxes
501 - rataxes
451 + babar
502 + babar
452 This is a noop statement so that
503 This is a noop statement so that
453 this test is still more bytes than success.
504 this test is still more bytes than success.
454 pad pad pad pad............................................................
505 pad pad pad pad............................................................
455
506
456 ERROR: test-failure.t output changed
507 ERROR: test-failure.t output changed
457 !
508 !
458 Failed test-failure.t: output changed
509 Failed test-failure.t: output changed
459 # Ran 2 tests, 1 skipped, 1 failed.
510 # Ran 2 tests, 1 skipped, 1 failed.
460 python hash seed: * (glob)
511 python hash seed: * (glob)
461 [1]
512 [1]
462
513
463 Verify that when a process fails to start we show a useful message
514 Verify that when a process fails to start we show a useful message
464 ==================================================================
515 ==================================================================
465
516
466 $ cat > test-serve-fail.t <<EOF
517 $ cat > test-serve-fail.t <<EOF
467 > $ echo 'abort: child process failed to start blah'
518 > $ echo 'abort: child process failed to start blah'
468 > EOF
519 > EOF
469 $ rt test-serve-fail.t
520 $ rt test-serve-fail.t
470
521
471 ERROR: test-serve-fail.t output changed
522 ERROR: test-serve-fail.t output changed
472 !
523 !
473 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
524 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
474 # Ran 1 tests, 0 skipped, 1 failed.
525 # Ran 1 tests, 0 skipped, 1 failed.
475 python hash seed: * (glob)
526 python hash seed: * (glob)
476 [1]
527 [1]
477 $ rm test-serve-fail.t
528 $ rm test-serve-fail.t
478
529
479 Verify that we can try other ports
530 Verify that we can try other ports
480 ===================================
531 ===================================
481 $ hg init inuse
532 $ hg init inuse
482 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
533 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
483 $ cat blocks.pid >> $DAEMON_PIDS
534 $ cat blocks.pid >> $DAEMON_PIDS
484 $ cat > test-serve-inuse.t <<EOF
535 $ cat > test-serve-inuse.t <<EOF
485 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
536 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
486 > $ cat hg.pid >> \$DAEMON_PIDS
537 > $ cat hg.pid >> \$DAEMON_PIDS
487 > EOF
538 > EOF
488 $ rt test-serve-inuse.t
539 $ rt test-serve-inuse.t
489 .
540 .
490 # Ran 1 tests, 0 skipped, 0 failed.
541 # Ran 1 tests, 0 skipped, 0 failed.
491 $ rm test-serve-inuse.t
542 $ rm test-serve-inuse.t
492 $ killdaemons.py $DAEMON_PIDS
543 $ killdaemons.py $DAEMON_PIDS
493 $ rm $DAEMON_PIDS
544 $ rm $DAEMON_PIDS
494
545
495 Running In Debug Mode
546 Running In Debug Mode
496 ======================
547 ======================
497
548
498 $ rt --debug 2>&1 | grep -v pwd
549 $ rt --debug 2>&1 | grep -v pwd
499 + echo *SALT* 0 0 (glob)
550 + echo *SALT* 0 0 (glob)
500 *SALT* 0 0 (glob)
551 *SALT* 0 0 (glob)
501 + echo babar
552 + echo babar
502 babar
553 babar
503 + echo *SALT* 10 0 (glob)
554 + echo *SALT* 10 0 (glob)
504 *SALT* 10 0 (glob)
555 *SALT* 10 0 (glob)
505 *+ echo *SALT* 0 0 (glob)
556 *+ echo *SALT* 0 0 (glob)
506 *SALT* 0 0 (glob)
557 *SALT* 0 0 (glob)
507 + echo babar
558 + echo babar
508 babar
559 babar
509 + echo *SALT* 2 0 (glob)
560 + echo *SALT* 2 0 (glob)
510 *SALT* 2 0 (glob)
561 *SALT* 2 0 (glob)
511 + echo xyzzy
562 + echo xyzzy
512 xyzzy
563 xyzzy
513 + echo *SALT* 9 0 (glob)
564 + echo *SALT* 9 0 (glob)
514 *SALT* 9 0 (glob)
565 *SALT* 9 0 (glob)
515 + printf *abc\ndef\nxyz\n* (glob)
566 + printf *abc\ndef\nxyz\n* (glob)
516 abc
567 abc
517 def
568 def
518 xyz
569 xyz
519 + echo *SALT* 15 0 (glob)
570 + echo *SALT* 15 0 (glob)
520 *SALT* 15 0 (glob)
571 *SALT* 15 0 (glob)
521 + printf *zyx\nwvu\ntsr\n* (glob)
572 + printf *zyx\nwvu\ntsr\n* (glob)
522 zyx
573 zyx
523 wvu
574 wvu
524 tsr
575 tsr
525 + echo *SALT* 22 0 (glob)
576 + echo *SALT* 22 0 (glob)
526 *SALT* 22 0 (glob)
577 *SALT* 22 0 (glob)
527 .
578 .
528 # Ran 2 tests, 0 skipped, 0 failed.
579 # Ran 2 tests, 0 skipped, 0 failed.
529
580
530 Parallel runs
581 Parallel runs
531 ==============
582 ==============
532
583
533 (duplicate the failing test to get predictable output)
584 (duplicate the failing test to get predictable output)
534 $ cp test-failure.t test-failure-copy.t
585 $ cp test-failure.t test-failure-copy.t
535
586
536 $ rt --jobs 2 test-failure*.t -n
587 $ rt --jobs 2 test-failure*.t -n
537 !!
588 !!
538 Failed test-failure*.t: output changed (glob)
589 Failed test-failure*.t: output changed (glob)
539 Failed test-failure*.t: output changed (glob)
590 Failed test-failure*.t: output changed (glob)
540 # Ran 2 tests, 0 skipped, 2 failed.
591 # Ran 2 tests, 0 skipped, 2 failed.
541 python hash seed: * (glob)
592 python hash seed: * (glob)
542 [1]
593 [1]
543
594
544 failures in parallel with --first should only print one failure
595 failures in parallel with --first should only print one failure
545 >>> f = open('test-nothing.t', 'w')
596 >>> f = open('test-nothing.t', 'w')
546 >>> f.write('foo\n' * 1024) and None
597 >>> f.write('foo\n' * 1024) and None
547 >>> f.write(' $ sleep 1') and None
598 >>> f.write(' $ sleep 1') and None
548 $ rt --jobs 2 --first
599 $ rt --jobs 2 --first
549
600
550 --- $TESTTMP/test-failure*.t (glob)
601 --- $TESTTMP/test-failure*.t (glob)
551 +++ $TESTTMP/test-failure*.t.err (glob)
602 +++ $TESTTMP/test-failure*.t.err (glob)
552 @@ -1,5 +1,5 @@
603 @@ -1,5 +1,5 @@
553 $ echo babar
604 $ echo babar
554 - rataxes
605 - rataxes
555 + babar
606 + babar
556 This is a noop statement so that
607 This is a noop statement so that
557 this test is still more bytes than success.
608 this test is still more bytes than success.
558 pad pad pad pad............................................................
609 pad pad pad pad............................................................
559
610
560 Failed test-failure*.t: output changed (glob)
611 Failed test-failure*.t: output changed (glob)
561 Failed test-nothing.t: output changed
612 Failed test-nothing.t: output changed
562 # Ran 2 tests, 0 skipped, 2 failed.
613 # Ran 2 tests, 0 skipped, 2 failed.
563 python hash seed: * (glob)
614 python hash seed: * (glob)
564 [1]
615 [1]
565
616
566
617
567 (delete the duplicated test file)
618 (delete the duplicated test file)
568 $ rm test-failure-copy.t test-nothing.t
619 $ rm test-failure-copy.t test-nothing.t
569
620
570
621
571 Interactive run
622 Interactive run
572 ===============
623 ===============
573
624
574 (backup the failing test)
625 (backup the failing test)
575 $ cp test-failure.t backup
626 $ cp test-failure.t backup
576
627
577 Refuse the fix
628 Refuse the fix
578
629
579 $ echo 'n' | rt -i
630 $ echo 'n' | rt -i
580
631
581 --- $TESTTMP/test-failure.t
632 --- $TESTTMP/test-failure.t
582 +++ $TESTTMP/test-failure.t.err
633 +++ $TESTTMP/test-failure.t.err
583 @@ -1,5 +1,5 @@
634 @@ -1,5 +1,5 @@
584 $ echo babar
635 $ echo babar
585 - rataxes
636 - rataxes
586 + babar
637 + babar
587 This is a noop statement so that
638 This is a noop statement so that
588 this test is still more bytes than success.
639 this test is still more bytes than success.
589 pad pad pad pad............................................................
640 pad pad pad pad............................................................
590 Accept this change? [n]
641 Accept this change? [n]
591 ERROR: test-failure.t output changed
642 ERROR: test-failure.t output changed
592 !.
643 !.
593 Failed test-failure.t: output changed
644 Failed test-failure.t: output changed
594 # Ran 2 tests, 0 skipped, 1 failed.
645 # Ran 2 tests, 0 skipped, 1 failed.
595 python hash seed: * (glob)
646 python hash seed: * (glob)
596 [1]
647 [1]
597
648
598 $ cat test-failure.t
649 $ cat test-failure.t
599 $ echo babar
650 $ echo babar
600 rataxes
651 rataxes
601 This is a noop statement so that
652 This is a noop statement so that
602 this test is still more bytes than success.
653 this test is still more bytes than success.
603 pad pad pad pad............................................................
654 pad pad pad pad............................................................
604 pad pad pad pad............................................................
655 pad pad pad pad............................................................
605 pad pad pad pad............................................................
656 pad pad pad pad............................................................
606 pad pad pad pad............................................................
657 pad pad pad pad............................................................
607 pad pad pad pad............................................................
658 pad pad pad pad............................................................
608 pad pad pad pad............................................................
659 pad pad pad pad............................................................
609
660
610 Interactive with custom view
661 Interactive with custom view
611
662
612 $ echo 'n' | rt -i --view echo
663 $ echo 'n' | rt -i --view echo
613 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
664 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
614 Accept this change? [n]* (glob)
665 Accept this change? [n]* (glob)
615 ERROR: test-failure.t output changed
666 ERROR: test-failure.t output changed
616 !.
667 !.
617 Failed test-failure.t: output changed
668 Failed test-failure.t: output changed
618 # Ran 2 tests, 0 skipped, 1 failed.
669 # Ran 2 tests, 0 skipped, 1 failed.
619 python hash seed: * (glob)
670 python hash seed: * (glob)
620 [1]
671 [1]
621
672
622 View the fix
673 View the fix
623
674
624 $ echo 'y' | rt --view echo
675 $ echo 'y' | rt --view echo
625 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
676 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
626
677
627 ERROR: test-failure.t output changed
678 ERROR: test-failure.t output changed
628 !.
679 !.
629 Failed test-failure.t: output changed
680 Failed test-failure.t: output changed
630 # Ran 2 tests, 0 skipped, 1 failed.
681 # Ran 2 tests, 0 skipped, 1 failed.
631 python hash seed: * (glob)
682 python hash seed: * (glob)
632 [1]
683 [1]
633
684
634 Accept the fix
685 Accept the fix
635
686
636 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
687 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
637 $ echo " saved backup bundle to \$TESTTMP/foo.hg" >> test-failure.t
688 $ echo " saved backup bundle to \$TESTTMP/foo.hg" >> test-failure.t
638 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
689 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
639 $ echo " saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t
690 $ echo " saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t
640 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
691 $ echo " $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
641 $ echo " saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t
692 $ echo " saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t
642 $ echo 'y' | rt -i 2>&1
693 $ echo 'y' | rt -i 2>&1
643
694
644 --- $TESTTMP/test-failure.t
695 --- $TESTTMP/test-failure.t
645 +++ $TESTTMP/test-failure.t.err
696 +++ $TESTTMP/test-failure.t.err
646 @@ -1,5 +1,5 @@
697 @@ -1,5 +1,5 @@
647 $ echo babar
698 $ echo babar
648 - rataxes
699 - rataxes
649 + babar
700 + babar
650 This is a noop statement so that
701 This is a noop statement so that
651 this test is still more bytes than success.
702 this test is still more bytes than success.
652 pad pad pad pad............................................................
703 pad pad pad pad............................................................
653 @@ -9,7 +9,7 @@
704 @@ -9,7 +9,7 @@
654 pad pad pad pad............................................................
705 pad pad pad pad............................................................
655 pad pad pad pad............................................................
706 pad pad pad pad............................................................
656 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
707 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
657 - saved backup bundle to $TESTTMP/foo.hg
708 - saved backup bundle to $TESTTMP/foo.hg
658 + saved backup bundle to $TESTTMP/foo.hg* (glob)
709 + saved backup bundle to $TESTTMP/foo.hg* (glob)
659 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
710 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
660 saved backup bundle to $TESTTMP/foo.hg* (glob)
711 saved backup bundle to $TESTTMP/foo.hg* (glob)
661 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
712 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
662 Accept this change? [n] ..
713 Accept this change? [n] ..
663 # Ran 2 tests, 0 skipped, 0 failed.
714 # Ran 2 tests, 0 skipped, 0 failed.
664
715
665 $ sed -e 's,(glob)$,&<,g' test-failure.t
716 $ sed -e 's,(glob)$,&<,g' test-failure.t
666 $ echo babar
717 $ echo babar
667 babar
718 babar
668 This is a noop statement so that
719 This is a noop statement so that
669 this test is still more bytes than success.
720 this test is still more bytes than success.
670 pad pad pad pad............................................................
721 pad pad pad pad............................................................
671 pad pad pad pad............................................................
722 pad pad pad pad............................................................
672 pad pad pad pad............................................................
723 pad pad pad pad............................................................
673 pad pad pad pad............................................................
724 pad pad pad pad............................................................
674 pad pad pad pad............................................................
725 pad pad pad pad............................................................
675 pad pad pad pad............................................................
726 pad pad pad pad............................................................
676 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
727 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
677 saved backup bundle to $TESTTMP/foo.hg (glob)<
728 saved backup bundle to $TESTTMP/foo.hg (glob)<
678 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
729 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
679 saved backup bundle to $TESTTMP/foo.hg (glob)<
730 saved backup bundle to $TESTTMP/foo.hg (glob)<
680 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
731 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
681 saved backup bundle to $TESTTMP/*.hg (glob)<
732 saved backup bundle to $TESTTMP/*.hg (glob)<
682
733
683 Race condition - test file was modified when test is running
734 Race condition - test file was modified when test is running
684
735
685 $ TESTRACEDIR=`pwd`
736 $ TESTRACEDIR=`pwd`
686 $ export TESTRACEDIR
737 $ export TESTRACEDIR
687 $ cat > test-race.t <<EOF
738 $ cat > test-race.t <<EOF
688 > $ echo 1
739 > $ echo 1
689 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
740 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
690 > EOF
741 > EOF
691
742
692 $ rt -i test-race.t
743 $ rt -i test-race.t
693
744
694 --- $TESTTMP/test-race.t
745 --- $TESTTMP/test-race.t
695 +++ $TESTTMP/test-race.t.err
746 +++ $TESTTMP/test-race.t.err
696 @@ -1,2 +1,3 @@
747 @@ -1,2 +1,3 @@
697 $ echo 1
748 $ echo 1
698 + 1
749 + 1
699 $ echo "# a new line" >> $TESTTMP/test-race.t
750 $ echo "# a new line" >> $TESTTMP/test-race.t
700 Reference output has changed (run again to prompt changes)
751 Reference output has changed (run again to prompt changes)
701 ERROR: test-race.t output changed
752 ERROR: test-race.t output changed
702 !
753 !
703 Failed test-race.t: output changed
754 Failed test-race.t: output changed
704 # Ran 1 tests, 0 skipped, 1 failed.
755 # Ran 1 tests, 0 skipped, 1 failed.
705 python hash seed: * (glob)
756 python hash seed: * (glob)
706 [1]
757 [1]
707
758
708 $ rm test-race.t
759 $ rm test-race.t
709
760
710 When "#testcases" is used in .t files
761 When "#testcases" is used in .t files
711
762
712 $ cat >> test-cases.t <<EOF
763 $ cat >> test-cases.t <<EOF
713 > #testcases a b
764 > #testcases a b
714 > #if a
765 > #if a
715 > $ echo 1
766 > $ echo 1
716 > #endif
767 > #endif
717 > #if b
768 > #if b
718 > $ echo 2
769 > $ echo 2
719 > #endif
770 > #endif
720 > EOF
771 > EOF
721
772
722 $ cat <<EOF | rt -i test-cases.t 2>&1
773 $ cat <<EOF | rt -i test-cases.t 2>&1
723 > y
774 > y
724 > y
775 > y
725 > EOF
776 > EOF
726
777
727 --- $TESTTMP/test-cases.t
778 --- $TESTTMP/test-cases.t
728 +++ $TESTTMP/test-cases.t.a.err
779 +++ $TESTTMP/test-cases.t.a.err
729 @@ -1,6 +1,7 @@
780 @@ -1,6 +1,7 @@
730 #testcases a b
781 #testcases a b
731 #if a
782 #if a
732 $ echo 1
783 $ echo 1
733 + 1
784 + 1
734 #endif
785 #endif
735 #if b
786 #if b
736 $ echo 2
787 $ echo 2
737 Accept this change? [n] .
788 Accept this change? [n] .
738 --- $TESTTMP/test-cases.t
789 --- $TESTTMP/test-cases.t
739 +++ $TESTTMP/test-cases.t.b.err
790 +++ $TESTTMP/test-cases.t.b.err
740 @@ -5,4 +5,5 @@
791 @@ -5,4 +5,5 @@
741 #endif
792 #endif
742 #if b
793 #if b
743 $ echo 2
794 $ echo 2
744 + 2
795 + 2
745 #endif
796 #endif
746 Accept this change? [n] .
797 Accept this change? [n] .
747 # Ran 2 tests, 0 skipped, 0 failed.
798 # Ran 2 tests, 0 skipped, 0 failed.
748
799
749 $ cat test-cases.t
800 $ cat test-cases.t
750 #testcases a b
801 #testcases a b
751 #if a
802 #if a
752 $ echo 1
803 $ echo 1
753 1
804 1
754 #endif
805 #endif
755 #if b
806 #if b
756 $ echo 2
807 $ echo 2
757 2
808 2
758 #endif
809 #endif
759
810
760 $ rm test-cases.t
811 $ rm test-cases.t
761
812
762 (reinstall)
813 (reinstall)
763 $ mv backup test-failure.t
814 $ mv backup test-failure.t
764
815
765 No Diff
816 No Diff
766 ===============
817 ===============
767
818
768 $ rt --nodiff
819 $ rt --nodiff
769 !.
820 !.
770 Failed test-failure.t: output changed
821 Failed test-failure.t: output changed
771 # Ran 2 tests, 0 skipped, 1 failed.
822 # Ran 2 tests, 0 skipped, 1 failed.
772 python hash seed: * (glob)
823 python hash seed: * (glob)
773 [1]
824 [1]
774
825
775 test --tmpdir support
826 test --tmpdir support
776 $ rt --tmpdir=$TESTTMP/keep test-success.t
827 $ rt --tmpdir=$TESTTMP/keep test-success.t
777
828
778 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t (glob)
829 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t (glob)
779 Keeping threadtmp dir: $TESTTMP/keep/child1 (glob)
830 Keeping threadtmp dir: $TESTTMP/keep/child1 (glob)
780 .
831 .
781 # Ran 1 tests, 0 skipped, 0 failed.
832 # Ran 1 tests, 0 skipped, 0 failed.
782
833
783 timeouts
834 timeouts
784 ========
835 ========
785 $ cat > test-timeout.t <<EOF
836 $ cat > test-timeout.t <<EOF
786 > $ sleep 2
837 > $ sleep 2
787 > $ echo pass
838 > $ echo pass
788 > pass
839 > pass
789 > EOF
840 > EOF
790 > echo '#require slow' > test-slow-timeout.t
841 > echo '#require slow' > test-slow-timeout.t
791 > cat test-timeout.t >> test-slow-timeout.t
842 > cat test-timeout.t >> test-slow-timeout.t
792 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
843 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
793 st
844 st
794 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
845 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
795 Failed test-timeout.t: timed out
846 Failed test-timeout.t: timed out
796 # Ran 1 tests, 1 skipped, 1 failed.
847 # Ran 1 tests, 1 skipped, 1 failed.
797 python hash seed: * (glob)
848 python hash seed: * (glob)
798 [1]
849 [1]
799 $ rt --timeout=1 --slowtimeout=3 \
850 $ rt --timeout=1 --slowtimeout=3 \
800 > test-timeout.t test-slow-timeout.t --allow-slow-tests
851 > test-timeout.t test-slow-timeout.t --allow-slow-tests
801 .t
852 .t
802 Failed test-timeout.t: timed out
853 Failed test-timeout.t: timed out
803 # Ran 2 tests, 0 skipped, 1 failed.
854 # Ran 2 tests, 0 skipped, 1 failed.
804 python hash seed: * (glob)
855 python hash seed: * (glob)
805 [1]
856 [1]
806 $ rm test-timeout.t test-slow-timeout.t
857 $ rm test-timeout.t test-slow-timeout.t
807
858
808 test for --time
859 test for --time
809 ==================
860 ==================
810
861
811 $ rt test-success.t --time
862 $ rt test-success.t --time
812 .
863 .
813 # Ran 1 tests, 0 skipped, 0 failed.
864 # Ran 1 tests, 0 skipped, 0 failed.
814 # Producing time report
865 # Producing time report
815 start end cuser csys real Test
866 start end cuser csys real Test
816 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
867 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
817
868
818 test for --time with --job enabled
869 test for --time with --job enabled
819 ====================================
870 ====================================
820
871
821 $ rt test-success.t --time --jobs 2
872 $ rt test-success.t --time --jobs 2
822 .
873 .
823 # Ran 1 tests, 0 skipped, 0 failed.
874 # Ran 1 tests, 0 skipped, 0 failed.
824 # Producing time report
875 # Producing time report
825 start end cuser csys real Test
876 start end cuser csys real Test
826 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
877 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
827
878
828 Skips
879 Skips
829 ================
880 ================
830 $ cat > test-skip.t <<EOF
881 $ cat > test-skip.t <<EOF
831 > $ echo xyzzy
882 > $ echo xyzzy
832 > #require false
883 > #require false
833 > EOF
884 > EOF
834 $ rt --nodiff
885 $ rt --nodiff
835 !.s
886 !.s
836 Skipped test-skip.t: missing feature: nail clipper
887 Skipped test-skip.t: missing feature: nail clipper
837 Failed test-failure.t: output changed
888 Failed test-failure.t: output changed
838 # Ran 2 tests, 1 skipped, 1 failed.
889 # Ran 2 tests, 1 skipped, 1 failed.
839 python hash seed: * (glob)
890 python hash seed: * (glob)
840 [1]
891 [1]
841
892
842 $ rt --keyword xyzzy
893 $ rt --keyword xyzzy
843 .s
894 .s
844 Skipped test-skip.t: missing feature: nail clipper
895 Skipped test-skip.t: missing feature: nail clipper
845 # Ran 2 tests, 2 skipped, 0 failed.
896 # Ran 2 tests, 2 skipped, 0 failed.
846
897
847 Skips with xml
898 Skips with xml
848 $ rt --keyword xyzzy \
899 $ rt --keyword xyzzy \
849 > --xunit=xunit.xml
900 > --xunit=xunit.xml
850 .s
901 .s
851 Skipped test-skip.t: missing feature: nail clipper
902 Skipped test-skip.t: missing feature: nail clipper
852 # Ran 2 tests, 2 skipped, 0 failed.
903 # Ran 2 tests, 2 skipped, 0 failed.
853 $ cat xunit.xml
904 $ cat xunit.xml
854 <?xml version="1.0" encoding="utf-8"?>
905 <?xml version="1.0" encoding="utf-8"?>
855 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
906 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
856 <testcase name="test-success.t" time="*"/> (glob)
907 <testcase name="test-success.t" time="*"/> (glob)
857 <testcase name="test-skip.t">
908 <testcase name="test-skip.t">
858 <skipped>
909 <skipped>
859 <![CDATA[missing feature: nail clipper]]> </skipped>
910 <![CDATA[missing feature: nail clipper]]> </skipped>
860 </testcase>
911 </testcase>
861 </testsuite>
912 </testsuite>
862
913
863 Missing skips or blacklisted skips don't count as executed:
914 Missing skips or blacklisted skips don't count as executed:
864 $ echo test-failure.t > blacklist
915 $ echo test-failure.t > blacklist
865 $ rt --blacklist=blacklist --json\
916 $ rt --blacklist=blacklist --json\
866 > test-failure.t test-bogus.t
917 > test-failure.t test-bogus.t
867 ss
918 ss
868 Skipped test-bogus.t: Doesn't exist
919 Skipped test-bogus.t: Doesn't exist
869 Skipped test-failure.t: blacklisted
920 Skipped test-failure.t: blacklisted
870 # Ran 0 tests, 2 skipped, 0 failed.
921 # Ran 0 tests, 2 skipped, 0 failed.
871 $ cat report.json
922 $ cat report.json
872 testreport ={
923 testreport ={
873 "test-bogus.t": {
924 "test-bogus.t": {
874 "result": "skip"
925 "result": "skip"
875 },
926 },
876 "test-failure.t": {
927 "test-failure.t": {
877 "result": "skip"
928 "result": "skip"
878 }
929 }
879 } (no-eol)
930 } (no-eol)
880
931
881 Whitelist trumps blacklist
932 Whitelist trumps blacklist
882 $ echo test-failure.t > whitelist
933 $ echo test-failure.t > whitelist
883 $ rt --blacklist=blacklist --whitelist=whitelist --json\
934 $ rt --blacklist=blacklist --whitelist=whitelist --json\
884 > test-failure.t test-bogus.t
935 > test-failure.t test-bogus.t
885 s
936 s
886 --- $TESTTMP/test-failure.t
937 --- $TESTTMP/test-failure.t
887 +++ $TESTTMP/test-failure.t.err
938 +++ $TESTTMP/test-failure.t.err
888 @@ -1,5 +1,5 @@
939 @@ -1,5 +1,5 @@
889 $ echo babar
940 $ echo babar
890 - rataxes
941 - rataxes
891 + babar
942 + babar
892 This is a noop statement so that
943 This is a noop statement so that
893 this test is still more bytes than success.
944 this test is still more bytes than success.
894 pad pad pad pad............................................................
945 pad pad pad pad............................................................
895
946
896 ERROR: test-failure.t output changed
947 ERROR: test-failure.t output changed
897 !
948 !
898 Skipped test-bogus.t: Doesn't exist
949 Skipped test-bogus.t: Doesn't exist
899 Failed test-failure.t: output changed
950 Failed test-failure.t: output changed
900 # Ran 1 tests, 1 skipped, 1 failed.
951 # Ran 1 tests, 1 skipped, 1 failed.
901 python hash seed: * (glob)
952 python hash seed: * (glob)
902 [1]
953 [1]
903
954
904 test for --json
955 test for --json
905 ==================
956 ==================
906
957
907 $ rt --json
958 $ rt --json
908
959
909 --- $TESTTMP/test-failure.t
960 --- $TESTTMP/test-failure.t
910 +++ $TESTTMP/test-failure.t.err
961 +++ $TESTTMP/test-failure.t.err
911 @@ -1,5 +1,5 @@
962 @@ -1,5 +1,5 @@
912 $ echo babar
963 $ echo babar
913 - rataxes
964 - rataxes
914 + babar
965 + babar
915 This is a noop statement so that
966 This is a noop statement so that
916 this test is still more bytes than success.
967 this test is still more bytes than success.
917 pad pad pad pad............................................................
968 pad pad pad pad............................................................
918
969
919 ERROR: test-failure.t output changed
970 ERROR: test-failure.t output changed
920 !.s
971 !.s
921 Skipped test-skip.t: missing feature: nail clipper
972 Skipped test-skip.t: missing feature: nail clipper
922 Failed test-failure.t: output changed
973 Failed test-failure.t: output changed
923 # Ran 2 tests, 1 skipped, 1 failed.
974 # Ran 2 tests, 1 skipped, 1 failed.
924 python hash seed: * (glob)
975 python hash seed: * (glob)
925 [1]
976 [1]
926
977
927 $ cat report.json
978 $ cat report.json
928 testreport ={
979 testreport ={
929 "test-failure.t": [\{] (re)
980 "test-failure.t": [\{] (re)
930 "csys": "\s*[\d\.]{4,5}", ? (re)
981 "csys": "\s*[\d\.]{4,5}", ? (re)
931 "cuser": "\s*[\d\.]{4,5}", ? (re)
982 "cuser": "\s*[\d\.]{4,5}", ? (re)
932 "diff": "---.+\+\+\+.+", ? (re)
983 "diff": "---.+\+\+\+.+", ? (re)
933 "end": "\s*[\d\.]{4,5}", ? (re)
984 "end": "\s*[\d\.]{4,5}", ? (re)
934 "result": "failure", ? (re)
985 "result": "failure", ? (re)
935 "start": "\s*[\d\.]{4,5}", ? (re)
986 "start": "\s*[\d\.]{4,5}", ? (re)
936 "time": "\s*[\d\.]{4,5}" (re)
987 "time": "\s*[\d\.]{4,5}" (re)
937 }, ? (re)
988 }, ? (re)
938 "test-skip.t": {
989 "test-skip.t": {
939 "csys": "\s*[\d\.]{4,5}", ? (re)
990 "csys": "\s*[\d\.]{4,5}", ? (re)
940 "cuser": "\s*[\d\.]{4,5}", ? (re)
991 "cuser": "\s*[\d\.]{4,5}", ? (re)
941 "diff": "", ? (re)
992 "diff": "", ? (re)
942 "end": "\s*[\d\.]{4,5}", ? (re)
993 "end": "\s*[\d\.]{4,5}", ? (re)
943 "result": "skip", ? (re)
994 "result": "skip", ? (re)
944 "start": "\s*[\d\.]{4,5}", ? (re)
995 "start": "\s*[\d\.]{4,5}", ? (re)
945 "time": "\s*[\d\.]{4,5}" (re)
996 "time": "\s*[\d\.]{4,5}" (re)
946 }, ? (re)
997 }, ? (re)
947 "test-success.t": [\{] (re)
998 "test-success.t": [\{] (re)
948 "csys": "\s*[\d\.]{4,5}", ? (re)
999 "csys": "\s*[\d\.]{4,5}", ? (re)
949 "cuser": "\s*[\d\.]{4,5}", ? (re)
1000 "cuser": "\s*[\d\.]{4,5}", ? (re)
950 "diff": "", ? (re)
1001 "diff": "", ? (re)
951 "end": "\s*[\d\.]{4,5}", ? (re)
1002 "end": "\s*[\d\.]{4,5}", ? (re)
952 "result": "success", ? (re)
1003 "result": "success", ? (re)
953 "start": "\s*[\d\.]{4,5}", ? (re)
1004 "start": "\s*[\d\.]{4,5}", ? (re)
954 "time": "\s*[\d\.]{4,5}" (re)
1005 "time": "\s*[\d\.]{4,5}" (re)
955 }
1006 }
956 } (no-eol)
1007 } (no-eol)
957 --json with --outputdir
1008 --json with --outputdir
958
1009
959 $ rm report.json
1010 $ rm report.json
960 $ rm -r output
1011 $ rm -r output
961 $ mkdir output
1012 $ mkdir output
962 $ rt --json --outputdir output
1013 $ rt --json --outputdir output
963
1014
964 --- $TESTTMP/test-failure.t
1015 --- $TESTTMP/test-failure.t
965 +++ $TESTTMP/output/test-failure.t.err
1016 +++ $TESTTMP/output/test-failure.t.err
966 @@ -1,5 +1,5 @@
1017 @@ -1,5 +1,5 @@
967 $ echo babar
1018 $ echo babar
968 - rataxes
1019 - rataxes
969 + babar
1020 + babar
970 This is a noop statement so that
1021 This is a noop statement so that
971 this test is still more bytes than success.
1022 this test is still more bytes than success.
972 pad pad pad pad............................................................
1023 pad pad pad pad............................................................
973
1024
974 ERROR: test-failure.t output changed
1025 ERROR: test-failure.t output changed
975 !.s
1026 !.s
976 Skipped test-skip.t: missing feature: nail clipper
1027 Skipped test-skip.t: missing feature: nail clipper
977 Failed test-failure.t: output changed
1028 Failed test-failure.t: output changed
978 # Ran 2 tests, 1 skipped, 1 failed.
1029 # Ran 2 tests, 1 skipped, 1 failed.
979 python hash seed: * (glob)
1030 python hash seed: * (glob)
980 [1]
1031 [1]
981 $ f report.json
1032 $ f report.json
982 report.json: file not found
1033 report.json: file not found
983 $ cat output/report.json
1034 $ cat output/report.json
984 testreport ={
1035 testreport ={
985 "test-failure.t": [\{] (re)
1036 "test-failure.t": [\{] (re)
986 "csys": "\s*[\d\.]{4,5}", ? (re)
1037 "csys": "\s*[\d\.]{4,5}", ? (re)
987 "cuser": "\s*[\d\.]{4,5}", ? (re)
1038 "cuser": "\s*[\d\.]{4,5}", ? (re)
988 "diff": "---.+\+\+\+.+", ? (re)
1039 "diff": "---.+\+\+\+.+", ? (re)
989 "end": "\s*[\d\.]{4,5}", ? (re)
1040 "end": "\s*[\d\.]{4,5}", ? (re)
990 "result": "failure", ? (re)
1041 "result": "failure", ? (re)
991 "start": "\s*[\d\.]{4,5}", ? (re)
1042 "start": "\s*[\d\.]{4,5}", ? (re)
992 "time": "\s*[\d\.]{4,5}" (re)
1043 "time": "\s*[\d\.]{4,5}" (re)
993 }, ? (re)
1044 }, ? (re)
994 "test-skip.t": {
1045 "test-skip.t": {
995 "csys": "\s*[\d\.]{4,5}", ? (re)
1046 "csys": "\s*[\d\.]{4,5}", ? (re)
996 "cuser": "\s*[\d\.]{4,5}", ? (re)
1047 "cuser": "\s*[\d\.]{4,5}", ? (re)
997 "diff": "", ? (re)
1048 "diff": "", ? (re)
998 "end": "\s*[\d\.]{4,5}", ? (re)
1049 "end": "\s*[\d\.]{4,5}", ? (re)
999 "result": "skip", ? (re)
1050 "result": "skip", ? (re)
1000 "start": "\s*[\d\.]{4,5}", ? (re)
1051 "start": "\s*[\d\.]{4,5}", ? (re)
1001 "time": "\s*[\d\.]{4,5}" (re)
1052 "time": "\s*[\d\.]{4,5}" (re)
1002 }, ? (re)
1053 }, ? (re)
1003 "test-success.t": [\{] (re)
1054 "test-success.t": [\{] (re)
1004 "csys": "\s*[\d\.]{4,5}", ? (re)
1055 "csys": "\s*[\d\.]{4,5}", ? (re)
1005 "cuser": "\s*[\d\.]{4,5}", ? (re)
1056 "cuser": "\s*[\d\.]{4,5}", ? (re)
1006 "diff": "", ? (re)
1057 "diff": "", ? (re)
1007 "end": "\s*[\d\.]{4,5}", ? (re)
1058 "end": "\s*[\d\.]{4,5}", ? (re)
1008 "result": "success", ? (re)
1059 "result": "success", ? (re)
1009 "start": "\s*[\d\.]{4,5}", ? (re)
1060 "start": "\s*[\d\.]{4,5}", ? (re)
1010 "time": "\s*[\d\.]{4,5}" (re)
1061 "time": "\s*[\d\.]{4,5}" (re)
1011 }
1062 }
1012 } (no-eol)
1063 } (no-eol)
1013 $ ls -a output
1064 $ ls -a output
1014 .
1065 .
1015 ..
1066 ..
1016 .testtimes
1067 .testtimes
1017 report.json
1068 report.json
1018 test-failure.t.err
1069 test-failure.t.err
1019
1070
1020 Test that failed test accepted through interactive are properly reported:
1071 Test that failed test accepted through interactive are properly reported:
1021
1072
1022 $ cp test-failure.t backup
1073 $ cp test-failure.t backup
1023 $ echo y | rt --json -i
1074 $ echo y | rt --json -i
1024
1075
1025 --- $TESTTMP/test-failure.t
1076 --- $TESTTMP/test-failure.t
1026 +++ $TESTTMP/test-failure.t.err
1077 +++ $TESTTMP/test-failure.t.err
1027 @@ -1,5 +1,5 @@
1078 @@ -1,5 +1,5 @@
1028 $ echo babar
1079 $ echo babar
1029 - rataxes
1080 - rataxes
1030 + babar
1081 + babar
1031 This is a noop statement so that
1082 This is a noop statement so that
1032 this test is still more bytes than success.
1083 this test is still more bytes than success.
1033 pad pad pad pad............................................................
1084 pad pad pad pad............................................................
1034 Accept this change? [n] ..s
1085 Accept this change? [n] ..s
1035 Skipped test-skip.t: missing feature: nail clipper
1086 Skipped test-skip.t: missing feature: nail clipper
1036 # Ran 2 tests, 1 skipped, 0 failed.
1087 # Ran 2 tests, 1 skipped, 0 failed.
1037
1088
1038 $ cat report.json
1089 $ cat report.json
1039 testreport ={
1090 testreport ={
1040 "test-failure.t": [\{] (re)
1091 "test-failure.t": [\{] (re)
1041 "csys": "\s*[\d\.]{4,5}", ? (re)
1092 "csys": "\s*[\d\.]{4,5}", ? (re)
1042 "cuser": "\s*[\d\.]{4,5}", ? (re)
1093 "cuser": "\s*[\d\.]{4,5}", ? (re)
1043 "diff": "", ? (re)
1094 "diff": "", ? (re)
1044 "end": "\s*[\d\.]{4,5}", ? (re)
1095 "end": "\s*[\d\.]{4,5}", ? (re)
1045 "result": "success", ? (re)
1096 "result": "success", ? (re)
1046 "start": "\s*[\d\.]{4,5}", ? (re)
1097 "start": "\s*[\d\.]{4,5}", ? (re)
1047 "time": "\s*[\d\.]{4,5}" (re)
1098 "time": "\s*[\d\.]{4,5}" (re)
1048 }, ? (re)
1099 }, ? (re)
1049 "test-skip.t": {
1100 "test-skip.t": {
1050 "csys": "\s*[\d\.]{4,5}", ? (re)
1101 "csys": "\s*[\d\.]{4,5}", ? (re)
1051 "cuser": "\s*[\d\.]{4,5}", ? (re)
1102 "cuser": "\s*[\d\.]{4,5}", ? (re)
1052 "diff": "", ? (re)
1103 "diff": "", ? (re)
1053 "end": "\s*[\d\.]{4,5}", ? (re)
1104 "end": "\s*[\d\.]{4,5}", ? (re)
1054 "result": "skip", ? (re)
1105 "result": "skip", ? (re)
1055 "start": "\s*[\d\.]{4,5}", ? (re)
1106 "start": "\s*[\d\.]{4,5}", ? (re)
1056 "time": "\s*[\d\.]{4,5}" (re)
1107 "time": "\s*[\d\.]{4,5}" (re)
1057 }, ? (re)
1108 }, ? (re)
1058 "test-success.t": [\{] (re)
1109 "test-success.t": [\{] (re)
1059 "csys": "\s*[\d\.]{4,5}", ? (re)
1110 "csys": "\s*[\d\.]{4,5}", ? (re)
1060 "cuser": "\s*[\d\.]{4,5}", ? (re)
1111 "cuser": "\s*[\d\.]{4,5}", ? (re)
1061 "diff": "", ? (re)
1112 "diff": "", ? (re)
1062 "end": "\s*[\d\.]{4,5}", ? (re)
1113 "end": "\s*[\d\.]{4,5}", ? (re)
1063 "result": "success", ? (re)
1114 "result": "success", ? (re)
1064 "start": "\s*[\d\.]{4,5}", ? (re)
1115 "start": "\s*[\d\.]{4,5}", ? (re)
1065 "time": "\s*[\d\.]{4,5}" (re)
1116 "time": "\s*[\d\.]{4,5}" (re)
1066 }
1117 }
1067 } (no-eol)
1118 } (no-eol)
1068 $ mv backup test-failure.t
1119 $ mv backup test-failure.t
1069
1120
1070 backslash on end of line with glob matching is handled properly
1121 backslash on end of line with glob matching is handled properly
1071
1122
1072 $ cat > test-glob-backslash.t << EOF
1123 $ cat > test-glob-backslash.t << EOF
1073 > $ echo 'foo bar \\'
1124 > $ echo 'foo bar \\'
1074 > foo * \ (glob)
1125 > foo * \ (glob)
1075 > EOF
1126 > EOF
1076
1127
1077 $ rt test-glob-backslash.t
1128 $ rt test-glob-backslash.t
1078 .
1129 .
1079 # Ran 1 tests, 0 skipped, 0 failed.
1130 # Ran 1 tests, 0 skipped, 0 failed.
1080
1131
1081 $ rm -f test-glob-backslash.t
1132 $ rm -f test-glob-backslash.t
1082
1133
1083 Test globbing of local IP addresses
1134 Test globbing of local IP addresses
1084 $ echo 172.16.18.1
1135 $ echo 172.16.18.1
1085 $LOCALIP (glob)
1136 $LOCALIP (glob)
1086 $ echo dead:beef::1
1137 $ echo dead:beef::1
1087 $LOCALIP (glob)
1138 $LOCALIP (glob)
1088
1139
1089 Test reusability for third party tools
1140 Test reusability for third party tools
1090 ======================================
1141 ======================================
1091
1142
1092 $ mkdir "$TESTTMP"/anothertests
1143 $ mkdir "$TESTTMP"/anothertests
1093 $ cd "$TESTTMP"/anothertests
1144 $ cd "$TESTTMP"/anothertests
1094
1145
1095 test that `run-tests.py` can execute hghave, even if it runs not in
1146 test that `run-tests.py` can execute hghave, even if it runs not in
1096 Mercurial source tree.
1147 Mercurial source tree.
1097
1148
1098 $ cat > test-hghave.t <<EOF
1149 $ cat > test-hghave.t <<EOF
1099 > #require true
1150 > #require true
1100 > $ echo foo
1151 > $ echo foo
1101 > foo
1152 > foo
1102 > EOF
1153 > EOF
1103 $ rt test-hghave.t
1154 $ rt test-hghave.t
1104 .
1155 .
1105 # Ran 1 tests, 0 skipped, 0 failed.
1156 # Ran 1 tests, 0 skipped, 0 failed.
1106
1157
1107 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1158 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1108 running is placed.
1159 running is placed.
1109
1160
1110 $ cat > test-runtestdir.t <<EOF
1161 $ cat > test-runtestdir.t <<EOF
1111 > - $TESTDIR, in which test-run-tests.t is placed
1162 > - $TESTDIR, in which test-run-tests.t is placed
1112 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1163 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1113 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1164 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1114 >
1165 >
1115 > #if windows
1166 > #if windows
1116 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1167 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1117 > #else
1168 > #else
1118 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1169 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1119 > #endif
1170 > #endif
1120 > $ test "\$RUNTESTDIR" = "$TESTDIR"
1171 > $ test "\$RUNTESTDIR" = "$TESTDIR"
1121 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1172 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1122 > #!USRBINENVPY
1173 > #!USRBINENVPY
1123 > #
1174 > #
1124 > # check-code - a style and portability checker for Mercurial
1175 > # check-code - a style and portability checker for Mercurial
1125 > EOF
1176 > EOF
1126 $ rt test-runtestdir.t
1177 $ rt test-runtestdir.t
1127 .
1178 .
1128 # Ran 1 tests, 0 skipped, 0 failed.
1179 # Ran 1 tests, 0 skipped, 0 failed.
1129
1180
1130 #if execbit
1181 #if execbit
1131
1182
1132 test that TESTDIR is referred in PATH
1183 test that TESTDIR is referred in PATH
1133
1184
1134 $ cat > custom-command.sh <<EOF
1185 $ cat > custom-command.sh <<EOF
1135 > #!/bin/sh
1186 > #!/bin/sh
1136 > echo "hello world"
1187 > echo "hello world"
1137 > EOF
1188 > EOF
1138 $ chmod +x custom-command.sh
1189 $ chmod +x custom-command.sh
1139 $ cat > test-testdir-path.t <<EOF
1190 $ cat > test-testdir-path.t <<EOF
1140 > $ custom-command.sh
1191 > $ custom-command.sh
1141 > hello world
1192 > hello world
1142 > EOF
1193 > EOF
1143 $ rt test-testdir-path.t
1194 $ rt test-testdir-path.t
1144 .
1195 .
1145 # Ran 1 tests, 0 skipped, 0 failed.
1196 # Ran 1 tests, 0 skipped, 0 failed.
1146
1197
1147 #endif
1198 #endif
1148
1199
1149 test support for --allow-slow-tests
1200 test support for --allow-slow-tests
1150 $ cat > test-very-slow-test.t <<EOF
1201 $ cat > test-very-slow-test.t <<EOF
1151 > #require slow
1202 > #require slow
1152 > $ echo pass
1203 > $ echo pass
1153 > pass
1204 > pass
1154 > EOF
1205 > EOF
1155 $ rt test-very-slow-test.t
1206 $ rt test-very-slow-test.t
1156 s
1207 s
1157 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1208 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1158 # Ran 0 tests, 1 skipped, 0 failed.
1209 # Ran 0 tests, 1 skipped, 0 failed.
1159 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1210 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1160 .
1211 .
1161 # Ran 1 tests, 0 skipped, 0 failed.
1212 # Ran 1 tests, 0 skipped, 0 failed.
1162
1213
1163 support for running a test outside the current directory
1214 support for running a test outside the current directory
1164 $ mkdir nonlocal
1215 $ mkdir nonlocal
1165 $ cat > nonlocal/test-is-not-here.t << EOF
1216 $ cat > nonlocal/test-is-not-here.t << EOF
1166 > $ echo pass
1217 > $ echo pass
1167 > pass
1218 > pass
1168 > EOF
1219 > EOF
1169 $ rt nonlocal/test-is-not-here.t
1220 $ rt nonlocal/test-is-not-here.t
1170 .
1221 .
1171 # Ran 1 tests, 0 skipped, 0 failed.
1222 # Ran 1 tests, 0 skipped, 0 failed.
1172
1223
1173 support for bisecting failed tests automatically
1224 support for bisecting failed tests automatically
1174 $ hg init bisect
1225 $ hg init bisect
1175 $ cd bisect
1226 $ cd bisect
1176 $ cat >> test-bisect.t <<EOF
1227 $ cat >> test-bisect.t <<EOF
1177 > $ echo pass
1228 > $ echo pass
1178 > pass
1229 > pass
1179 > EOF
1230 > EOF
1180 $ hg add test-bisect.t
1231 $ hg add test-bisect.t
1181 $ hg ci -m 'good'
1232 $ hg ci -m 'good'
1182 $ cat >> test-bisect.t <<EOF
1233 $ cat >> test-bisect.t <<EOF
1183 > $ echo pass
1234 > $ echo pass
1184 > fail
1235 > fail
1185 > EOF
1236 > EOF
1186 $ hg ci -m 'bad'
1237 $ hg ci -m 'bad'
1187 $ rt --known-good-rev=0 test-bisect.t
1238 $ rt --known-good-rev=0 test-bisect.t
1188
1239
1189 --- $TESTTMP/anothertests/bisect/test-bisect.t
1240 --- $TESTTMP/anothertests/bisect/test-bisect.t
1190 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1241 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1191 @@ -1,4 +1,4 @@
1242 @@ -1,4 +1,4 @@
1192 $ echo pass
1243 $ echo pass
1193 pass
1244 pass
1194 $ echo pass
1245 $ echo pass
1195 - fail
1246 - fail
1196 + pass
1247 + pass
1197
1248
1198 ERROR: test-bisect.t output changed
1249 ERROR: test-bisect.t output changed
1199 !
1250 !
1200 Failed test-bisect.t: output changed
1251 Failed test-bisect.t: output changed
1201 test-bisect.t broken by 72cbf122d116 (bad)
1252 test-bisect.t broken by 72cbf122d116 (bad)
1202 # Ran 1 tests, 0 skipped, 1 failed.
1253 # Ran 1 tests, 0 skipped, 1 failed.
1203 python hash seed: * (glob)
1254 python hash seed: * (glob)
1204 [1]
1255 [1]
1205
1256
1206 $ cd ..
1257 $ cd ..
1207
1258
1208 Test a broken #if statement doesn't break run-tests threading.
1259 Test a broken #if statement doesn't break run-tests threading.
1209 ==============================================================
1260 ==============================================================
1210 $ mkdir broken
1261 $ mkdir broken
1211 $ cd broken
1262 $ cd broken
1212 $ cat > test-broken.t <<EOF
1263 $ cat > test-broken.t <<EOF
1213 > true
1264 > true
1214 > #if notarealhghavefeature
1265 > #if notarealhghavefeature
1215 > $ false
1266 > $ false
1216 > #endif
1267 > #endif
1217 > EOF
1268 > EOF
1218 $ for f in 1 2 3 4 ; do
1269 $ for f in 1 2 3 4 ; do
1219 > cat > test-works-$f.t <<EOF
1270 > cat > test-works-$f.t <<EOF
1220 > This is test case $f
1271 > This is test case $f
1221 > $ sleep 1
1272 > $ sleep 1
1222 > EOF
1273 > EOF
1223 > done
1274 > done
1224 $ rt -j 2
1275 $ rt -j 2
1225 ....
1276 ....
1226 # Ran 5 tests, 0 skipped, 0 failed.
1277 # Ran 5 tests, 0 skipped, 0 failed.
1227 skipped: unknown feature: notarealhghavefeature
1278 skipped: unknown feature: notarealhghavefeature
1228
1279
1229 $ cd ..
1280 $ cd ..
1230 $ rm -rf broken
1281 $ rm -rf broken
1231
1282
1232 Test cases in .t files
1283 Test cases in .t files
1233 ======================
1284 ======================
1234 $ mkdir cases
1285 $ mkdir cases
1235 $ cd cases
1286 $ cd cases
1236 $ cat > test-cases-abc.t <<'EOF'
1287 $ cat > test-cases-abc.t <<'EOF'
1237 > #testcases A B C
1288 > #testcases A B C
1238 > $ V=B
1289 > $ V=B
1239 > #if A
1290 > #if A
1240 > $ V=A
1291 > $ V=A
1241 > #endif
1292 > #endif
1242 > #if C
1293 > #if C
1243 > $ V=C
1294 > $ V=C
1244 > #endif
1295 > #endif
1245 > $ echo $V | sed 's/A/C/'
1296 > $ echo $V | sed 's/A/C/'
1246 > C
1297 > C
1247 > #if C
1298 > #if C
1248 > $ [ $V = C ]
1299 > $ [ $V = C ]
1249 > #endif
1300 > #endif
1250 > #if A
1301 > #if A
1251 > $ [ $V = C ]
1302 > $ [ $V = C ]
1252 > [1]
1303 > [1]
1253 > #endif
1304 > #endif
1254 > #if no-C
1305 > #if no-C
1255 > $ [ $V = C ]
1306 > $ [ $V = C ]
1256 > [1]
1307 > [1]
1257 > #endif
1308 > #endif
1258 > $ [ $V = D ]
1309 > $ [ $V = D ]
1259 > [1]
1310 > [1]
1260 > EOF
1311 > EOF
1261 $ rt
1312 $ rt
1262 .
1313 .
1263 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1314 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1264 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1315 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1265 @@ -7,7 +7,7 @@
1316 @@ -7,7 +7,7 @@
1266 $ V=C
1317 $ V=C
1267 #endif
1318 #endif
1268 $ echo $V | sed 's/A/C/'
1319 $ echo $V | sed 's/A/C/'
1269 - C
1320 - C
1270 + B
1321 + B
1271 #if C
1322 #if C
1272 $ [ $V = C ]
1323 $ [ $V = C ]
1273 #endif
1324 #endif
1274
1325
1275 ERROR: test-cases-abc.t (case B) output changed
1326 ERROR: test-cases-abc.t (case B) output changed
1276 !.
1327 !.
1277 Failed test-cases-abc.t (case B): output changed
1328 Failed test-cases-abc.t (case B): output changed
1278 # Ran 3 tests, 0 skipped, 1 failed.
1329 # Ran 3 tests, 0 skipped, 1 failed.
1279 python hash seed: * (glob)
1330 python hash seed: * (glob)
1280 [1]
1331 [1]
1281
1332
1282 --restart works
1333 --restart works
1283
1334
1284 $ rt --restart
1335 $ rt --restart
1285
1336
1286 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1337 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1287 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1338 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1288 @@ -7,7 +7,7 @@
1339 @@ -7,7 +7,7 @@
1289 $ V=C
1340 $ V=C
1290 #endif
1341 #endif
1291 $ echo $V | sed 's/A/C/'
1342 $ echo $V | sed 's/A/C/'
1292 - C
1343 - C
1293 + B
1344 + B
1294 #if C
1345 #if C
1295 $ [ $V = C ]
1346 $ [ $V = C ]
1296 #endif
1347 #endif
1297
1348
1298 ERROR: test-cases-abc.t (case B) output changed
1349 ERROR: test-cases-abc.t (case B) output changed
1299 !.
1350 !.
1300 Failed test-cases-abc.t (case B): output changed
1351 Failed test-cases-abc.t (case B): output changed
1301 # Ran 2 tests, 0 skipped, 1 failed.
1352 # Ran 2 tests, 0 skipped, 1 failed.
1302 python hash seed: * (glob)
1353 python hash seed: * (glob)
1303 [1]
1354 [1]
1304
1355
1305 --restart works with outputdir
1356 --restart works with outputdir
1306
1357
1307 $ mkdir output
1358 $ mkdir output
1308 $ mv test-cases-abc.t.B.err output
1359 $ mv test-cases-abc.t.B.err output
1309 $ rt --restart --outputdir output
1360 $ rt --restart --outputdir output
1310
1361
1311 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1362 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1312 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t.B.err
1363 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t.B.err
1313 @@ -7,7 +7,7 @@
1364 @@ -7,7 +7,7 @@
1314 $ V=C
1365 $ V=C
1315 #endif
1366 #endif
1316 $ echo $V | sed 's/A/C/'
1367 $ echo $V | sed 's/A/C/'
1317 - C
1368 - C
1318 + B
1369 + B
1319 #if C
1370 #if C
1320 $ [ $V = C ]
1371 $ [ $V = C ]
1321 #endif
1372 #endif
1322
1373
1323 ERROR: test-cases-abc.t (case B) output changed
1374 ERROR: test-cases-abc.t (case B) output changed
1324 !.
1375 !.
1325 Failed test-cases-abc.t (case B): output changed
1376 Failed test-cases-abc.t (case B): output changed
1326 # Ran 2 tests, 0 skipped, 1 failed.
1377 # Ran 2 tests, 0 skipped, 1 failed.
1327 python hash seed: * (glob)
1378 python hash seed: * (glob)
1328 [1]
1379 [1]
General Comments 0
You need to be logged in to leave comments. Login now