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