##// END OF EJS Templates
tests: setup dummyssh as the default ssh...
Valentin Gatien-Baron -
r48731:a28a7dcb default
parent child Browse files
Show More
@@ -1,4067 +1,4069 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
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 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Olivia Mackall <olivia@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 argparse
48 import argparse
49 import collections
49 import collections
50 import contextlib
50 import contextlib
51 import difflib
51 import difflib
52 import distutils.version as version
52 import distutils.version as version
53 import errno
53 import errno
54 import json
54 import json
55 import multiprocessing
55 import multiprocessing
56 import os
56 import os
57 import platform
57 import platform
58 import random
58 import random
59 import re
59 import re
60 import shutil
60 import shutil
61 import signal
61 import signal
62 import socket
62 import socket
63 import subprocess
63 import subprocess
64 import sys
64 import sys
65 import sysconfig
65 import sysconfig
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 uuid
70 import uuid
71 import xml.dom.minidom as minidom
71 import xml.dom.minidom as minidom
72
72
73 WINDOWS = os.name == r'nt'
73 WINDOWS = os.name == r'nt'
74
74
75 try:
75 try:
76 import Queue as queue
76 import Queue as queue
77 except ImportError:
77 except ImportError:
78 import queue
78 import queue
79
79
80 try:
80 try:
81 import shlex
81 import shlex
82
82
83 shellquote = shlex.quote
83 shellquote = shlex.quote
84 except (ImportError, AttributeError):
84 except (ImportError, AttributeError):
85 import pipes
85 import pipes
86
86
87 shellquote = pipes.quote
87 shellquote = pipes.quote
88
88
89
89
90 processlock = threading.Lock()
90 processlock = threading.Lock()
91
91
92 pygmentspresent = False
92 pygmentspresent = False
93 try: # is pygments installed
93 try: # is pygments installed
94 import pygments
94 import pygments
95 import pygments.lexers as lexers
95 import pygments.lexers as lexers
96 import pygments.lexer as lexer
96 import pygments.lexer as lexer
97 import pygments.formatters as formatters
97 import pygments.formatters as formatters
98 import pygments.token as token
98 import pygments.token as token
99 import pygments.style as style
99 import pygments.style as style
100
100
101 if WINDOWS:
101 if WINDOWS:
102 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
102 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
103 sys.path.append(hgpath)
103 sys.path.append(hgpath)
104 try:
104 try:
105 from mercurial import win32 # pytype: disable=import-error
105 from mercurial import win32 # pytype: disable=import-error
106
106
107 # Don't check the result code because it fails on heptapod, but
107 # Don't check the result code because it fails on heptapod, but
108 # something is able to convert to color anyway.
108 # something is able to convert to color anyway.
109 win32.enablevtmode()
109 win32.enablevtmode()
110 finally:
110 finally:
111 sys.path = sys.path[:-1]
111 sys.path = sys.path[:-1]
112
112
113 pygmentspresent = True
113 pygmentspresent = True
114 difflexer = lexers.DiffLexer()
114 difflexer = lexers.DiffLexer()
115 terminal256formatter = formatters.Terminal256Formatter()
115 terminal256formatter = formatters.Terminal256Formatter()
116 except ImportError:
116 except ImportError:
117 pass
117 pass
118
118
119 if pygmentspresent:
119 if pygmentspresent:
120
120
121 class TestRunnerStyle(style.Style):
121 class TestRunnerStyle(style.Style):
122 default_style = ""
122 default_style = ""
123 skipped = token.string_to_tokentype("Token.Generic.Skipped")
123 skipped = token.string_to_tokentype("Token.Generic.Skipped")
124 failed = token.string_to_tokentype("Token.Generic.Failed")
124 failed = token.string_to_tokentype("Token.Generic.Failed")
125 skippedname = token.string_to_tokentype("Token.Generic.SName")
125 skippedname = token.string_to_tokentype("Token.Generic.SName")
126 failedname = token.string_to_tokentype("Token.Generic.FName")
126 failedname = token.string_to_tokentype("Token.Generic.FName")
127 styles = {
127 styles = {
128 skipped: '#e5e5e5',
128 skipped: '#e5e5e5',
129 skippedname: '#00ffff',
129 skippedname: '#00ffff',
130 failed: '#7f0000',
130 failed: '#7f0000',
131 failedname: '#ff0000',
131 failedname: '#ff0000',
132 }
132 }
133
133
134 class TestRunnerLexer(lexer.RegexLexer):
134 class TestRunnerLexer(lexer.RegexLexer):
135 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
135 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
136 tokens = {
136 tokens = {
137 'root': [
137 'root': [
138 (r'^Skipped', token.Generic.Skipped, 'skipped'),
138 (r'^Skipped', token.Generic.Skipped, 'skipped'),
139 (r'^Failed ', token.Generic.Failed, 'failed'),
139 (r'^Failed ', token.Generic.Failed, 'failed'),
140 (r'^ERROR: ', token.Generic.Failed, 'failed'),
140 (r'^ERROR: ', token.Generic.Failed, 'failed'),
141 ],
141 ],
142 'skipped': [
142 'skipped': [
143 (testpattern, token.Generic.SName),
143 (testpattern, token.Generic.SName),
144 (r':.*', token.Generic.Skipped),
144 (r':.*', token.Generic.Skipped),
145 ],
145 ],
146 'failed': [
146 'failed': [
147 (testpattern, token.Generic.FName),
147 (testpattern, token.Generic.FName),
148 (r'(:| ).*', token.Generic.Failed),
148 (r'(:| ).*', token.Generic.Failed),
149 ],
149 ],
150 }
150 }
151
151
152 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
152 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
153 runnerlexer = TestRunnerLexer()
153 runnerlexer = TestRunnerLexer()
154
154
155 origenviron = os.environ.copy()
155 origenviron = os.environ.copy()
156
156
157
157
158 if sys.version_info > (3, 5, 0):
158 if sys.version_info > (3, 5, 0):
159 PYTHON3 = True
159 PYTHON3 = True
160 xrange = range # we use xrange in one place, and we'd rather not use range
160 xrange = range # we use xrange in one place, and we'd rather not use range
161
161
162 def _sys2bytes(p):
162 def _sys2bytes(p):
163 if p is None:
163 if p is None:
164 return p
164 return p
165 return p.encode('utf-8')
165 return p.encode('utf-8')
166
166
167 def _bytes2sys(p):
167 def _bytes2sys(p):
168 if p is None:
168 if p is None:
169 return p
169 return p
170 return p.decode('utf-8')
170 return p.decode('utf-8')
171
171
172 osenvironb = getattr(os, 'environb', None)
172 osenvironb = getattr(os, 'environb', None)
173 if osenvironb is None:
173 if osenvironb is None:
174 # Windows lacks os.environb, for instance. A proxy over the real thing
174 # Windows lacks os.environb, for instance. A proxy over the real thing
175 # instead of a copy allows the environment to be updated via bytes on
175 # instead of a copy allows the environment to be updated via bytes on
176 # all platforms.
176 # all platforms.
177 class environbytes(object):
177 class environbytes(object):
178 def __init__(self, strenv):
178 def __init__(self, strenv):
179 self.__len__ = strenv.__len__
179 self.__len__ = strenv.__len__
180 self.clear = strenv.clear
180 self.clear = strenv.clear
181 self._strenv = strenv
181 self._strenv = strenv
182
182
183 def __getitem__(self, k):
183 def __getitem__(self, k):
184 v = self._strenv.__getitem__(_bytes2sys(k))
184 v = self._strenv.__getitem__(_bytes2sys(k))
185 return _sys2bytes(v)
185 return _sys2bytes(v)
186
186
187 def __setitem__(self, k, v):
187 def __setitem__(self, k, v):
188 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
188 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
189
189
190 def __delitem__(self, k):
190 def __delitem__(self, k):
191 self._strenv.__delitem__(_bytes2sys(k))
191 self._strenv.__delitem__(_bytes2sys(k))
192
192
193 def __contains__(self, k):
193 def __contains__(self, k):
194 return self._strenv.__contains__(_bytes2sys(k))
194 return self._strenv.__contains__(_bytes2sys(k))
195
195
196 def __iter__(self):
196 def __iter__(self):
197 return iter([_sys2bytes(k) for k in iter(self._strenv)])
197 return iter([_sys2bytes(k) for k in iter(self._strenv)])
198
198
199 def get(self, k, default=None):
199 def get(self, k, default=None):
200 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
200 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
201 return _sys2bytes(v)
201 return _sys2bytes(v)
202
202
203 def pop(self, k, default=None):
203 def pop(self, k, default=None):
204 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
204 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
205 return _sys2bytes(v)
205 return _sys2bytes(v)
206
206
207 osenvironb = environbytes(os.environ)
207 osenvironb = environbytes(os.environ)
208
208
209 getcwdb = getattr(os, 'getcwdb')
209 getcwdb = getattr(os, 'getcwdb')
210 if not getcwdb or WINDOWS:
210 if not getcwdb or WINDOWS:
211 getcwdb = lambda: _sys2bytes(os.getcwd())
211 getcwdb = lambda: _sys2bytes(os.getcwd())
212
212
213 elif sys.version_info >= (3, 0, 0):
213 elif sys.version_info >= (3, 0, 0):
214 print(
214 print(
215 '%s is only supported on Python 3.5+ and 2.7, not %s'
215 '%s is only supported on Python 3.5+ and 2.7, not %s'
216 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
216 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
217 )
217 )
218 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
218 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
219 else:
219 else:
220 PYTHON3 = False
220 PYTHON3 = False
221
221
222 # In python 2.x, path operations are generally done using
222 # In python 2.x, path operations are generally done using
223 # bytestrings by default, so we don't have to do any extra
223 # bytestrings by default, so we don't have to do any extra
224 # fiddling there. We define the wrapper functions anyway just to
224 # fiddling there. We define the wrapper functions anyway just to
225 # help keep code consistent between platforms.
225 # help keep code consistent between platforms.
226 def _sys2bytes(p):
226 def _sys2bytes(p):
227 return p
227 return p
228
228
229 _bytes2sys = _sys2bytes
229 _bytes2sys = _sys2bytes
230 osenvironb = os.environ
230 osenvironb = os.environ
231 getcwdb = os.getcwd
231 getcwdb = os.getcwd
232
232
233 if WINDOWS:
233 if WINDOWS:
234 _getcwdb = getcwdb
234 _getcwdb = getcwdb
235
235
236 def getcwdb():
236 def getcwdb():
237 cwd = _getcwdb()
237 cwd = _getcwdb()
238 if re.match(b'^[a-z]:', cwd):
238 if re.match(b'^[a-z]:', cwd):
239 # os.getcwd() is inconsistent on the capitalization of the drive
239 # os.getcwd() is inconsistent on the capitalization of the drive
240 # letter, so adjust it. see https://bugs.python.org/issue40368
240 # letter, so adjust it. see https://bugs.python.org/issue40368
241 cwd = cwd[0:1].upper() + cwd[1:]
241 cwd = cwd[0:1].upper() + cwd[1:]
242 return cwd
242 return cwd
243
243
244
244
245 # For Windows support
245 # For Windows support
246 wifexited = getattr(os, "WIFEXITED", lambda x: False)
246 wifexited = getattr(os, "WIFEXITED", lambda x: False)
247
247
248 # Whether to use IPv6
248 # Whether to use IPv6
249 def checksocketfamily(name, port=20058):
249 def checksocketfamily(name, port=20058):
250 """return true if we can listen on localhost using family=name
250 """return true if we can listen on localhost using family=name
251
251
252 name should be either 'AF_INET', or 'AF_INET6'.
252 name should be either 'AF_INET', or 'AF_INET6'.
253 port being used is okay - EADDRINUSE is considered as successful.
253 port being used is okay - EADDRINUSE is considered as successful.
254 """
254 """
255 family = getattr(socket, name, None)
255 family = getattr(socket, name, None)
256 if family is None:
256 if family is None:
257 return False
257 return False
258 try:
258 try:
259 s = socket.socket(family, socket.SOCK_STREAM)
259 s = socket.socket(family, socket.SOCK_STREAM)
260 s.bind(('localhost', port))
260 s.bind(('localhost', port))
261 s.close()
261 s.close()
262 return True
262 return True
263 except socket.error as exc:
263 except socket.error as exc:
264 if exc.errno == errno.EADDRINUSE:
264 if exc.errno == errno.EADDRINUSE:
265 return True
265 return True
266 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
266 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
267 return False
267 return False
268 else:
268 else:
269 raise
269 raise
270 else:
270 else:
271 return False
271 return False
272
272
273
273
274 # useipv6 will be set by parseargs
274 # useipv6 will be set by parseargs
275 useipv6 = None
275 useipv6 = None
276
276
277
277
278 def checkportisavailable(port):
278 def checkportisavailable(port):
279 """return true if a port seems free to bind on localhost"""
279 """return true if a port seems free to bind on localhost"""
280 if useipv6:
280 if useipv6:
281 family = socket.AF_INET6
281 family = socket.AF_INET6
282 else:
282 else:
283 family = socket.AF_INET
283 family = socket.AF_INET
284 try:
284 try:
285 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
285 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
286 s.bind(('localhost', port))
286 s.bind(('localhost', port))
287 return True
287 return True
288 except socket.error as exc:
288 except socket.error as exc:
289 if WINDOWS and exc.errno == errno.WSAEACCES:
289 if WINDOWS and exc.errno == errno.WSAEACCES:
290 return False
290 return False
291 elif PYTHON3:
291 elif PYTHON3:
292 # TODO: make a proper exception handler after dropping py2. This
292 # TODO: make a proper exception handler after dropping py2. This
293 # works because socket.error is an alias for OSError on py3,
293 # works because socket.error is an alias for OSError on py3,
294 # which is also the baseclass of PermissionError.
294 # which is also the baseclass of PermissionError.
295 if isinstance(exc, PermissionError):
295 if isinstance(exc, PermissionError):
296 return False
296 return False
297 if exc.errno not in (
297 if exc.errno not in (
298 errno.EADDRINUSE,
298 errno.EADDRINUSE,
299 errno.EADDRNOTAVAIL,
299 errno.EADDRNOTAVAIL,
300 errno.EPROTONOSUPPORT,
300 errno.EPROTONOSUPPORT,
301 ):
301 ):
302 raise
302 raise
303 return False
303 return False
304
304
305
305
306 closefds = os.name == 'posix'
306 closefds = os.name == 'posix'
307
307
308
308
309 def Popen4(cmd, wd, timeout, env=None):
309 def Popen4(cmd, wd, timeout, env=None):
310 processlock.acquire()
310 processlock.acquire()
311 p = subprocess.Popen(
311 p = subprocess.Popen(
312 _bytes2sys(cmd),
312 _bytes2sys(cmd),
313 shell=True,
313 shell=True,
314 bufsize=-1,
314 bufsize=-1,
315 cwd=_bytes2sys(wd),
315 cwd=_bytes2sys(wd),
316 env=env,
316 env=env,
317 close_fds=closefds,
317 close_fds=closefds,
318 stdin=subprocess.PIPE,
318 stdin=subprocess.PIPE,
319 stdout=subprocess.PIPE,
319 stdout=subprocess.PIPE,
320 stderr=subprocess.STDOUT,
320 stderr=subprocess.STDOUT,
321 )
321 )
322 processlock.release()
322 processlock.release()
323
323
324 p.fromchild = p.stdout
324 p.fromchild = p.stdout
325 p.tochild = p.stdin
325 p.tochild = p.stdin
326 p.childerr = p.stderr
326 p.childerr = p.stderr
327
327
328 p.timeout = False
328 p.timeout = False
329 if timeout:
329 if timeout:
330
330
331 def t():
331 def t():
332 start = time.time()
332 start = time.time()
333 while time.time() - start < timeout and p.returncode is None:
333 while time.time() - start < timeout and p.returncode is None:
334 time.sleep(0.1)
334 time.sleep(0.1)
335 p.timeout = True
335 p.timeout = True
336 vlog('# Timout reached for process %d' % p.pid)
336 vlog('# Timout reached for process %d' % p.pid)
337 if p.returncode is None:
337 if p.returncode is None:
338 terminate(p)
338 terminate(p)
339
339
340 threading.Thread(target=t).start()
340 threading.Thread(target=t).start()
341
341
342 return p
342 return p
343
343
344
344
345 if sys.executable:
345 if sys.executable:
346 sysexecutable = sys.executable
346 sysexecutable = sys.executable
347 elif os.environ.get('PYTHONEXECUTABLE'):
347 elif os.environ.get('PYTHONEXECUTABLE'):
348 sysexecutable = os.environ['PYTHONEXECUTABLE']
348 sysexecutable = os.environ['PYTHONEXECUTABLE']
349 elif os.environ.get('PYTHON'):
349 elif os.environ.get('PYTHON'):
350 sysexecutable = os.environ['PYTHON']
350 sysexecutable = os.environ['PYTHON']
351 else:
351 else:
352 raise AssertionError('Could not find Python interpreter')
352 raise AssertionError('Could not find Python interpreter')
353
353
354 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
354 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
355 IMPL_PATH = b'PYTHONPATH'
355 IMPL_PATH = b'PYTHONPATH'
356 if 'java' in sys.platform:
356 if 'java' in sys.platform:
357 IMPL_PATH = b'JYTHONPATH'
357 IMPL_PATH = b'JYTHONPATH'
358
358
359 default_defaults = {
359 default_defaults = {
360 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
360 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
361 'timeout': ('HGTEST_TIMEOUT', 360),
361 'timeout': ('HGTEST_TIMEOUT', 360),
362 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
362 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
363 'port': ('HGTEST_PORT', 20059),
363 'port': ('HGTEST_PORT', 20059),
364 'shell': ('HGTEST_SHELL', 'sh'),
364 'shell': ('HGTEST_SHELL', 'sh'),
365 }
365 }
366
366
367 defaults = default_defaults.copy()
367 defaults = default_defaults.copy()
368
368
369
369
370 def canonpath(path):
370 def canonpath(path):
371 return os.path.realpath(os.path.expanduser(path))
371 return os.path.realpath(os.path.expanduser(path))
372
372
373
373
374 def which(exe):
374 def which(exe):
375 if PYTHON3:
375 if PYTHON3:
376 # shutil.which only accept bytes from 3.8
376 # shutil.which only accept bytes from 3.8
377 cmd = _bytes2sys(exe)
377 cmd = _bytes2sys(exe)
378 real_exec = shutil.which(cmd)
378 real_exec = shutil.which(cmd)
379 return _sys2bytes(real_exec)
379 return _sys2bytes(real_exec)
380 else:
380 else:
381 # let us do the os work
381 # let us do the os work
382 for p in osenvironb[b'PATH'].split(os.pathsep):
382 for p in osenvironb[b'PATH'].split(os.pathsep):
383 f = os.path.join(p, exe)
383 f = os.path.join(p, exe)
384 if os.path.isfile(f):
384 if os.path.isfile(f):
385 return f
385 return f
386 return None
386 return None
387
387
388
388
389 def parselistfiles(files, listtype, warn=True):
389 def parselistfiles(files, listtype, warn=True):
390 entries = dict()
390 entries = dict()
391 for filename in files:
391 for filename in files:
392 try:
392 try:
393 path = os.path.expanduser(os.path.expandvars(filename))
393 path = os.path.expanduser(os.path.expandvars(filename))
394 f = open(path, "rb")
394 f = open(path, "rb")
395 except IOError as err:
395 except IOError as err:
396 if err.errno != errno.ENOENT:
396 if err.errno != errno.ENOENT:
397 raise
397 raise
398 if warn:
398 if warn:
399 print("warning: no such %s file: %s" % (listtype, filename))
399 print("warning: no such %s file: %s" % (listtype, filename))
400 continue
400 continue
401
401
402 for line in f.readlines():
402 for line in f.readlines():
403 line = line.split(b'#', 1)[0].strip()
403 line = line.split(b'#', 1)[0].strip()
404 if line:
404 if line:
405 # Ensure path entries are compatible with os.path.relpath()
405 # Ensure path entries are compatible with os.path.relpath()
406 entries[os.path.normpath(line)] = filename
406 entries[os.path.normpath(line)] = filename
407
407
408 f.close()
408 f.close()
409 return entries
409 return entries
410
410
411
411
412 def parsettestcases(path):
412 def parsettestcases(path):
413 """read a .t test file, return a set of test case names
413 """read a .t test file, return a set of test case names
414
414
415 If path does not exist, return an empty set.
415 If path does not exist, return an empty set.
416 """
416 """
417 cases = []
417 cases = []
418 try:
418 try:
419 with open(path, 'rb') as f:
419 with open(path, 'rb') as f:
420 for l in f:
420 for l in f:
421 if l.startswith(b'#testcases '):
421 if l.startswith(b'#testcases '):
422 cases.append(sorted(l[11:].split()))
422 cases.append(sorted(l[11:].split()))
423 except IOError as ex:
423 except IOError as ex:
424 if ex.errno != errno.ENOENT:
424 if ex.errno != errno.ENOENT:
425 raise
425 raise
426 return cases
426 return cases
427
427
428
428
429 def getparser():
429 def getparser():
430 """Obtain the OptionParser used by the CLI."""
430 """Obtain the OptionParser used by the CLI."""
431 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
431 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
432
432
433 selection = parser.add_argument_group('Test Selection')
433 selection = parser.add_argument_group('Test Selection')
434 selection.add_argument(
434 selection.add_argument(
435 '--allow-slow-tests',
435 '--allow-slow-tests',
436 action='store_true',
436 action='store_true',
437 help='allow extremely slow tests',
437 help='allow extremely slow tests',
438 )
438 )
439 selection.add_argument(
439 selection.add_argument(
440 "--blacklist",
440 "--blacklist",
441 action="append",
441 action="append",
442 help="skip tests listed in the specified blacklist file",
442 help="skip tests listed in the specified blacklist file",
443 )
443 )
444 selection.add_argument(
444 selection.add_argument(
445 "--changed",
445 "--changed",
446 help="run tests that are changed in parent rev or working directory",
446 help="run tests that are changed in parent rev or working directory",
447 )
447 )
448 selection.add_argument(
448 selection.add_argument(
449 "-k", "--keywords", help="run tests matching keywords"
449 "-k", "--keywords", help="run tests matching keywords"
450 )
450 )
451 selection.add_argument(
451 selection.add_argument(
452 "-r", "--retest", action="store_true", help="retest failed tests"
452 "-r", "--retest", action="store_true", help="retest failed tests"
453 )
453 )
454 selection.add_argument(
454 selection.add_argument(
455 "--test-list",
455 "--test-list",
456 action="append",
456 action="append",
457 help="read tests to run from the specified file",
457 help="read tests to run from the specified file",
458 )
458 )
459 selection.add_argument(
459 selection.add_argument(
460 "--whitelist",
460 "--whitelist",
461 action="append",
461 action="append",
462 help="always run tests listed in the specified whitelist file",
462 help="always run tests listed in the specified whitelist file",
463 )
463 )
464 selection.add_argument(
464 selection.add_argument(
465 'tests', metavar='TESTS', nargs='*', help='Tests to run'
465 'tests', metavar='TESTS', nargs='*', help='Tests to run'
466 )
466 )
467
467
468 harness = parser.add_argument_group('Test Harness Behavior')
468 harness = parser.add_argument_group('Test Harness Behavior')
469 harness.add_argument(
469 harness.add_argument(
470 '--bisect-repo',
470 '--bisect-repo',
471 metavar='bisect_repo',
471 metavar='bisect_repo',
472 help=(
472 help=(
473 "Path of a repo to bisect. Use together with " "--known-good-rev"
473 "Path of a repo to bisect. Use together with " "--known-good-rev"
474 ),
474 ),
475 )
475 )
476 harness.add_argument(
476 harness.add_argument(
477 "-d",
477 "-d",
478 "--debug",
478 "--debug",
479 action="store_true",
479 action="store_true",
480 help="debug mode: write output of test scripts to console"
480 help="debug mode: write output of test scripts to console"
481 " rather than capturing and diffing it (disables timeout)",
481 " rather than capturing and diffing it (disables timeout)",
482 )
482 )
483 harness.add_argument(
483 harness.add_argument(
484 "-f",
484 "-f",
485 "--first",
485 "--first",
486 action="store_true",
486 action="store_true",
487 help="exit on the first test failure",
487 help="exit on the first test failure",
488 )
488 )
489 harness.add_argument(
489 harness.add_argument(
490 "-i",
490 "-i",
491 "--interactive",
491 "--interactive",
492 action="store_true",
492 action="store_true",
493 help="prompt to accept changed output",
493 help="prompt to accept changed output",
494 )
494 )
495 harness.add_argument(
495 harness.add_argument(
496 "-j",
496 "-j",
497 "--jobs",
497 "--jobs",
498 type=int,
498 type=int,
499 help="number of jobs to run in parallel"
499 help="number of jobs to run in parallel"
500 " (default: $%s or %d)" % defaults['jobs'],
500 " (default: $%s or %d)" % defaults['jobs'],
501 )
501 )
502 harness.add_argument(
502 harness.add_argument(
503 "--keep-tmpdir",
503 "--keep-tmpdir",
504 action="store_true",
504 action="store_true",
505 help="keep temporary directory after running tests",
505 help="keep temporary directory after running tests",
506 )
506 )
507 harness.add_argument(
507 harness.add_argument(
508 '--known-good-rev',
508 '--known-good-rev',
509 metavar="known_good_rev",
509 metavar="known_good_rev",
510 help=(
510 help=(
511 "Automatically bisect any failures using this "
511 "Automatically bisect any failures using this "
512 "revision as a known-good revision."
512 "revision as a known-good revision."
513 ),
513 ),
514 )
514 )
515 harness.add_argument(
515 harness.add_argument(
516 "--list-tests",
516 "--list-tests",
517 action="store_true",
517 action="store_true",
518 help="list tests instead of running them",
518 help="list tests instead of running them",
519 )
519 )
520 harness.add_argument(
520 harness.add_argument(
521 "--loop", action="store_true", help="loop tests repeatedly"
521 "--loop", action="store_true", help="loop tests repeatedly"
522 )
522 )
523 harness.add_argument(
523 harness.add_argument(
524 '--random', action="store_true", help='run tests in random order'
524 '--random', action="store_true", help='run tests in random order'
525 )
525 )
526 harness.add_argument(
526 harness.add_argument(
527 '--order-by-runtime',
527 '--order-by-runtime',
528 action="store_true",
528 action="store_true",
529 help='run slowest tests first, according to .testtimes',
529 help='run slowest tests first, according to .testtimes',
530 )
530 )
531 harness.add_argument(
531 harness.add_argument(
532 "-p",
532 "-p",
533 "--port",
533 "--port",
534 type=int,
534 type=int,
535 help="port on which servers should listen"
535 help="port on which servers should listen"
536 " (default: $%s or %d)" % defaults['port'],
536 " (default: $%s or %d)" % defaults['port'],
537 )
537 )
538 harness.add_argument(
538 harness.add_argument(
539 '--profile-runner',
539 '--profile-runner',
540 action='store_true',
540 action='store_true',
541 help='run statprof on run-tests',
541 help='run statprof on run-tests',
542 )
542 )
543 harness.add_argument(
543 harness.add_argument(
544 "-R", "--restart", action="store_true", help="restart at last error"
544 "-R", "--restart", action="store_true", help="restart at last error"
545 )
545 )
546 harness.add_argument(
546 harness.add_argument(
547 "--runs-per-test",
547 "--runs-per-test",
548 type=int,
548 type=int,
549 dest="runs_per_test",
549 dest="runs_per_test",
550 help="run each test N times (default=1)",
550 help="run each test N times (default=1)",
551 default=1,
551 default=1,
552 )
552 )
553 harness.add_argument(
553 harness.add_argument(
554 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
554 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
555 )
555 )
556 harness.add_argument(
556 harness.add_argument(
557 '--showchannels', action='store_true', help='show scheduling channels'
557 '--showchannels', action='store_true', help='show scheduling channels'
558 )
558 )
559 harness.add_argument(
559 harness.add_argument(
560 "--slowtimeout",
560 "--slowtimeout",
561 type=int,
561 type=int,
562 help="kill errant slow tests after SLOWTIMEOUT seconds"
562 help="kill errant slow tests after SLOWTIMEOUT seconds"
563 " (default: $%s or %d)" % defaults['slowtimeout'],
563 " (default: $%s or %d)" % defaults['slowtimeout'],
564 )
564 )
565 harness.add_argument(
565 harness.add_argument(
566 "-t",
566 "-t",
567 "--timeout",
567 "--timeout",
568 type=int,
568 type=int,
569 help="kill errant tests after TIMEOUT seconds"
569 help="kill errant tests after TIMEOUT seconds"
570 " (default: $%s or %d)" % defaults['timeout'],
570 " (default: $%s or %d)" % defaults['timeout'],
571 )
571 )
572 harness.add_argument(
572 harness.add_argument(
573 "--tmpdir",
573 "--tmpdir",
574 help="run tests in the given temporary directory"
574 help="run tests in the given temporary directory"
575 " (implies --keep-tmpdir)",
575 " (implies --keep-tmpdir)",
576 )
576 )
577 harness.add_argument(
577 harness.add_argument(
578 "-v", "--verbose", action="store_true", help="output verbose messages"
578 "-v", "--verbose", action="store_true", help="output verbose messages"
579 )
579 )
580
580
581 hgconf = parser.add_argument_group('Mercurial Configuration')
581 hgconf = parser.add_argument_group('Mercurial Configuration')
582 hgconf.add_argument(
582 hgconf.add_argument(
583 "--chg",
583 "--chg",
584 action="store_true",
584 action="store_true",
585 help="install and use chg wrapper in place of hg",
585 help="install and use chg wrapper in place of hg",
586 )
586 )
587 hgconf.add_argument(
587 hgconf.add_argument(
588 "--chg-debug",
588 "--chg-debug",
589 action="store_true",
589 action="store_true",
590 help="show chg debug logs",
590 help="show chg debug logs",
591 )
591 )
592 hgconf.add_argument(
592 hgconf.add_argument(
593 "--rhg",
593 "--rhg",
594 action="store_true",
594 action="store_true",
595 help="install and use rhg Rust implementation in place of hg",
595 help="install and use rhg Rust implementation in place of hg",
596 )
596 )
597 hgconf.add_argument(
597 hgconf.add_argument(
598 "--pyoxidized",
598 "--pyoxidized",
599 action="store_true",
599 action="store_true",
600 help="build the hg binary using pyoxidizer",
600 help="build the hg binary using pyoxidizer",
601 )
601 )
602 hgconf.add_argument("--compiler", help="compiler to build with")
602 hgconf.add_argument("--compiler", help="compiler to build with")
603 hgconf.add_argument(
603 hgconf.add_argument(
604 '--extra-config-opt',
604 '--extra-config-opt',
605 action="append",
605 action="append",
606 default=[],
606 default=[],
607 help='set the given config opt in the test hgrc',
607 help='set the given config opt in the test hgrc',
608 )
608 )
609 hgconf.add_argument(
609 hgconf.add_argument(
610 "-l",
610 "-l",
611 "--local",
611 "--local",
612 action="store_true",
612 action="store_true",
613 help="shortcut for --with-hg=<testdir>/../hg, "
613 help="shortcut for --with-hg=<testdir>/../hg, "
614 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
614 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
615 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
615 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
616 )
616 )
617 hgconf.add_argument(
617 hgconf.add_argument(
618 "--ipv6",
618 "--ipv6",
619 action="store_true",
619 action="store_true",
620 help="prefer IPv6 to IPv4 for network related tests",
620 help="prefer IPv6 to IPv4 for network related tests",
621 )
621 )
622 hgconf.add_argument(
622 hgconf.add_argument(
623 "--pure",
623 "--pure",
624 action="store_true",
624 action="store_true",
625 help="use pure Python code instead of C extensions",
625 help="use pure Python code instead of C extensions",
626 )
626 )
627 hgconf.add_argument(
627 hgconf.add_argument(
628 "--rust",
628 "--rust",
629 action="store_true",
629 action="store_true",
630 help="use Rust code alongside C extensions",
630 help="use Rust code alongside C extensions",
631 )
631 )
632 hgconf.add_argument(
632 hgconf.add_argument(
633 "--no-rust",
633 "--no-rust",
634 action="store_true",
634 action="store_true",
635 help="do not use Rust code even if compiled",
635 help="do not use Rust code even if compiled",
636 )
636 )
637 hgconf.add_argument(
637 hgconf.add_argument(
638 "--with-chg",
638 "--with-chg",
639 metavar="CHG",
639 metavar="CHG",
640 help="use specified chg wrapper in place of hg",
640 help="use specified chg wrapper in place of hg",
641 )
641 )
642 hgconf.add_argument(
642 hgconf.add_argument(
643 "--with-rhg",
643 "--with-rhg",
644 metavar="RHG",
644 metavar="RHG",
645 help="use specified rhg Rust implementation in place of hg",
645 help="use specified rhg Rust implementation in place of hg",
646 )
646 )
647 hgconf.add_argument(
647 hgconf.add_argument(
648 "--with-hg",
648 "--with-hg",
649 metavar="HG",
649 metavar="HG",
650 help="test using specified hg script rather than a "
650 help="test using specified hg script rather than a "
651 "temporary installation",
651 "temporary installation",
652 )
652 )
653
653
654 reporting = parser.add_argument_group('Results Reporting')
654 reporting = parser.add_argument_group('Results Reporting')
655 reporting.add_argument(
655 reporting.add_argument(
656 "-C",
656 "-C",
657 "--annotate",
657 "--annotate",
658 action="store_true",
658 action="store_true",
659 help="output files annotated with coverage",
659 help="output files annotated with coverage",
660 )
660 )
661 reporting.add_argument(
661 reporting.add_argument(
662 "--color",
662 "--color",
663 choices=["always", "auto", "never"],
663 choices=["always", "auto", "never"],
664 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
664 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
665 help="colorisation: always|auto|never (default: auto)",
665 help="colorisation: always|auto|never (default: auto)",
666 )
666 )
667 reporting.add_argument(
667 reporting.add_argument(
668 "-c",
668 "-c",
669 "--cover",
669 "--cover",
670 action="store_true",
670 action="store_true",
671 help="print a test coverage report",
671 help="print a test coverage report",
672 )
672 )
673 reporting.add_argument(
673 reporting.add_argument(
674 '--exceptions',
674 '--exceptions',
675 action='store_true',
675 action='store_true',
676 help='log all exceptions and generate an exception report',
676 help='log all exceptions and generate an exception report',
677 )
677 )
678 reporting.add_argument(
678 reporting.add_argument(
679 "-H",
679 "-H",
680 "--htmlcov",
680 "--htmlcov",
681 action="store_true",
681 action="store_true",
682 help="create an HTML report of the coverage of the files",
682 help="create an HTML report of the coverage of the files",
683 )
683 )
684 reporting.add_argument(
684 reporting.add_argument(
685 "--json",
685 "--json",
686 action="store_true",
686 action="store_true",
687 help="store test result data in 'report.json' file",
687 help="store test result data in 'report.json' file",
688 )
688 )
689 reporting.add_argument(
689 reporting.add_argument(
690 "--outputdir",
690 "--outputdir",
691 help="directory to write error logs to (default=test directory)",
691 help="directory to write error logs to (default=test directory)",
692 )
692 )
693 reporting.add_argument(
693 reporting.add_argument(
694 "-n", "--nodiff", action="store_true", help="skip showing test changes"
694 "-n", "--nodiff", action="store_true", help="skip showing test changes"
695 )
695 )
696 reporting.add_argument(
696 reporting.add_argument(
697 "-S",
697 "-S",
698 "--noskips",
698 "--noskips",
699 action="store_true",
699 action="store_true",
700 help="don't report skip tests verbosely",
700 help="don't report skip tests verbosely",
701 )
701 )
702 reporting.add_argument(
702 reporting.add_argument(
703 "--time", action="store_true", help="time how long each test takes"
703 "--time", action="store_true", help="time how long each test takes"
704 )
704 )
705 reporting.add_argument("--view", help="external diff viewer")
705 reporting.add_argument("--view", help="external diff viewer")
706 reporting.add_argument(
706 reporting.add_argument(
707 "--xunit", help="record xunit results at specified path"
707 "--xunit", help="record xunit results at specified path"
708 )
708 )
709
709
710 for option, (envvar, default) in defaults.items():
710 for option, (envvar, default) in defaults.items():
711 defaults[option] = type(default)(os.environ.get(envvar, default))
711 defaults[option] = type(default)(os.environ.get(envvar, default))
712 parser.set_defaults(**defaults)
712 parser.set_defaults(**defaults)
713
713
714 return parser
714 return parser
715
715
716
716
717 def parseargs(args, parser):
717 def parseargs(args, parser):
718 """Parse arguments with our OptionParser and validate results."""
718 """Parse arguments with our OptionParser and validate results."""
719 options = parser.parse_args(args)
719 options = parser.parse_args(args)
720
720
721 # jython is always pure
721 # jython is always pure
722 if 'java' in sys.platform or '__pypy__' in sys.modules:
722 if 'java' in sys.platform or '__pypy__' in sys.modules:
723 options.pure = True
723 options.pure = True
724
724
725 if platform.python_implementation() != 'CPython' and options.rust:
725 if platform.python_implementation() != 'CPython' and options.rust:
726 parser.error('Rust extensions are only available with CPython')
726 parser.error('Rust extensions are only available with CPython')
727
727
728 if options.pure and options.rust:
728 if options.pure and options.rust:
729 parser.error('--rust cannot be used with --pure')
729 parser.error('--rust cannot be used with --pure')
730
730
731 if options.rust and options.no_rust:
731 if options.rust and options.no_rust:
732 parser.error('--rust cannot be used with --no-rust')
732 parser.error('--rust cannot be used with --no-rust')
733
733
734 if options.local:
734 if options.local:
735 if options.with_hg or options.with_rhg or options.with_chg:
735 if options.with_hg or options.with_rhg or options.with_chg:
736 parser.error(
736 parser.error(
737 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
737 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
738 )
738 )
739 if options.pyoxidized:
739 if options.pyoxidized:
740 parser.error('--pyoxidized does not work with --local (yet)')
740 parser.error('--pyoxidized does not work with --local (yet)')
741 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
741 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
742 reporootdir = os.path.dirname(testdir)
742 reporootdir = os.path.dirname(testdir)
743 pathandattrs = [(b'hg', 'with_hg')]
743 pathandattrs = [(b'hg', 'with_hg')]
744 if options.chg:
744 if options.chg:
745 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
745 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
746 if options.rhg:
746 if options.rhg:
747 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
747 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
748 for relpath, attr in pathandattrs:
748 for relpath, attr in pathandattrs:
749 binpath = os.path.join(reporootdir, relpath)
749 binpath = os.path.join(reporootdir, relpath)
750 if not (WINDOWS or os.access(binpath, os.X_OK)):
750 if not (WINDOWS or os.access(binpath, os.X_OK)):
751 parser.error(
751 parser.error(
752 '--local specified, but %r not found or '
752 '--local specified, but %r not found or '
753 'not executable' % binpath
753 'not executable' % binpath
754 )
754 )
755 setattr(options, attr, _bytes2sys(binpath))
755 setattr(options, attr, _bytes2sys(binpath))
756
756
757 if options.with_hg:
757 if options.with_hg:
758 options.with_hg = canonpath(_sys2bytes(options.with_hg))
758 options.with_hg = canonpath(_sys2bytes(options.with_hg))
759 if not (
759 if not (
760 os.path.isfile(options.with_hg)
760 os.path.isfile(options.with_hg)
761 and os.access(options.with_hg, os.X_OK)
761 and os.access(options.with_hg, os.X_OK)
762 ):
762 ):
763 parser.error('--with-hg must specify an executable hg script')
763 parser.error('--with-hg must specify an executable hg script')
764 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
764 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
765 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
765 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
766 msg %= _bytes2sys(os.path.basename(options.with_hg))
766 msg %= _bytes2sys(os.path.basename(options.with_hg))
767 sys.stderr.write(msg)
767 sys.stderr.write(msg)
768 sys.stderr.flush()
768 sys.stderr.flush()
769
769
770 if (options.chg or options.with_chg) and WINDOWS:
770 if (options.chg or options.with_chg) and WINDOWS:
771 parser.error('chg does not work on %s' % os.name)
771 parser.error('chg does not work on %s' % os.name)
772 if (options.rhg or options.with_rhg) and WINDOWS:
772 if (options.rhg or options.with_rhg) and WINDOWS:
773 parser.error('rhg does not work on %s' % os.name)
773 parser.error('rhg does not work on %s' % os.name)
774 if options.pyoxidized and not WINDOWS:
774 if options.pyoxidized and not WINDOWS:
775 parser.error('--pyoxidized is currently Windows only')
775 parser.error('--pyoxidized is currently Windows only')
776 if options.with_chg:
776 if options.with_chg:
777 options.chg = False # no installation to temporary location
777 options.chg = False # no installation to temporary location
778 options.with_chg = canonpath(_sys2bytes(options.with_chg))
778 options.with_chg = canonpath(_sys2bytes(options.with_chg))
779 if not (
779 if not (
780 os.path.isfile(options.with_chg)
780 os.path.isfile(options.with_chg)
781 and os.access(options.with_chg, os.X_OK)
781 and os.access(options.with_chg, os.X_OK)
782 ):
782 ):
783 parser.error('--with-chg must specify a chg executable')
783 parser.error('--with-chg must specify a chg executable')
784 if options.with_rhg:
784 if options.with_rhg:
785 options.rhg = False # no installation to temporary location
785 options.rhg = False # no installation to temporary location
786 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
786 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
787 if not (
787 if not (
788 os.path.isfile(options.with_rhg)
788 os.path.isfile(options.with_rhg)
789 and os.access(options.with_rhg, os.X_OK)
789 and os.access(options.with_rhg, os.X_OK)
790 ):
790 ):
791 parser.error('--with-rhg must specify a rhg executable')
791 parser.error('--with-rhg must specify a rhg executable')
792 if options.chg and options.with_hg:
792 if options.chg and options.with_hg:
793 # chg shares installation location with hg
793 # chg shares installation location with hg
794 parser.error(
794 parser.error(
795 '--chg does not work when --with-hg is specified '
795 '--chg does not work when --with-hg is specified '
796 '(use --with-chg instead)'
796 '(use --with-chg instead)'
797 )
797 )
798 if options.rhg and options.with_hg:
798 if options.rhg and options.with_hg:
799 # rhg shares installation location with hg
799 # rhg shares installation location with hg
800 parser.error(
800 parser.error(
801 '--rhg does not work when --with-hg is specified '
801 '--rhg does not work when --with-hg is specified '
802 '(use --with-rhg instead)'
802 '(use --with-rhg instead)'
803 )
803 )
804 if options.rhg and options.chg:
804 if options.rhg and options.chg:
805 parser.error('--rhg and --chg do not work together')
805 parser.error('--rhg and --chg do not work together')
806
806
807 if options.color == 'always' and not pygmentspresent:
807 if options.color == 'always' and not pygmentspresent:
808 sys.stderr.write(
808 sys.stderr.write(
809 'warning: --color=always ignored because '
809 'warning: --color=always ignored because '
810 'pygments is not installed\n'
810 'pygments is not installed\n'
811 )
811 )
812
812
813 if options.bisect_repo and not options.known_good_rev:
813 if options.bisect_repo and not options.known_good_rev:
814 parser.error("--bisect-repo cannot be used without --known-good-rev")
814 parser.error("--bisect-repo cannot be used without --known-good-rev")
815
815
816 global useipv6
816 global useipv6
817 if options.ipv6:
817 if options.ipv6:
818 useipv6 = checksocketfamily('AF_INET6')
818 useipv6 = checksocketfamily('AF_INET6')
819 else:
819 else:
820 # only use IPv6 if IPv4 is unavailable and IPv6 is available
820 # only use IPv6 if IPv4 is unavailable and IPv6 is available
821 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
821 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
822 'AF_INET6'
822 'AF_INET6'
823 )
823 )
824
824
825 options.anycoverage = options.cover or options.annotate or options.htmlcov
825 options.anycoverage = options.cover or options.annotate or options.htmlcov
826 if options.anycoverage:
826 if options.anycoverage:
827 try:
827 try:
828 import coverage
828 import coverage
829
829
830 covver = version.StrictVersion(coverage.__version__).version
830 covver = version.StrictVersion(coverage.__version__).version
831 if covver < (3, 3):
831 if covver < (3, 3):
832 parser.error('coverage options require coverage 3.3 or later')
832 parser.error('coverage options require coverage 3.3 or later')
833 except ImportError:
833 except ImportError:
834 parser.error('coverage options now require the coverage package')
834 parser.error('coverage options now require the coverage package')
835
835
836 if options.anycoverage and options.local:
836 if options.anycoverage and options.local:
837 # this needs some path mangling somewhere, I guess
837 # this needs some path mangling somewhere, I guess
838 parser.error(
838 parser.error(
839 "sorry, coverage options do not work when --local " "is specified"
839 "sorry, coverage options do not work when --local " "is specified"
840 )
840 )
841
841
842 if options.anycoverage and options.with_hg:
842 if options.anycoverage and options.with_hg:
843 parser.error(
843 parser.error(
844 "sorry, coverage options do not work when --with-hg " "is specified"
844 "sorry, coverage options do not work when --with-hg " "is specified"
845 )
845 )
846
846
847 global verbose
847 global verbose
848 if options.verbose:
848 if options.verbose:
849 verbose = ''
849 verbose = ''
850
850
851 if options.tmpdir:
851 if options.tmpdir:
852 options.tmpdir = canonpath(options.tmpdir)
852 options.tmpdir = canonpath(options.tmpdir)
853
853
854 if options.jobs < 1:
854 if options.jobs < 1:
855 parser.error('--jobs must be positive')
855 parser.error('--jobs must be positive')
856 if options.interactive and options.debug:
856 if options.interactive and options.debug:
857 parser.error("-i/--interactive and -d/--debug are incompatible")
857 parser.error("-i/--interactive and -d/--debug are incompatible")
858 if options.debug:
858 if options.debug:
859 if options.timeout != defaults['timeout']:
859 if options.timeout != defaults['timeout']:
860 sys.stderr.write('warning: --timeout option ignored with --debug\n')
860 sys.stderr.write('warning: --timeout option ignored with --debug\n')
861 if options.slowtimeout != defaults['slowtimeout']:
861 if options.slowtimeout != defaults['slowtimeout']:
862 sys.stderr.write(
862 sys.stderr.write(
863 'warning: --slowtimeout option ignored with --debug\n'
863 'warning: --slowtimeout option ignored with --debug\n'
864 )
864 )
865 options.timeout = 0
865 options.timeout = 0
866 options.slowtimeout = 0
866 options.slowtimeout = 0
867
867
868 if options.blacklist:
868 if options.blacklist:
869 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
869 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
870 if options.whitelist:
870 if options.whitelist:
871 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
871 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
872 else:
872 else:
873 options.whitelisted = {}
873 options.whitelisted = {}
874
874
875 if options.showchannels:
875 if options.showchannels:
876 options.nodiff = True
876 options.nodiff = True
877
877
878 return options
878 return options
879
879
880
880
881 def rename(src, dst):
881 def rename(src, dst):
882 """Like os.rename(), trade atomicity and opened files friendliness
882 """Like os.rename(), trade atomicity and opened files friendliness
883 for existing destination support.
883 for existing destination support.
884 """
884 """
885 shutil.copy(src, dst)
885 shutil.copy(src, dst)
886 os.remove(src)
886 os.remove(src)
887
887
888
888
889 def makecleanable(path):
889 def makecleanable(path):
890 """Try to fix directory permission recursively so that the entire tree
890 """Try to fix directory permission recursively so that the entire tree
891 can be deleted"""
891 can be deleted"""
892 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
892 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
893 for d in dirnames:
893 for d in dirnames:
894 p = os.path.join(dirpath, d)
894 p = os.path.join(dirpath, d)
895 try:
895 try:
896 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
896 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
897 except OSError:
897 except OSError:
898 pass
898 pass
899
899
900
900
901 _unified_diff = difflib.unified_diff
901 _unified_diff = difflib.unified_diff
902 if PYTHON3:
902 if PYTHON3:
903 import functools
903 import functools
904
904
905 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
905 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
906
906
907
907
908 def getdiff(expected, output, ref, err):
908 def getdiff(expected, output, ref, err):
909 servefail = False
909 servefail = False
910 lines = []
910 lines = []
911 for line in _unified_diff(expected, output, ref, err):
911 for line in _unified_diff(expected, output, ref, err):
912 if line.startswith(b'+++') or line.startswith(b'---'):
912 if line.startswith(b'+++') or line.startswith(b'---'):
913 line = line.replace(b'\\', b'/')
913 line = line.replace(b'\\', b'/')
914 if line.endswith(b' \n'):
914 if line.endswith(b' \n'):
915 line = line[:-2] + b'\n'
915 line = line[:-2] + b'\n'
916 lines.append(line)
916 lines.append(line)
917 if not servefail and line.startswith(
917 if not servefail and line.startswith(
918 b'+ abort: child process failed to start'
918 b'+ abort: child process failed to start'
919 ):
919 ):
920 servefail = True
920 servefail = True
921
921
922 return servefail, lines
922 return servefail, lines
923
923
924
924
925 verbose = False
925 verbose = False
926
926
927
927
928 def vlog(*msg):
928 def vlog(*msg):
929 """Log only when in verbose mode."""
929 """Log only when in verbose mode."""
930 if verbose is False:
930 if verbose is False:
931 return
931 return
932
932
933 return log(*msg)
933 return log(*msg)
934
934
935
935
936 # Bytes that break XML even in a CDATA block: control characters 0-31
936 # Bytes that break XML even in a CDATA block: control characters 0-31
937 # sans \t, \n and \r
937 # sans \t, \n and \r
938 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
938 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
939
939
940 # Match feature conditionalized output lines in the form, capturing the feature
940 # Match feature conditionalized output lines in the form, capturing the feature
941 # list in group 2, and the preceeding line output in group 1:
941 # list in group 2, and the preceeding line output in group 1:
942 #
942 #
943 # output..output (feature !)\n
943 # output..output (feature !)\n
944 optline = re.compile(br'(.*) \((.+?) !\)\n$')
944 optline = re.compile(br'(.*) \((.+?) !\)\n$')
945
945
946
946
947 def cdatasafe(data):
947 def cdatasafe(data):
948 """Make a string safe to include in a CDATA block.
948 """Make a string safe to include in a CDATA block.
949
949
950 Certain control characters are illegal in a CDATA block, and
950 Certain control characters are illegal in a CDATA block, and
951 there's no way to include a ]]> in a CDATA either. This function
951 there's no way to include a ]]> in a CDATA either. This function
952 replaces illegal bytes with ? and adds a space between the ]] so
952 replaces illegal bytes with ? and adds a space between the ]] so
953 that it won't break the CDATA block.
953 that it won't break the CDATA block.
954 """
954 """
955 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
955 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
956
956
957
957
958 def log(*msg):
958 def log(*msg):
959 """Log something to stdout.
959 """Log something to stdout.
960
960
961 Arguments are strings to print.
961 Arguments are strings to print.
962 """
962 """
963 with iolock:
963 with iolock:
964 if verbose:
964 if verbose:
965 print(verbose, end=' ')
965 print(verbose, end=' ')
966 for m in msg:
966 for m in msg:
967 print(m, end=' ')
967 print(m, end=' ')
968 print()
968 print()
969 sys.stdout.flush()
969 sys.stdout.flush()
970
970
971
971
972 def highlightdiff(line, color):
972 def highlightdiff(line, color):
973 if not color:
973 if not color:
974 return line
974 return line
975 assert pygmentspresent
975 assert pygmentspresent
976 return pygments.highlight(
976 return pygments.highlight(
977 line.decode('latin1'), difflexer, terminal256formatter
977 line.decode('latin1'), difflexer, terminal256formatter
978 ).encode('latin1')
978 ).encode('latin1')
979
979
980
980
981 def highlightmsg(msg, color):
981 def highlightmsg(msg, color):
982 if not color:
982 if not color:
983 return msg
983 return msg
984 assert pygmentspresent
984 assert pygmentspresent
985 return pygments.highlight(msg, runnerlexer, runnerformatter)
985 return pygments.highlight(msg, runnerlexer, runnerformatter)
986
986
987
987
988 def terminate(proc):
988 def terminate(proc):
989 """Terminate subprocess"""
989 """Terminate subprocess"""
990 vlog('# Terminating process %d' % proc.pid)
990 vlog('# Terminating process %d' % proc.pid)
991 try:
991 try:
992 proc.terminate()
992 proc.terminate()
993 except OSError:
993 except OSError:
994 pass
994 pass
995
995
996
996
997 def killdaemons(pidfile):
997 def killdaemons(pidfile):
998 import killdaemons as killmod
998 import killdaemons as killmod
999
999
1000 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
1000 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
1001
1001
1002
1002
1003 class Test(unittest.TestCase):
1003 class Test(unittest.TestCase):
1004 """Encapsulates a single, runnable test.
1004 """Encapsulates a single, runnable test.
1005
1005
1006 While this class conforms to the unittest.TestCase API, it differs in that
1006 While this class conforms to the unittest.TestCase API, it differs in that
1007 instances need to be instantiated manually. (Typically, unittest.TestCase
1007 instances need to be instantiated manually. (Typically, unittest.TestCase
1008 classes are instantiated automatically by scanning modules.)
1008 classes are instantiated automatically by scanning modules.)
1009 """
1009 """
1010
1010
1011 # Status code reserved for skipped tests (used by hghave).
1011 # Status code reserved for skipped tests (used by hghave).
1012 SKIPPED_STATUS = 80
1012 SKIPPED_STATUS = 80
1013
1013
1014 def __init__(
1014 def __init__(
1015 self,
1015 self,
1016 path,
1016 path,
1017 outputdir,
1017 outputdir,
1018 tmpdir,
1018 tmpdir,
1019 keeptmpdir=False,
1019 keeptmpdir=False,
1020 debug=False,
1020 debug=False,
1021 first=False,
1021 first=False,
1022 timeout=None,
1022 timeout=None,
1023 startport=None,
1023 startport=None,
1024 extraconfigopts=None,
1024 extraconfigopts=None,
1025 shell=None,
1025 shell=None,
1026 hgcommand=None,
1026 hgcommand=None,
1027 slowtimeout=None,
1027 slowtimeout=None,
1028 usechg=False,
1028 usechg=False,
1029 chgdebug=False,
1029 chgdebug=False,
1030 useipv6=False,
1030 useipv6=False,
1031 ):
1031 ):
1032 """Create a test from parameters.
1032 """Create a test from parameters.
1033
1033
1034 path is the full path to the file defining the test.
1034 path is the full path to the file defining the test.
1035
1035
1036 tmpdir is the main temporary directory to use for this test.
1036 tmpdir is the main temporary directory to use for this test.
1037
1037
1038 keeptmpdir determines whether to keep the test's temporary directory
1038 keeptmpdir determines whether to keep the test's temporary directory
1039 after execution. It defaults to removal (False).
1039 after execution. It defaults to removal (False).
1040
1040
1041 debug mode will make the test execute verbosely, with unfiltered
1041 debug mode will make the test execute verbosely, with unfiltered
1042 output.
1042 output.
1043
1043
1044 timeout controls the maximum run time of the test. It is ignored when
1044 timeout controls the maximum run time of the test. It is ignored when
1045 debug is True. See slowtimeout for tests with #require slow.
1045 debug is True. See slowtimeout for tests with #require slow.
1046
1046
1047 slowtimeout overrides timeout if the test has #require slow.
1047 slowtimeout overrides timeout if the test has #require slow.
1048
1048
1049 startport controls the starting port number to use for this test. Each
1049 startport controls the starting port number to use for this test. Each
1050 test will reserve 3 port numbers for execution. It is the caller's
1050 test will reserve 3 port numbers for execution. It is the caller's
1051 responsibility to allocate a non-overlapping port range to Test
1051 responsibility to allocate a non-overlapping port range to Test
1052 instances.
1052 instances.
1053
1053
1054 extraconfigopts is an iterable of extra hgrc config options. Values
1054 extraconfigopts is an iterable of extra hgrc config options. Values
1055 must have the form "key=value" (something understood by hgrc). Values
1055 must have the form "key=value" (something understood by hgrc). Values
1056 of the form "foo.key=value" will result in "[foo] key=value".
1056 of the form "foo.key=value" will result in "[foo] key=value".
1057
1057
1058 shell is the shell to execute tests in.
1058 shell is the shell to execute tests in.
1059 """
1059 """
1060 if timeout is None:
1060 if timeout is None:
1061 timeout = defaults['timeout']
1061 timeout = defaults['timeout']
1062 if startport is None:
1062 if startport is None:
1063 startport = defaults['port']
1063 startport = defaults['port']
1064 if slowtimeout is None:
1064 if slowtimeout is None:
1065 slowtimeout = defaults['slowtimeout']
1065 slowtimeout = defaults['slowtimeout']
1066 self.path = path
1066 self.path = path
1067 self.relpath = os.path.relpath(path)
1067 self.relpath = os.path.relpath(path)
1068 self.bname = os.path.basename(path)
1068 self.bname = os.path.basename(path)
1069 self.name = _bytes2sys(self.bname)
1069 self.name = _bytes2sys(self.bname)
1070 self._testdir = os.path.dirname(path)
1070 self._testdir = os.path.dirname(path)
1071 self._outputdir = outputdir
1071 self._outputdir = outputdir
1072 self._tmpname = os.path.basename(path)
1072 self._tmpname = os.path.basename(path)
1073 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1073 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1074
1074
1075 self._threadtmp = tmpdir
1075 self._threadtmp = tmpdir
1076 self._keeptmpdir = keeptmpdir
1076 self._keeptmpdir = keeptmpdir
1077 self._debug = debug
1077 self._debug = debug
1078 self._first = first
1078 self._first = first
1079 self._timeout = timeout
1079 self._timeout = timeout
1080 self._slowtimeout = slowtimeout
1080 self._slowtimeout = slowtimeout
1081 self._startport = startport
1081 self._startport = startport
1082 self._extraconfigopts = extraconfigopts or []
1082 self._extraconfigopts = extraconfigopts or []
1083 self._shell = _sys2bytes(shell)
1083 self._shell = _sys2bytes(shell)
1084 self._hgcommand = hgcommand or b'hg'
1084 self._hgcommand = hgcommand or b'hg'
1085 self._usechg = usechg
1085 self._usechg = usechg
1086 self._chgdebug = chgdebug
1086 self._chgdebug = chgdebug
1087 self._useipv6 = useipv6
1087 self._useipv6 = useipv6
1088
1088
1089 self._aborted = False
1089 self._aborted = False
1090 self._daemonpids = []
1090 self._daemonpids = []
1091 self._finished = None
1091 self._finished = None
1092 self._ret = None
1092 self._ret = None
1093 self._out = None
1093 self._out = None
1094 self._skipped = None
1094 self._skipped = None
1095 self._testtmp = None
1095 self._testtmp = None
1096 self._chgsockdir = None
1096 self._chgsockdir = None
1097
1097
1098 self._refout = self.readrefout()
1098 self._refout = self.readrefout()
1099
1099
1100 def readrefout(self):
1100 def readrefout(self):
1101 """read reference output"""
1101 """read reference output"""
1102 # If we're not in --debug mode and reference output file exists,
1102 # If we're not in --debug mode and reference output file exists,
1103 # check test output against it.
1103 # check test output against it.
1104 if self._debug:
1104 if self._debug:
1105 return None # to match "out is None"
1105 return None # to match "out is None"
1106 elif os.path.exists(self.refpath):
1106 elif os.path.exists(self.refpath):
1107 with open(self.refpath, 'rb') as f:
1107 with open(self.refpath, 'rb') as f:
1108 return f.read().splitlines(True)
1108 return f.read().splitlines(True)
1109 else:
1109 else:
1110 return []
1110 return []
1111
1111
1112 # needed to get base class __repr__ running
1112 # needed to get base class __repr__ running
1113 @property
1113 @property
1114 def _testMethodName(self):
1114 def _testMethodName(self):
1115 return self.name
1115 return self.name
1116
1116
1117 def __str__(self):
1117 def __str__(self):
1118 return self.name
1118 return self.name
1119
1119
1120 def shortDescription(self):
1120 def shortDescription(self):
1121 return self.name
1121 return self.name
1122
1122
1123 def setUp(self):
1123 def setUp(self):
1124 """Tasks to perform before run()."""
1124 """Tasks to perform before run()."""
1125 self._finished = False
1125 self._finished = False
1126 self._ret = None
1126 self._ret = None
1127 self._out = None
1127 self._out = None
1128 self._skipped = None
1128 self._skipped = None
1129
1129
1130 try:
1130 try:
1131 os.mkdir(self._threadtmp)
1131 os.mkdir(self._threadtmp)
1132 except OSError as e:
1132 except OSError as e:
1133 if e.errno != errno.EEXIST:
1133 if e.errno != errno.EEXIST:
1134 raise
1134 raise
1135
1135
1136 name = self._tmpname
1136 name = self._tmpname
1137 self._testtmp = os.path.join(self._threadtmp, name)
1137 self._testtmp = os.path.join(self._threadtmp, name)
1138 os.mkdir(self._testtmp)
1138 os.mkdir(self._testtmp)
1139
1139
1140 # Remove any previous output files.
1140 # Remove any previous output files.
1141 if os.path.exists(self.errpath):
1141 if os.path.exists(self.errpath):
1142 try:
1142 try:
1143 os.remove(self.errpath)
1143 os.remove(self.errpath)
1144 except OSError as e:
1144 except OSError as e:
1145 # We might have raced another test to clean up a .err
1145 # We might have raced another test to clean up a .err
1146 # file, so ignore ENOENT when removing a previous .err
1146 # file, so ignore ENOENT when removing a previous .err
1147 # file.
1147 # file.
1148 if e.errno != errno.ENOENT:
1148 if e.errno != errno.ENOENT:
1149 raise
1149 raise
1150
1150
1151 if self._usechg:
1151 if self._usechg:
1152 self._chgsockdir = os.path.join(
1152 self._chgsockdir = os.path.join(
1153 self._threadtmp, b'%s.chgsock' % name
1153 self._threadtmp, b'%s.chgsock' % name
1154 )
1154 )
1155 os.mkdir(self._chgsockdir)
1155 os.mkdir(self._chgsockdir)
1156
1156
1157 def run(self, result):
1157 def run(self, result):
1158 """Run this test and report results against a TestResult instance."""
1158 """Run this test and report results against a TestResult instance."""
1159 # This function is extremely similar to unittest.TestCase.run(). Once
1159 # This function is extremely similar to unittest.TestCase.run(). Once
1160 # we require Python 2.7 (or at least its version of unittest), this
1160 # we require Python 2.7 (or at least its version of unittest), this
1161 # function can largely go away.
1161 # function can largely go away.
1162 self._result = result
1162 self._result = result
1163 result.startTest(self)
1163 result.startTest(self)
1164 try:
1164 try:
1165 try:
1165 try:
1166 self.setUp()
1166 self.setUp()
1167 except (KeyboardInterrupt, SystemExit):
1167 except (KeyboardInterrupt, SystemExit):
1168 self._aborted = True
1168 self._aborted = True
1169 raise
1169 raise
1170 except Exception:
1170 except Exception:
1171 result.addError(self, sys.exc_info())
1171 result.addError(self, sys.exc_info())
1172 return
1172 return
1173
1173
1174 success = False
1174 success = False
1175 try:
1175 try:
1176 self.runTest()
1176 self.runTest()
1177 except KeyboardInterrupt:
1177 except KeyboardInterrupt:
1178 self._aborted = True
1178 self._aborted = True
1179 raise
1179 raise
1180 except unittest.SkipTest as e:
1180 except unittest.SkipTest as e:
1181 result.addSkip(self, str(e))
1181 result.addSkip(self, str(e))
1182 # The base class will have already counted this as a
1182 # The base class will have already counted this as a
1183 # test we "ran", but we want to exclude skipped tests
1183 # test we "ran", but we want to exclude skipped tests
1184 # from those we count towards those run.
1184 # from those we count towards those run.
1185 result.testsRun -= 1
1185 result.testsRun -= 1
1186 except self.failureException as e:
1186 except self.failureException as e:
1187 # This differs from unittest in that we don't capture
1187 # This differs from unittest in that we don't capture
1188 # the stack trace. This is for historical reasons and
1188 # the stack trace. This is for historical reasons and
1189 # this decision could be revisited in the future,
1189 # this decision could be revisited in the future,
1190 # especially for PythonTest instances.
1190 # especially for PythonTest instances.
1191 if result.addFailure(self, str(e)):
1191 if result.addFailure(self, str(e)):
1192 success = True
1192 success = True
1193 except Exception:
1193 except Exception:
1194 result.addError(self, sys.exc_info())
1194 result.addError(self, sys.exc_info())
1195 else:
1195 else:
1196 success = True
1196 success = True
1197
1197
1198 try:
1198 try:
1199 self.tearDown()
1199 self.tearDown()
1200 except (KeyboardInterrupt, SystemExit):
1200 except (KeyboardInterrupt, SystemExit):
1201 self._aborted = True
1201 self._aborted = True
1202 raise
1202 raise
1203 except Exception:
1203 except Exception:
1204 result.addError(self, sys.exc_info())
1204 result.addError(self, sys.exc_info())
1205 success = False
1205 success = False
1206
1206
1207 if success:
1207 if success:
1208 result.addSuccess(self)
1208 result.addSuccess(self)
1209 finally:
1209 finally:
1210 result.stopTest(self, interrupted=self._aborted)
1210 result.stopTest(self, interrupted=self._aborted)
1211
1211
1212 def runTest(self):
1212 def runTest(self):
1213 """Run this test instance.
1213 """Run this test instance.
1214
1214
1215 This will return a tuple describing the result of the test.
1215 This will return a tuple describing the result of the test.
1216 """
1216 """
1217 env = self._getenv()
1217 env = self._getenv()
1218 self._genrestoreenv(env)
1218 self._genrestoreenv(env)
1219 self._daemonpids.append(env['DAEMON_PIDS'])
1219 self._daemonpids.append(env['DAEMON_PIDS'])
1220 self._createhgrc(env['HGRCPATH'])
1220 self._createhgrc(env['HGRCPATH'])
1221
1221
1222 vlog('# Test', self.name)
1222 vlog('# Test', self.name)
1223
1223
1224 ret, out = self._run(env)
1224 ret, out = self._run(env)
1225 self._finished = True
1225 self._finished = True
1226 self._ret = ret
1226 self._ret = ret
1227 self._out = out
1227 self._out = out
1228
1228
1229 def describe(ret):
1229 def describe(ret):
1230 if ret < 0:
1230 if ret < 0:
1231 return 'killed by signal: %d' % -ret
1231 return 'killed by signal: %d' % -ret
1232 return 'returned error code %d' % ret
1232 return 'returned error code %d' % ret
1233
1233
1234 self._skipped = False
1234 self._skipped = False
1235
1235
1236 if ret == self.SKIPPED_STATUS:
1236 if ret == self.SKIPPED_STATUS:
1237 if out is None: # Debug mode, nothing to parse.
1237 if out is None: # Debug mode, nothing to parse.
1238 missing = ['unknown']
1238 missing = ['unknown']
1239 failed = None
1239 failed = None
1240 else:
1240 else:
1241 missing, failed = TTest.parsehghaveoutput(out)
1241 missing, failed = TTest.parsehghaveoutput(out)
1242
1242
1243 if not missing:
1243 if not missing:
1244 missing = ['skipped']
1244 missing = ['skipped']
1245
1245
1246 if failed:
1246 if failed:
1247 self.fail('hg have failed checking for %s' % failed[-1])
1247 self.fail('hg have failed checking for %s' % failed[-1])
1248 else:
1248 else:
1249 self._skipped = True
1249 self._skipped = True
1250 raise unittest.SkipTest(missing[-1])
1250 raise unittest.SkipTest(missing[-1])
1251 elif ret == 'timeout':
1251 elif ret == 'timeout':
1252 self.fail('timed out')
1252 self.fail('timed out')
1253 elif ret is False:
1253 elif ret is False:
1254 self.fail('no result code from test')
1254 self.fail('no result code from test')
1255 elif out != self._refout:
1255 elif out != self._refout:
1256 # Diff generation may rely on written .err file.
1256 # Diff generation may rely on written .err file.
1257 if (
1257 if (
1258 (ret != 0 or out != self._refout)
1258 (ret != 0 or out != self._refout)
1259 and not self._skipped
1259 and not self._skipped
1260 and not self._debug
1260 and not self._debug
1261 ):
1261 ):
1262 with open(self.errpath, 'wb') as f:
1262 with open(self.errpath, 'wb') as f:
1263 for line in out:
1263 for line in out:
1264 f.write(line)
1264 f.write(line)
1265
1265
1266 # The result object handles diff calculation for us.
1266 # The result object handles diff calculation for us.
1267 with firstlock:
1267 with firstlock:
1268 if self._result.addOutputMismatch(self, ret, out, self._refout):
1268 if self._result.addOutputMismatch(self, ret, out, self._refout):
1269 # change was accepted, skip failing
1269 # change was accepted, skip failing
1270 return
1270 return
1271 if self._first:
1271 if self._first:
1272 global firsterror
1272 global firsterror
1273 firsterror = True
1273 firsterror = True
1274
1274
1275 if ret:
1275 if ret:
1276 msg = 'output changed and ' + describe(ret)
1276 msg = 'output changed and ' + describe(ret)
1277 else:
1277 else:
1278 msg = 'output changed'
1278 msg = 'output changed'
1279
1279
1280 self.fail(msg)
1280 self.fail(msg)
1281 elif ret:
1281 elif ret:
1282 self.fail(describe(ret))
1282 self.fail(describe(ret))
1283
1283
1284 def tearDown(self):
1284 def tearDown(self):
1285 """Tasks to perform after run()."""
1285 """Tasks to perform after run()."""
1286 for entry in self._daemonpids:
1286 for entry in self._daemonpids:
1287 killdaemons(entry)
1287 killdaemons(entry)
1288 self._daemonpids = []
1288 self._daemonpids = []
1289
1289
1290 if self._keeptmpdir:
1290 if self._keeptmpdir:
1291 log(
1291 log(
1292 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1292 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1293 % (
1293 % (
1294 _bytes2sys(self._testtmp),
1294 _bytes2sys(self._testtmp),
1295 _bytes2sys(self._threadtmp),
1295 _bytes2sys(self._threadtmp),
1296 )
1296 )
1297 )
1297 )
1298 else:
1298 else:
1299 try:
1299 try:
1300 shutil.rmtree(self._testtmp)
1300 shutil.rmtree(self._testtmp)
1301 except OSError:
1301 except OSError:
1302 # unreadable directory may be left in $TESTTMP; fix permission
1302 # unreadable directory may be left in $TESTTMP; fix permission
1303 # and try again
1303 # and try again
1304 makecleanable(self._testtmp)
1304 makecleanable(self._testtmp)
1305 shutil.rmtree(self._testtmp, True)
1305 shutil.rmtree(self._testtmp, True)
1306 shutil.rmtree(self._threadtmp, True)
1306 shutil.rmtree(self._threadtmp, True)
1307
1307
1308 if self._usechg:
1308 if self._usechg:
1309 # chgservers will stop automatically after they find the socket
1309 # chgservers will stop automatically after they find the socket
1310 # files are deleted
1310 # files are deleted
1311 shutil.rmtree(self._chgsockdir, True)
1311 shutil.rmtree(self._chgsockdir, True)
1312
1312
1313 if (
1313 if (
1314 (self._ret != 0 or self._out != self._refout)
1314 (self._ret != 0 or self._out != self._refout)
1315 and not self._skipped
1315 and not self._skipped
1316 and not self._debug
1316 and not self._debug
1317 and self._out
1317 and self._out
1318 ):
1318 ):
1319 with open(self.errpath, 'wb') as f:
1319 with open(self.errpath, 'wb') as f:
1320 for line in self._out:
1320 for line in self._out:
1321 f.write(line)
1321 f.write(line)
1322
1322
1323 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1323 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1324
1324
1325 def _run(self, env):
1325 def _run(self, env):
1326 # This should be implemented in child classes to run tests.
1326 # This should be implemented in child classes to run tests.
1327 raise unittest.SkipTest('unknown test type')
1327 raise unittest.SkipTest('unknown test type')
1328
1328
1329 def abort(self):
1329 def abort(self):
1330 """Terminate execution of this test."""
1330 """Terminate execution of this test."""
1331 self._aborted = True
1331 self._aborted = True
1332
1332
1333 def _portmap(self, i):
1333 def _portmap(self, i):
1334 offset = b'' if i == 0 else b'%d' % i
1334 offset = b'' if i == 0 else b'%d' % i
1335 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1335 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1336
1336
1337 def _getreplacements(self):
1337 def _getreplacements(self):
1338 """Obtain a mapping of text replacements to apply to test output.
1338 """Obtain a mapping of text replacements to apply to test output.
1339
1339
1340 Test output needs to be normalized so it can be compared to expected
1340 Test output needs to be normalized so it can be compared to expected
1341 output. This function defines how some of that normalization will
1341 output. This function defines how some of that normalization will
1342 occur.
1342 occur.
1343 """
1343 """
1344 r = [
1344 r = [
1345 # This list should be parallel to defineport in _getenv
1345 # This list should be parallel to defineport in _getenv
1346 self._portmap(0),
1346 self._portmap(0),
1347 self._portmap(1),
1347 self._portmap(1),
1348 self._portmap(2),
1348 self._portmap(2),
1349 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1349 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1350 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1350 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1351 ]
1351 ]
1352 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1352 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1353 if WINDOWS:
1353 if WINDOWS:
1354 # JSON output escapes backslashes in Windows paths, so also catch a
1354 # JSON output escapes backslashes in Windows paths, so also catch a
1355 # double-escape.
1355 # double-escape.
1356 replaced = self._testtmp.replace(b'\\', br'\\')
1356 replaced = self._testtmp.replace(b'\\', br'\\')
1357 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1357 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1358
1358
1359 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1359 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1360
1360
1361 if os.path.exists(replacementfile):
1361 if os.path.exists(replacementfile):
1362 data = {}
1362 data = {}
1363 with open(replacementfile, mode='rb') as source:
1363 with open(replacementfile, mode='rb') as source:
1364 # the intermediate 'compile' step help with debugging
1364 # the intermediate 'compile' step help with debugging
1365 code = compile(source.read(), replacementfile, 'exec')
1365 code = compile(source.read(), replacementfile, 'exec')
1366 exec(code, data)
1366 exec(code, data)
1367 for value in data.get('substitutions', ()):
1367 for value in data.get('substitutions', ()):
1368 if len(value) != 2:
1368 if len(value) != 2:
1369 msg = 'malformatted substitution in %s: %r'
1369 msg = 'malformatted substitution in %s: %r'
1370 msg %= (replacementfile, value)
1370 msg %= (replacementfile, value)
1371 raise ValueError(msg)
1371 raise ValueError(msg)
1372 r.append(value)
1372 r.append(value)
1373 return r
1373 return r
1374
1374
1375 def _escapepath(self, p):
1375 def _escapepath(self, p):
1376 if WINDOWS:
1376 if WINDOWS:
1377 return b''.join(
1377 return b''.join(
1378 c.isalpha()
1378 c.isalpha()
1379 and b'[%s%s]' % (c.lower(), c.upper())
1379 and b'[%s%s]' % (c.lower(), c.upper())
1380 or c in b'/\\'
1380 or c in b'/\\'
1381 and br'[/\\]'
1381 and br'[/\\]'
1382 or c.isdigit()
1382 or c.isdigit()
1383 and c
1383 and c
1384 or b'\\' + c
1384 or b'\\' + c
1385 for c in [p[i : i + 1] for i in range(len(p))]
1385 for c in [p[i : i + 1] for i in range(len(p))]
1386 )
1386 )
1387 else:
1387 else:
1388 return re.escape(p)
1388 return re.escape(p)
1389
1389
1390 def _localip(self):
1390 def _localip(self):
1391 if self._useipv6:
1391 if self._useipv6:
1392 return b'::1'
1392 return b'::1'
1393 else:
1393 else:
1394 return b'127.0.0.1'
1394 return b'127.0.0.1'
1395
1395
1396 def _genrestoreenv(self, testenv):
1396 def _genrestoreenv(self, testenv):
1397 """Generate a script that can be used by tests to restore the original
1397 """Generate a script that can be used by tests to restore the original
1398 environment."""
1398 environment."""
1399 # Put the restoreenv script inside self._threadtmp
1399 # Put the restoreenv script inside self._threadtmp
1400 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1400 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1401 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1401 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1402
1402
1403 # Only restore environment variable names that the shell allows
1403 # Only restore environment variable names that the shell allows
1404 # us to export.
1404 # us to export.
1405 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1405 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1406
1406
1407 # Do not restore these variables; otherwise tests would fail.
1407 # Do not restore these variables; otherwise tests would fail.
1408 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1408 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1409
1409
1410 with open(scriptpath, 'w') as envf:
1410 with open(scriptpath, 'w') as envf:
1411 for name, value in origenviron.items():
1411 for name, value in origenviron.items():
1412 if not name_regex.match(name):
1412 if not name_regex.match(name):
1413 # Skip environment variables with unusual names not
1413 # Skip environment variables with unusual names not
1414 # allowed by most shells.
1414 # allowed by most shells.
1415 continue
1415 continue
1416 if name in reqnames:
1416 if name in reqnames:
1417 continue
1417 continue
1418 envf.write('%s=%s\n' % (name, shellquote(value)))
1418 envf.write('%s=%s\n' % (name, shellquote(value)))
1419
1419
1420 for name in testenv:
1420 for name in testenv:
1421 if name in origenviron or name in reqnames:
1421 if name in origenviron or name in reqnames:
1422 continue
1422 continue
1423 envf.write('unset %s\n' % (name,))
1423 envf.write('unset %s\n' % (name,))
1424
1424
1425 def _getenv(self):
1425 def _getenv(self):
1426 """Obtain environment variables to use during test execution."""
1426 """Obtain environment variables to use during test execution."""
1427
1427
1428 def defineport(i):
1428 def defineport(i):
1429 offset = '' if i == 0 else '%s' % i
1429 offset = '' if i == 0 else '%s' % i
1430 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1430 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1431
1431
1432 env = os.environ.copy()
1432 env = os.environ.copy()
1433 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1433 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1434 env['HGEMITWARNINGS'] = '1'
1434 env['HGEMITWARNINGS'] = '1'
1435 env['TESTTMP'] = _bytes2sys(self._testtmp)
1435 env['TESTTMP'] = _bytes2sys(self._testtmp)
1436 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1436 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1437 env['HGTEST_UUIDFILE'] = uid_file
1437 env['HGTEST_UUIDFILE'] = uid_file
1438 env['TESTNAME'] = self.name
1438 env['TESTNAME'] = self.name
1439 env['HOME'] = _bytes2sys(self._testtmp)
1439 env['HOME'] = _bytes2sys(self._testtmp)
1440 if WINDOWS:
1440 if WINDOWS:
1441 env['REALUSERPROFILE'] = env['USERPROFILE']
1441 env['REALUSERPROFILE'] = env['USERPROFILE']
1442 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1442 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1443 env['USERPROFILE'] = env['HOME']
1443 env['USERPROFILE'] = env['HOME']
1444 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1444 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1445 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1445 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1446 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1446 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1447 # This number should match portneeded in _getport
1447 # This number should match portneeded in _getport
1448 for port in xrange(3):
1448 for port in xrange(3):
1449 # This list should be parallel to _portmap in _getreplacements
1449 # This list should be parallel to _portmap in _getreplacements
1450 defineport(port)
1450 defineport(port)
1451 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1451 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1452 env["DAEMON_PIDS"] = _bytes2sys(
1452 env["DAEMON_PIDS"] = _bytes2sys(
1453 os.path.join(self._threadtmp, b'daemon.pids')
1453 os.path.join(self._threadtmp, b'daemon.pids')
1454 )
1454 )
1455 env["HGEDITOR"] = (
1455 env["HGEDITOR"] = (
1456 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1456 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1457 )
1457 )
1458 env["HGUSER"] = "test"
1458 env["HGUSER"] = "test"
1459 env["HGENCODING"] = "ascii"
1459 env["HGENCODING"] = "ascii"
1460 env["HGENCODINGMODE"] = "strict"
1460 env["HGENCODINGMODE"] = "strict"
1461 env["HGHOSTNAME"] = "test-hostname"
1461 env["HGHOSTNAME"] = "test-hostname"
1462 env['HGIPV6'] = str(int(self._useipv6))
1462 env['HGIPV6'] = str(int(self._useipv6))
1463 # See contrib/catapipe.py for how to use this functionality.
1463 # See contrib/catapipe.py for how to use this functionality.
1464 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1464 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1465 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1465 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1466 # non-test one in as a default, otherwise set to devnull
1466 # non-test one in as a default, otherwise set to devnull
1467 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1467 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1468 'HGCATAPULTSERVERPIPE', os.devnull
1468 'HGCATAPULTSERVERPIPE', os.devnull
1469 )
1469 )
1470
1470
1471 extraextensions = []
1471 extraextensions = []
1472 for opt in self._extraconfigopts:
1472 for opt in self._extraconfigopts:
1473 section, key = opt.split('.', 1)
1473 section, key = opt.split('.', 1)
1474 if section != 'extensions':
1474 if section != 'extensions':
1475 continue
1475 continue
1476 name = key.split('=', 1)[0]
1476 name = key.split('=', 1)[0]
1477 extraextensions.append(name)
1477 extraextensions.append(name)
1478
1478
1479 if extraextensions:
1479 if extraextensions:
1480 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1480 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1481
1481
1482 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1482 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1483 # IP addresses.
1483 # IP addresses.
1484 env['LOCALIP'] = _bytes2sys(self._localip())
1484 env['LOCALIP'] = _bytes2sys(self._localip())
1485
1485
1486 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1486 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1487 # but this is needed for testing python instances like dummyssh,
1487 # but this is needed for testing python instances like dummyssh,
1488 # dummysmtpd.py, and dumbhttp.py.
1488 # dummysmtpd.py, and dumbhttp.py.
1489 if PYTHON3 and WINDOWS:
1489 if PYTHON3 and WINDOWS:
1490 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1490 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1491
1491
1492 # Modified HOME in test environment can confuse Rust tools. So set
1492 # Modified HOME in test environment can confuse Rust tools. So set
1493 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1493 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1494 # present and these variables aren't already defined.
1494 # present and these variables aren't already defined.
1495 cargo_home_path = os.path.expanduser('~/.cargo')
1495 cargo_home_path = os.path.expanduser('~/.cargo')
1496 rustup_home_path = os.path.expanduser('~/.rustup')
1496 rustup_home_path = os.path.expanduser('~/.rustup')
1497
1497
1498 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1498 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1499 env['CARGO_HOME'] = cargo_home_path
1499 env['CARGO_HOME'] = cargo_home_path
1500 if (
1500 if (
1501 os.path.exists(rustup_home_path)
1501 os.path.exists(rustup_home_path)
1502 and b'RUSTUP_HOME' not in osenvironb
1502 and b'RUSTUP_HOME' not in osenvironb
1503 ):
1503 ):
1504 env['RUSTUP_HOME'] = rustup_home_path
1504 env['RUSTUP_HOME'] = rustup_home_path
1505
1505
1506 # Reset some environment variables to well-known values so that
1506 # Reset some environment variables to well-known values so that
1507 # the tests produce repeatable output.
1507 # the tests produce repeatable output.
1508 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1508 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1509 env['TZ'] = 'GMT'
1509 env['TZ'] = 'GMT'
1510 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1510 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1511 env['COLUMNS'] = '80'
1511 env['COLUMNS'] = '80'
1512 env['TERM'] = 'xterm'
1512 env['TERM'] = 'xterm'
1513
1513
1514 dropped = [
1514 dropped = [
1515 'CDPATH',
1515 'CDPATH',
1516 'CHGDEBUG',
1516 'CHGDEBUG',
1517 'EDITOR',
1517 'EDITOR',
1518 'GREP_OPTIONS',
1518 'GREP_OPTIONS',
1519 'HG',
1519 'HG',
1520 'HGMERGE',
1520 'HGMERGE',
1521 'HGPLAIN',
1521 'HGPLAIN',
1522 'HGPLAINEXCEPT',
1522 'HGPLAINEXCEPT',
1523 'HGPROF',
1523 'HGPROF',
1524 'http_proxy',
1524 'http_proxy',
1525 'no_proxy',
1525 'no_proxy',
1526 'NO_PROXY',
1526 'NO_PROXY',
1527 'PAGER',
1527 'PAGER',
1528 'VISUAL',
1528 'VISUAL',
1529 ]
1529 ]
1530
1530
1531 for k in dropped:
1531 for k in dropped:
1532 if k in env:
1532 if k in env:
1533 del env[k]
1533 del env[k]
1534
1534
1535 # unset env related to hooks
1535 # unset env related to hooks
1536 for k in list(env):
1536 for k in list(env):
1537 if k.startswith('HG_'):
1537 if k.startswith('HG_'):
1538 del env[k]
1538 del env[k]
1539
1539
1540 if self._usechg:
1540 if self._usechg:
1541 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1541 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1542 if self._chgdebug:
1542 if self._chgdebug:
1543 env['CHGDEBUG'] = 'true'
1543 env['CHGDEBUG'] = 'true'
1544
1544
1545 return env
1545 return env
1546
1546
1547 def _createhgrc(self, path):
1547 def _createhgrc(self, path):
1548 """Create an hgrc file for this test."""
1548 """Create an hgrc file for this test."""
1549 with open(path, 'wb') as hgrc:
1549 with open(path, 'wb') as hgrc:
1550 hgrc.write(b'[ui]\n')
1550 hgrc.write(b'[ui]\n')
1551 hgrc.write(b'slash = True\n')
1551 hgrc.write(b'slash = True\n')
1552 hgrc.write(b'interactive = False\n')
1552 hgrc.write(b'interactive = False\n')
1553 hgrc.write(b'detailed-exit-code = True\n')
1553 hgrc.write(b'detailed-exit-code = True\n')
1554 hgrc.write(b'merge = internal:merge\n')
1554 hgrc.write(b'merge = internal:merge\n')
1555 hgrc.write(b'mergemarkers = detailed\n')
1555 hgrc.write(b'mergemarkers = detailed\n')
1556 hgrc.write(b'promptecho = True\n')
1556 hgrc.write(b'promptecho = True\n')
1557 dummyssh = os.path.join(self._testdir, b'dummyssh')
1558 hgrc.write(b'ssh = "%s" "%s"\n' % (PYTHON, dummyssh))
1557 hgrc.write(b'timeout.warn=15\n')
1559 hgrc.write(b'timeout.warn=15\n')
1558 hgrc.write(b'[chgserver]\n')
1560 hgrc.write(b'[chgserver]\n')
1559 hgrc.write(b'idletimeout=60\n')
1561 hgrc.write(b'idletimeout=60\n')
1560 hgrc.write(b'[defaults]\n')
1562 hgrc.write(b'[defaults]\n')
1561 hgrc.write(b'[devel]\n')
1563 hgrc.write(b'[devel]\n')
1562 hgrc.write(b'all-warnings = true\n')
1564 hgrc.write(b'all-warnings = true\n')
1563 hgrc.write(b'default-date = 0 0\n')
1565 hgrc.write(b'default-date = 0 0\n')
1564 hgrc.write(b'[largefiles]\n')
1566 hgrc.write(b'[largefiles]\n')
1565 hgrc.write(
1567 hgrc.write(
1566 b'usercache = %s\n'
1568 b'usercache = %s\n'
1567 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1569 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1568 )
1570 )
1569 hgrc.write(b'[lfs]\n')
1571 hgrc.write(b'[lfs]\n')
1570 hgrc.write(
1572 hgrc.write(
1571 b'usercache = %s\n'
1573 b'usercache = %s\n'
1572 % (os.path.join(self._testtmp, b'.cache/lfs'))
1574 % (os.path.join(self._testtmp, b'.cache/lfs'))
1573 )
1575 )
1574 hgrc.write(b'[web]\n')
1576 hgrc.write(b'[web]\n')
1575 hgrc.write(b'address = localhost\n')
1577 hgrc.write(b'address = localhost\n')
1576 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1578 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1577 hgrc.write(b'server-header = testing stub value\n')
1579 hgrc.write(b'server-header = testing stub value\n')
1578
1580
1579 for opt in self._extraconfigopts:
1581 for opt in self._extraconfigopts:
1580 section, key = _sys2bytes(opt).split(b'.', 1)
1582 section, key = _sys2bytes(opt).split(b'.', 1)
1581 assert b'=' in key, (
1583 assert b'=' in key, (
1582 'extra config opt %s must ' 'have an = for assignment' % opt
1584 'extra config opt %s must ' 'have an = for assignment' % opt
1583 )
1585 )
1584 hgrc.write(b'[%s]\n%s\n' % (section, key))
1586 hgrc.write(b'[%s]\n%s\n' % (section, key))
1585
1587
1586 def fail(self, msg):
1588 def fail(self, msg):
1587 # unittest differentiates between errored and failed.
1589 # unittest differentiates between errored and failed.
1588 # Failed is denoted by AssertionError (by default at least).
1590 # Failed is denoted by AssertionError (by default at least).
1589 raise AssertionError(msg)
1591 raise AssertionError(msg)
1590
1592
1591 def _runcommand(self, cmd, env, normalizenewlines=False):
1593 def _runcommand(self, cmd, env, normalizenewlines=False):
1592 """Run command in a sub-process, capturing the output (stdout and
1594 """Run command in a sub-process, capturing the output (stdout and
1593 stderr).
1595 stderr).
1594
1596
1595 Return a tuple (exitcode, output). output is None in debug mode.
1597 Return a tuple (exitcode, output). output is None in debug mode.
1596 """
1598 """
1597 if self._debug:
1599 if self._debug:
1598 proc = subprocess.Popen(
1600 proc = subprocess.Popen(
1599 _bytes2sys(cmd),
1601 _bytes2sys(cmd),
1600 shell=True,
1602 shell=True,
1601 close_fds=closefds,
1603 close_fds=closefds,
1602 cwd=_bytes2sys(self._testtmp),
1604 cwd=_bytes2sys(self._testtmp),
1603 env=env,
1605 env=env,
1604 )
1606 )
1605 ret = proc.wait()
1607 ret = proc.wait()
1606 return (ret, None)
1608 return (ret, None)
1607
1609
1608 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1610 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1609
1611
1610 def cleanup():
1612 def cleanup():
1611 terminate(proc)
1613 terminate(proc)
1612 ret = proc.wait()
1614 ret = proc.wait()
1613 if ret == 0:
1615 if ret == 0:
1614 ret = signal.SIGTERM << 8
1616 ret = signal.SIGTERM << 8
1615 killdaemons(env['DAEMON_PIDS'])
1617 killdaemons(env['DAEMON_PIDS'])
1616 return ret
1618 return ret
1617
1619
1618 proc.tochild.close()
1620 proc.tochild.close()
1619
1621
1620 try:
1622 try:
1621 output = proc.fromchild.read()
1623 output = proc.fromchild.read()
1622 except KeyboardInterrupt:
1624 except KeyboardInterrupt:
1623 vlog('# Handling keyboard interrupt')
1625 vlog('# Handling keyboard interrupt')
1624 cleanup()
1626 cleanup()
1625 raise
1627 raise
1626
1628
1627 ret = proc.wait()
1629 ret = proc.wait()
1628 if wifexited(ret):
1630 if wifexited(ret):
1629 ret = os.WEXITSTATUS(ret)
1631 ret = os.WEXITSTATUS(ret)
1630
1632
1631 if proc.timeout:
1633 if proc.timeout:
1632 ret = 'timeout'
1634 ret = 'timeout'
1633
1635
1634 if ret:
1636 if ret:
1635 killdaemons(env['DAEMON_PIDS'])
1637 killdaemons(env['DAEMON_PIDS'])
1636
1638
1637 for s, r in self._getreplacements():
1639 for s, r in self._getreplacements():
1638 output = re.sub(s, r, output)
1640 output = re.sub(s, r, output)
1639
1641
1640 if normalizenewlines:
1642 if normalizenewlines:
1641 output = output.replace(b'\r\n', b'\n')
1643 output = output.replace(b'\r\n', b'\n')
1642
1644
1643 return ret, output.splitlines(True)
1645 return ret, output.splitlines(True)
1644
1646
1645
1647
1646 class PythonTest(Test):
1648 class PythonTest(Test):
1647 """A Python-based test."""
1649 """A Python-based test."""
1648
1650
1649 @property
1651 @property
1650 def refpath(self):
1652 def refpath(self):
1651 return os.path.join(self._testdir, b'%s.out' % self.bname)
1653 return os.path.join(self._testdir, b'%s.out' % self.bname)
1652
1654
1653 def _run(self, env):
1655 def _run(self, env):
1654 # Quote the python(3) executable for Windows
1656 # Quote the python(3) executable for Windows
1655 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1657 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1656 vlog("# Running", cmd.decode("utf-8"))
1658 vlog("# Running", cmd.decode("utf-8"))
1657 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1659 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1658 if self._aborted:
1660 if self._aborted:
1659 raise KeyboardInterrupt()
1661 raise KeyboardInterrupt()
1660
1662
1661 return result
1663 return result
1662
1664
1663
1665
1664 # Some glob patterns apply only in some circumstances, so the script
1666 # Some glob patterns apply only in some circumstances, so the script
1665 # might want to remove (glob) annotations that otherwise should be
1667 # might want to remove (glob) annotations that otherwise should be
1666 # retained.
1668 # retained.
1667 checkcodeglobpats = [
1669 checkcodeglobpats = [
1668 # On Windows it looks like \ doesn't require a (glob), but we know
1670 # On Windows it looks like \ doesn't require a (glob), but we know
1669 # better.
1671 # better.
1670 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1672 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1671 re.compile(br'^moving \S+/.*[^)]$'),
1673 re.compile(br'^moving \S+/.*[^)]$'),
1672 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1674 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1673 # Not all platforms have 127.0.0.1 as loopback (though most do),
1675 # Not all platforms have 127.0.0.1 as loopback (though most do),
1674 # so we always glob that too.
1676 # so we always glob that too.
1675 re.compile(br'.*\$LOCALIP.*$'),
1677 re.compile(br'.*\$LOCALIP.*$'),
1676 ]
1678 ]
1677
1679
1678 bchr = chr
1680 bchr = chr
1679 if PYTHON3:
1681 if PYTHON3:
1680 bchr = lambda x: bytes([x])
1682 bchr = lambda x: bytes([x])
1681
1683
1682 WARN_UNDEFINED = 1
1684 WARN_UNDEFINED = 1
1683 WARN_YES = 2
1685 WARN_YES = 2
1684 WARN_NO = 3
1686 WARN_NO = 3
1685
1687
1686 MARK_OPTIONAL = b" (?)\n"
1688 MARK_OPTIONAL = b" (?)\n"
1687
1689
1688
1690
1689 def isoptional(line):
1691 def isoptional(line):
1690 return line.endswith(MARK_OPTIONAL)
1692 return line.endswith(MARK_OPTIONAL)
1691
1693
1692
1694
1693 class TTest(Test):
1695 class TTest(Test):
1694 """A "t test" is a test backed by a .t file."""
1696 """A "t test" is a test backed by a .t file."""
1695
1697
1696 SKIPPED_PREFIX = b'skipped: '
1698 SKIPPED_PREFIX = b'skipped: '
1697 FAILED_PREFIX = b'hghave check failed: '
1699 FAILED_PREFIX = b'hghave check failed: '
1698 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1700 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1699
1701
1700 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1702 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1701 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1703 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1702 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1704 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1703
1705
1704 def __init__(self, path, *args, **kwds):
1706 def __init__(self, path, *args, **kwds):
1705 # accept an extra "case" parameter
1707 # accept an extra "case" parameter
1706 case = kwds.pop('case', [])
1708 case = kwds.pop('case', [])
1707 self._case = case
1709 self._case = case
1708 self._allcases = {x for y in parsettestcases(path) for x in y}
1710 self._allcases = {x for y in parsettestcases(path) for x in y}
1709 super(TTest, self).__init__(path, *args, **kwds)
1711 super(TTest, self).__init__(path, *args, **kwds)
1710 if case:
1712 if case:
1711 casepath = b'#'.join(case)
1713 casepath = b'#'.join(case)
1712 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1714 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1713 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1715 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1714 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1716 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1715 self._have = {}
1717 self._have = {}
1716
1718
1717 @property
1719 @property
1718 def refpath(self):
1720 def refpath(self):
1719 return os.path.join(self._testdir, self.bname)
1721 return os.path.join(self._testdir, self.bname)
1720
1722
1721 def _run(self, env):
1723 def _run(self, env):
1722 with open(self.path, 'rb') as f:
1724 with open(self.path, 'rb') as f:
1723 lines = f.readlines()
1725 lines = f.readlines()
1724
1726
1725 # .t file is both reference output and the test input, keep reference
1727 # .t file is both reference output and the test input, keep reference
1726 # output updated with the the test input. This avoids some race
1728 # output updated with the the test input. This avoids some race
1727 # conditions where the reference output does not match the actual test.
1729 # conditions where the reference output does not match the actual test.
1728 if self._refout is not None:
1730 if self._refout is not None:
1729 self._refout = lines
1731 self._refout = lines
1730
1732
1731 salt, script, after, expected = self._parsetest(lines)
1733 salt, script, after, expected = self._parsetest(lines)
1732
1734
1733 # Write out the generated script.
1735 # Write out the generated script.
1734 fname = b'%s.sh' % self._testtmp
1736 fname = b'%s.sh' % self._testtmp
1735 with open(fname, 'wb') as f:
1737 with open(fname, 'wb') as f:
1736 for l in script:
1738 for l in script:
1737 f.write(l)
1739 f.write(l)
1738
1740
1739 cmd = b'%s "%s"' % (self._shell, fname)
1741 cmd = b'%s "%s"' % (self._shell, fname)
1740 vlog("# Running", cmd.decode("utf-8"))
1742 vlog("# Running", cmd.decode("utf-8"))
1741
1743
1742 exitcode, output = self._runcommand(cmd, env)
1744 exitcode, output = self._runcommand(cmd, env)
1743
1745
1744 if self._aborted:
1746 if self._aborted:
1745 raise KeyboardInterrupt()
1747 raise KeyboardInterrupt()
1746
1748
1747 # Do not merge output if skipped. Return hghave message instead.
1749 # Do not merge output if skipped. Return hghave message instead.
1748 # Similarly, with --debug, output is None.
1750 # Similarly, with --debug, output is None.
1749 if exitcode == self.SKIPPED_STATUS or output is None:
1751 if exitcode == self.SKIPPED_STATUS or output is None:
1750 return exitcode, output
1752 return exitcode, output
1751
1753
1752 return self._processoutput(exitcode, output, salt, after, expected)
1754 return self._processoutput(exitcode, output, salt, after, expected)
1753
1755
1754 def _hghave(self, reqs):
1756 def _hghave(self, reqs):
1755 allreqs = b' '.join(reqs)
1757 allreqs = b' '.join(reqs)
1756
1758
1757 self._detectslow(reqs)
1759 self._detectslow(reqs)
1758
1760
1759 if allreqs in self._have:
1761 if allreqs in self._have:
1760 return self._have.get(allreqs)
1762 return self._have.get(allreqs)
1761
1763
1762 # TODO do something smarter when all other uses of hghave are gone.
1764 # TODO do something smarter when all other uses of hghave are gone.
1763 runtestdir = osenvironb[b'RUNTESTDIR']
1765 runtestdir = osenvironb[b'RUNTESTDIR']
1764 tdir = runtestdir.replace(b'\\', b'/')
1766 tdir = runtestdir.replace(b'\\', b'/')
1765 proc = Popen4(
1767 proc = Popen4(
1766 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1768 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1767 self._testtmp,
1769 self._testtmp,
1768 0,
1770 0,
1769 self._getenv(),
1771 self._getenv(),
1770 )
1772 )
1771 stdout, stderr = proc.communicate()
1773 stdout, stderr = proc.communicate()
1772 ret = proc.wait()
1774 ret = proc.wait()
1773 if wifexited(ret):
1775 if wifexited(ret):
1774 ret = os.WEXITSTATUS(ret)
1776 ret = os.WEXITSTATUS(ret)
1775 if ret == 2:
1777 if ret == 2:
1776 print(stdout.decode('utf-8'))
1778 print(stdout.decode('utf-8'))
1777 sys.exit(1)
1779 sys.exit(1)
1778
1780
1779 if ret != 0:
1781 if ret != 0:
1780 self._have[allreqs] = (False, stdout)
1782 self._have[allreqs] = (False, stdout)
1781 return False, stdout
1783 return False, stdout
1782
1784
1783 self._have[allreqs] = (True, None)
1785 self._have[allreqs] = (True, None)
1784 return True, None
1786 return True, None
1785
1787
1786 def _detectslow(self, reqs):
1788 def _detectslow(self, reqs):
1787 """update the timeout of slow test when appropriate"""
1789 """update the timeout of slow test when appropriate"""
1788 if b'slow' in reqs:
1790 if b'slow' in reqs:
1789 self._timeout = self._slowtimeout
1791 self._timeout = self._slowtimeout
1790
1792
1791 def _iftest(self, args):
1793 def _iftest(self, args):
1792 # implements "#if"
1794 # implements "#if"
1793 reqs = []
1795 reqs = []
1794 for arg in args:
1796 for arg in args:
1795 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1797 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1796 if arg[3:] in self._case:
1798 if arg[3:] in self._case:
1797 return False
1799 return False
1798 elif arg in self._allcases:
1800 elif arg in self._allcases:
1799 if arg not in self._case:
1801 if arg not in self._case:
1800 return False
1802 return False
1801 else:
1803 else:
1802 reqs.append(arg)
1804 reqs.append(arg)
1803 self._detectslow(reqs)
1805 self._detectslow(reqs)
1804 return self._hghave(reqs)[0]
1806 return self._hghave(reqs)[0]
1805
1807
1806 def _parsetest(self, lines):
1808 def _parsetest(self, lines):
1807 # We generate a shell script which outputs unique markers to line
1809 # We generate a shell script which outputs unique markers to line
1808 # up script results with our source. These markers include input
1810 # up script results with our source. These markers include input
1809 # line number and the last return code.
1811 # line number and the last return code.
1810 salt = b"SALT%d" % time.time()
1812 salt = b"SALT%d" % time.time()
1811
1813
1812 def addsalt(line, inpython):
1814 def addsalt(line, inpython):
1813 if inpython:
1815 if inpython:
1814 script.append(b'%s %d 0\n' % (salt, line))
1816 script.append(b'%s %d 0\n' % (salt, line))
1815 else:
1817 else:
1816 script.append(b'echo %s %d $?\n' % (salt, line))
1818 script.append(b'echo %s %d $?\n' % (salt, line))
1817
1819
1818 activetrace = []
1820 activetrace = []
1819 session = str(uuid.uuid4())
1821 session = str(uuid.uuid4())
1820 if PYTHON3:
1822 if PYTHON3:
1821 session = session.encode('ascii')
1823 session = session.encode('ascii')
1822 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1824 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1823 'HGCATAPULTSERVERPIPE'
1825 'HGCATAPULTSERVERPIPE'
1824 )
1826 )
1825
1827
1826 def toggletrace(cmd=None):
1828 def toggletrace(cmd=None):
1827 if not hgcatapult or hgcatapult == os.devnull:
1829 if not hgcatapult or hgcatapult == os.devnull:
1828 return
1830 return
1829
1831
1830 if activetrace:
1832 if activetrace:
1831 script.append(
1833 script.append(
1832 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1834 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1833 % (session, activetrace[0])
1835 % (session, activetrace[0])
1834 )
1836 )
1835 if cmd is None:
1837 if cmd is None:
1836 return
1838 return
1837
1839
1838 if isinstance(cmd, str):
1840 if isinstance(cmd, str):
1839 quoted = shellquote(cmd.strip())
1841 quoted = shellquote(cmd.strip())
1840 else:
1842 else:
1841 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1843 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1842 quoted = quoted.replace(b'\\', b'\\\\')
1844 quoted = quoted.replace(b'\\', b'\\\\')
1843 script.append(
1845 script.append(
1844 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1846 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1845 % (session, quoted)
1847 % (session, quoted)
1846 )
1848 )
1847 activetrace[0:] = [quoted]
1849 activetrace[0:] = [quoted]
1848
1850
1849 script = []
1851 script = []
1850
1852
1851 # After we run the shell script, we re-unify the script output
1853 # After we run the shell script, we re-unify the script output
1852 # with non-active parts of the source, with synchronization by our
1854 # with non-active parts of the source, with synchronization by our
1853 # SALT line number markers. The after table contains the non-active
1855 # SALT line number markers. The after table contains the non-active
1854 # components, ordered by line number.
1856 # components, ordered by line number.
1855 after = {}
1857 after = {}
1856
1858
1857 # Expected shell script output.
1859 # Expected shell script output.
1858 expected = {}
1860 expected = {}
1859
1861
1860 pos = prepos = -1
1862 pos = prepos = -1
1861
1863
1862 # True or False when in a true or false conditional section
1864 # True or False when in a true or false conditional section
1863 skipping = None
1865 skipping = None
1864
1866
1865 # We keep track of whether or not we're in a Python block so we
1867 # We keep track of whether or not we're in a Python block so we
1866 # can generate the surrounding doctest magic.
1868 # can generate the surrounding doctest magic.
1867 inpython = False
1869 inpython = False
1868
1870
1869 if self._debug:
1871 if self._debug:
1870 script.append(b'set -x\n')
1872 script.append(b'set -x\n')
1871 if os.getenv('MSYSTEM'):
1873 if os.getenv('MSYSTEM'):
1872 script.append(b'alias pwd="pwd -W"\n')
1874 script.append(b'alias pwd="pwd -W"\n')
1873
1875
1874 if hgcatapult and hgcatapult != os.devnull:
1876 if hgcatapult and hgcatapult != os.devnull:
1875 if PYTHON3:
1877 if PYTHON3:
1876 hgcatapult = hgcatapult.encode('utf8')
1878 hgcatapult = hgcatapult.encode('utf8')
1877 cataname = self.name.encode('utf8')
1879 cataname = self.name.encode('utf8')
1878 else:
1880 else:
1879 cataname = self.name
1881 cataname = self.name
1880
1882
1881 # Kludge: use a while loop to keep the pipe from getting
1883 # Kludge: use a while loop to keep the pipe from getting
1882 # closed by our echo commands. The still-running file gets
1884 # closed by our echo commands. The still-running file gets
1883 # reaped at the end of the script, which causes the while
1885 # reaped at the end of the script, which causes the while
1884 # loop to exit and closes the pipe. Sigh.
1886 # loop to exit and closes the pipe. Sigh.
1885 script.append(
1887 script.append(
1886 b'rtendtracing() {\n'
1888 b'rtendtracing() {\n'
1887 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1889 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1888 b' rm -f "$TESTTMP/.still-running"\n'
1890 b' rm -f "$TESTTMP/.still-running"\n'
1889 b'}\n'
1891 b'}\n'
1890 b'trap "rtendtracing" 0\n'
1892 b'trap "rtendtracing" 0\n'
1891 b'touch "$TESTTMP/.still-running"\n'
1893 b'touch "$TESTTMP/.still-running"\n'
1892 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1894 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1893 b'> %(catapult)s &\n'
1895 b'> %(catapult)s &\n'
1894 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1896 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1895 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1897 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1896 % {
1898 % {
1897 b'name': cataname,
1899 b'name': cataname,
1898 b'session': session,
1900 b'session': session,
1899 b'catapult': hgcatapult,
1901 b'catapult': hgcatapult,
1900 }
1902 }
1901 )
1903 )
1902
1904
1903 if self._case:
1905 if self._case:
1904 casestr = b'#'.join(self._case)
1906 casestr = b'#'.join(self._case)
1905 if isinstance(casestr, str):
1907 if isinstance(casestr, str):
1906 quoted = shellquote(casestr)
1908 quoted = shellquote(casestr)
1907 else:
1909 else:
1908 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1910 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1909 script.append(b'TESTCASE=%s\n' % quoted)
1911 script.append(b'TESTCASE=%s\n' % quoted)
1910 script.append(b'export TESTCASE\n')
1912 script.append(b'export TESTCASE\n')
1911
1913
1912 n = 0
1914 n = 0
1913 for n, l in enumerate(lines):
1915 for n, l in enumerate(lines):
1914 if not l.endswith(b'\n'):
1916 if not l.endswith(b'\n'):
1915 l += b'\n'
1917 l += b'\n'
1916 if l.startswith(b'#require'):
1918 if l.startswith(b'#require'):
1917 lsplit = l.split()
1919 lsplit = l.split()
1918 if len(lsplit) < 2 or lsplit[0] != b'#require':
1920 if len(lsplit) < 2 or lsplit[0] != b'#require':
1919 after.setdefault(pos, []).append(
1921 after.setdefault(pos, []).append(
1920 b' !!! invalid #require\n'
1922 b' !!! invalid #require\n'
1921 )
1923 )
1922 if not skipping:
1924 if not skipping:
1923 haveresult, message = self._hghave(lsplit[1:])
1925 haveresult, message = self._hghave(lsplit[1:])
1924 if not haveresult:
1926 if not haveresult:
1925 script = [b'echo "%s"\nexit 80\n' % message]
1927 script = [b'echo "%s"\nexit 80\n' % message]
1926 break
1928 break
1927 after.setdefault(pos, []).append(l)
1929 after.setdefault(pos, []).append(l)
1928 elif l.startswith(b'#if'):
1930 elif l.startswith(b'#if'):
1929 lsplit = l.split()
1931 lsplit = l.split()
1930 if len(lsplit) < 2 or lsplit[0] != b'#if':
1932 if len(lsplit) < 2 or lsplit[0] != b'#if':
1931 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1933 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1932 if skipping is not None:
1934 if skipping is not None:
1933 after.setdefault(pos, []).append(b' !!! nested #if\n')
1935 after.setdefault(pos, []).append(b' !!! nested #if\n')
1934 skipping = not self._iftest(lsplit[1:])
1936 skipping = not self._iftest(lsplit[1:])
1935 after.setdefault(pos, []).append(l)
1937 after.setdefault(pos, []).append(l)
1936 elif l.startswith(b'#else'):
1938 elif l.startswith(b'#else'):
1937 if skipping is None:
1939 if skipping is None:
1938 after.setdefault(pos, []).append(b' !!! missing #if\n')
1940 after.setdefault(pos, []).append(b' !!! missing #if\n')
1939 skipping = not skipping
1941 skipping = not skipping
1940 after.setdefault(pos, []).append(l)
1942 after.setdefault(pos, []).append(l)
1941 elif l.startswith(b'#endif'):
1943 elif l.startswith(b'#endif'):
1942 if skipping is None:
1944 if skipping is None:
1943 after.setdefault(pos, []).append(b' !!! missing #if\n')
1945 after.setdefault(pos, []).append(b' !!! missing #if\n')
1944 skipping = None
1946 skipping = None
1945 after.setdefault(pos, []).append(l)
1947 after.setdefault(pos, []).append(l)
1946 elif skipping:
1948 elif skipping:
1947 after.setdefault(pos, []).append(l)
1949 after.setdefault(pos, []).append(l)
1948 elif l.startswith(b' >>> '): # python inlines
1950 elif l.startswith(b' >>> '): # python inlines
1949 after.setdefault(pos, []).append(l)
1951 after.setdefault(pos, []).append(l)
1950 prepos = pos
1952 prepos = pos
1951 pos = n
1953 pos = n
1952 if not inpython:
1954 if not inpython:
1953 # We've just entered a Python block. Add the header.
1955 # We've just entered a Python block. Add the header.
1954 inpython = True
1956 inpython = True
1955 addsalt(prepos, False) # Make sure we report the exit code.
1957 addsalt(prepos, False) # Make sure we report the exit code.
1956 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1958 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1957 addsalt(n, True)
1959 addsalt(n, True)
1958 script.append(l[2:])
1960 script.append(l[2:])
1959 elif l.startswith(b' ... '): # python inlines
1961 elif l.startswith(b' ... '): # python inlines
1960 after.setdefault(prepos, []).append(l)
1962 after.setdefault(prepos, []).append(l)
1961 script.append(l[2:])
1963 script.append(l[2:])
1962 elif l.startswith(b' $ '): # commands
1964 elif l.startswith(b' $ '): # commands
1963 if inpython:
1965 if inpython:
1964 script.append(b'EOF\n')
1966 script.append(b'EOF\n')
1965 inpython = False
1967 inpython = False
1966 after.setdefault(pos, []).append(l)
1968 after.setdefault(pos, []).append(l)
1967 prepos = pos
1969 prepos = pos
1968 pos = n
1970 pos = n
1969 addsalt(n, False)
1971 addsalt(n, False)
1970 rawcmd = l[4:]
1972 rawcmd = l[4:]
1971 cmd = rawcmd.split()
1973 cmd = rawcmd.split()
1972 toggletrace(rawcmd)
1974 toggletrace(rawcmd)
1973 if len(cmd) == 2 and cmd[0] == b'cd':
1975 if len(cmd) == 2 and cmd[0] == b'cd':
1974 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1976 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1975 script.append(rawcmd)
1977 script.append(rawcmd)
1976 elif l.startswith(b' > '): # continuations
1978 elif l.startswith(b' > '): # continuations
1977 after.setdefault(prepos, []).append(l)
1979 after.setdefault(prepos, []).append(l)
1978 script.append(l[4:])
1980 script.append(l[4:])
1979 elif l.startswith(b' '): # results
1981 elif l.startswith(b' '): # results
1980 # Queue up a list of expected results.
1982 # Queue up a list of expected results.
1981 expected.setdefault(pos, []).append(l[2:])
1983 expected.setdefault(pos, []).append(l[2:])
1982 else:
1984 else:
1983 if inpython:
1985 if inpython:
1984 script.append(b'EOF\n')
1986 script.append(b'EOF\n')
1985 inpython = False
1987 inpython = False
1986 # Non-command/result. Queue up for merged output.
1988 # Non-command/result. Queue up for merged output.
1987 after.setdefault(pos, []).append(l)
1989 after.setdefault(pos, []).append(l)
1988
1990
1989 if inpython:
1991 if inpython:
1990 script.append(b'EOF\n')
1992 script.append(b'EOF\n')
1991 if skipping is not None:
1993 if skipping is not None:
1992 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1994 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1993 addsalt(n + 1, False)
1995 addsalt(n + 1, False)
1994 # Need to end any current per-command trace
1996 # Need to end any current per-command trace
1995 if activetrace:
1997 if activetrace:
1996 toggletrace()
1998 toggletrace()
1997 return salt, script, after, expected
1999 return salt, script, after, expected
1998
2000
1999 def _processoutput(self, exitcode, output, salt, after, expected):
2001 def _processoutput(self, exitcode, output, salt, after, expected):
2000 # Merge the script output back into a unified test.
2002 # Merge the script output back into a unified test.
2001 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
2003 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
2002 if exitcode != 0:
2004 if exitcode != 0:
2003 warnonly = WARN_NO
2005 warnonly = WARN_NO
2004
2006
2005 pos = -1
2007 pos = -1
2006 postout = []
2008 postout = []
2007 for out_rawline in output:
2009 for out_rawline in output:
2008 out_line, cmd_line = out_rawline, None
2010 out_line, cmd_line = out_rawline, None
2009 if salt in out_rawline:
2011 if salt in out_rawline:
2010 out_line, cmd_line = out_rawline.split(salt, 1)
2012 out_line, cmd_line = out_rawline.split(salt, 1)
2011
2013
2012 pos, postout, warnonly = self._process_out_line(
2014 pos, postout, warnonly = self._process_out_line(
2013 out_line, pos, postout, expected, warnonly
2015 out_line, pos, postout, expected, warnonly
2014 )
2016 )
2015 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2017 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2016
2018
2017 if pos in after:
2019 if pos in after:
2018 postout += after.pop(pos)
2020 postout += after.pop(pos)
2019
2021
2020 if warnonly == WARN_YES:
2022 if warnonly == WARN_YES:
2021 exitcode = False # Set exitcode to warned.
2023 exitcode = False # Set exitcode to warned.
2022
2024
2023 return exitcode, postout
2025 return exitcode, postout
2024
2026
2025 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2027 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2026 while out_line:
2028 while out_line:
2027 if not out_line.endswith(b'\n'):
2029 if not out_line.endswith(b'\n'):
2028 out_line += b' (no-eol)\n'
2030 out_line += b' (no-eol)\n'
2029
2031
2030 # Find the expected output at the current position.
2032 # Find the expected output at the current position.
2031 els = [None]
2033 els = [None]
2032 if expected.get(pos, None):
2034 if expected.get(pos, None):
2033 els = expected[pos]
2035 els = expected[pos]
2034
2036
2035 optional = []
2037 optional = []
2036 for i, el in enumerate(els):
2038 for i, el in enumerate(els):
2037 r = False
2039 r = False
2038 if el:
2040 if el:
2039 r, exact = self.linematch(el, out_line)
2041 r, exact = self.linematch(el, out_line)
2040 if isinstance(r, str):
2042 if isinstance(r, str):
2041 if r == '-glob':
2043 if r == '-glob':
2042 out_line = ''.join(el.rsplit(' (glob)', 1))
2044 out_line = ''.join(el.rsplit(' (glob)', 1))
2043 r = '' # Warn only this line.
2045 r = '' # Warn only this line.
2044 elif r == "retry":
2046 elif r == "retry":
2045 postout.append(b' ' + el)
2047 postout.append(b' ' + el)
2046 else:
2048 else:
2047 log('\ninfo, unknown linematch result: %r\n' % r)
2049 log('\ninfo, unknown linematch result: %r\n' % r)
2048 r = False
2050 r = False
2049 if r:
2051 if r:
2050 els.pop(i)
2052 els.pop(i)
2051 break
2053 break
2052 if el:
2054 if el:
2053 if isoptional(el):
2055 if isoptional(el):
2054 optional.append(i)
2056 optional.append(i)
2055 else:
2057 else:
2056 m = optline.match(el)
2058 m = optline.match(el)
2057 if m:
2059 if m:
2058 conditions = [c for c in m.group(2).split(b' ')]
2060 conditions = [c for c in m.group(2).split(b' ')]
2059
2061
2060 if not self._iftest(conditions):
2062 if not self._iftest(conditions):
2061 optional.append(i)
2063 optional.append(i)
2062 if exact:
2064 if exact:
2063 # Don't allow line to be matches against a later
2065 # Don't allow line to be matches against a later
2064 # line in the output
2066 # line in the output
2065 els.pop(i)
2067 els.pop(i)
2066 break
2068 break
2067
2069
2068 if r:
2070 if r:
2069 if r == "retry":
2071 if r == "retry":
2070 continue
2072 continue
2071 # clean up any optional leftovers
2073 # clean up any optional leftovers
2072 for i in optional:
2074 for i in optional:
2073 postout.append(b' ' + els[i])
2075 postout.append(b' ' + els[i])
2074 for i in reversed(optional):
2076 for i in reversed(optional):
2075 del els[i]
2077 del els[i]
2076 postout.append(b' ' + el)
2078 postout.append(b' ' + el)
2077 else:
2079 else:
2078 if self.NEEDESCAPE(out_line):
2080 if self.NEEDESCAPE(out_line):
2079 out_line = TTest._stringescape(
2081 out_line = TTest._stringescape(
2080 b'%s (esc)\n' % out_line.rstrip(b'\n')
2082 b'%s (esc)\n' % out_line.rstrip(b'\n')
2081 )
2083 )
2082 postout.append(b' ' + out_line) # Let diff deal with it.
2084 postout.append(b' ' + out_line) # Let diff deal with it.
2083 if r != '': # If line failed.
2085 if r != '': # If line failed.
2084 warnonly = WARN_NO
2086 warnonly = WARN_NO
2085 elif warnonly == WARN_UNDEFINED:
2087 elif warnonly == WARN_UNDEFINED:
2086 warnonly = WARN_YES
2088 warnonly = WARN_YES
2087 break
2089 break
2088 else:
2090 else:
2089 # clean up any optional leftovers
2091 # clean up any optional leftovers
2090 while expected.get(pos, None):
2092 while expected.get(pos, None):
2091 el = expected[pos].pop(0)
2093 el = expected[pos].pop(0)
2092 if el:
2094 if el:
2093 if not isoptional(el):
2095 if not isoptional(el):
2094 m = optline.match(el)
2096 m = optline.match(el)
2095 if m:
2097 if m:
2096 conditions = [c for c in m.group(2).split(b' ')]
2098 conditions = [c for c in m.group(2).split(b' ')]
2097
2099
2098 if self._iftest(conditions):
2100 if self._iftest(conditions):
2099 # Don't append as optional line
2101 # Don't append as optional line
2100 continue
2102 continue
2101 else:
2103 else:
2102 continue
2104 continue
2103 postout.append(b' ' + el)
2105 postout.append(b' ' + el)
2104 return pos, postout, warnonly
2106 return pos, postout, warnonly
2105
2107
2106 def _process_cmd_line(self, cmd_line, pos, postout, after):
2108 def _process_cmd_line(self, cmd_line, pos, postout, after):
2107 """process a "command" part of a line from unified test output"""
2109 """process a "command" part of a line from unified test output"""
2108 if cmd_line:
2110 if cmd_line:
2109 # Add on last return code.
2111 # Add on last return code.
2110 ret = int(cmd_line.split()[1])
2112 ret = int(cmd_line.split()[1])
2111 if ret != 0:
2113 if ret != 0:
2112 postout.append(b' [%d]\n' % ret)
2114 postout.append(b' [%d]\n' % ret)
2113 if pos in after:
2115 if pos in after:
2114 # Merge in non-active test bits.
2116 # Merge in non-active test bits.
2115 postout += after.pop(pos)
2117 postout += after.pop(pos)
2116 pos = int(cmd_line.split()[0])
2118 pos = int(cmd_line.split()[0])
2117 return pos, postout
2119 return pos, postout
2118
2120
2119 @staticmethod
2121 @staticmethod
2120 def rematch(el, l):
2122 def rematch(el, l):
2121 try:
2123 try:
2122 # parse any flags at the beginning of the regex. Only 'i' is
2124 # parse any flags at the beginning of the regex. Only 'i' is
2123 # supported right now, but this should be easy to extend.
2125 # supported right now, but this should be easy to extend.
2124 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2126 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2125 flags = flags or b''
2127 flags = flags or b''
2126 el = flags + b'(?:' + el + b')'
2128 el = flags + b'(?:' + el + b')'
2127 # use \Z to ensure that the regex matches to the end of the string
2129 # use \Z to ensure that the regex matches to the end of the string
2128 if WINDOWS:
2130 if WINDOWS:
2129 return re.match(el + br'\r?\n\Z', l)
2131 return re.match(el + br'\r?\n\Z', l)
2130 return re.match(el + br'\n\Z', l)
2132 return re.match(el + br'\n\Z', l)
2131 except re.error:
2133 except re.error:
2132 # el is an invalid regex
2134 # el is an invalid regex
2133 return False
2135 return False
2134
2136
2135 @staticmethod
2137 @staticmethod
2136 def globmatch(el, l):
2138 def globmatch(el, l):
2137 # The only supported special characters are * and ? plus / which also
2139 # The only supported special characters are * and ? plus / which also
2138 # matches \ on windows. Escaping of these characters is supported.
2140 # matches \ on windows. Escaping of these characters is supported.
2139 if el + b'\n' == l:
2141 if el + b'\n' == l:
2140 if os.altsep:
2142 if os.altsep:
2141 # matching on "/" is not needed for this line
2143 # matching on "/" is not needed for this line
2142 for pat in checkcodeglobpats:
2144 for pat in checkcodeglobpats:
2143 if pat.match(el):
2145 if pat.match(el):
2144 return True
2146 return True
2145 return b'-glob'
2147 return b'-glob'
2146 return True
2148 return True
2147 el = el.replace(b'$LOCALIP', b'*')
2149 el = el.replace(b'$LOCALIP', b'*')
2148 i, n = 0, len(el)
2150 i, n = 0, len(el)
2149 res = b''
2151 res = b''
2150 while i < n:
2152 while i < n:
2151 c = el[i : i + 1]
2153 c = el[i : i + 1]
2152 i += 1
2154 i += 1
2153 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2155 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2154 res += el[i - 1 : i + 1]
2156 res += el[i - 1 : i + 1]
2155 i += 1
2157 i += 1
2156 elif c == b'*':
2158 elif c == b'*':
2157 res += b'.*'
2159 res += b'.*'
2158 elif c == b'?':
2160 elif c == b'?':
2159 res += b'.'
2161 res += b'.'
2160 elif c == b'/' and os.altsep:
2162 elif c == b'/' and os.altsep:
2161 res += b'[/\\\\]'
2163 res += b'[/\\\\]'
2162 else:
2164 else:
2163 res += re.escape(c)
2165 res += re.escape(c)
2164 return TTest.rematch(res, l)
2166 return TTest.rematch(res, l)
2165
2167
2166 def linematch(self, el, l):
2168 def linematch(self, el, l):
2167 if el == l: # perfect match (fast)
2169 if el == l: # perfect match (fast)
2168 return True, True
2170 return True, True
2169 retry = False
2171 retry = False
2170 if isoptional(el):
2172 if isoptional(el):
2171 retry = "retry"
2173 retry = "retry"
2172 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2174 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2173 else:
2175 else:
2174 m = optline.match(el)
2176 m = optline.match(el)
2175 if m:
2177 if m:
2176 conditions = [c for c in m.group(2).split(b' ')]
2178 conditions = [c for c in m.group(2).split(b' ')]
2177
2179
2178 el = m.group(1) + b"\n"
2180 el = m.group(1) + b"\n"
2179 if not self._iftest(conditions):
2181 if not self._iftest(conditions):
2180 # listed feature missing, should not match
2182 # listed feature missing, should not match
2181 return "retry", False
2183 return "retry", False
2182
2184
2183 if el.endswith(b" (esc)\n"):
2185 if el.endswith(b" (esc)\n"):
2184 if PYTHON3:
2186 if PYTHON3:
2185 el = el[:-7].decode('unicode_escape') + '\n'
2187 el = el[:-7].decode('unicode_escape') + '\n'
2186 el = el.encode('latin-1')
2188 el = el.encode('latin-1')
2187 else:
2189 else:
2188 el = el[:-7].decode('string-escape') + '\n'
2190 el = el[:-7].decode('string-escape') + '\n'
2189 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2191 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2190 return True, True
2192 return True, True
2191 if el.endswith(b" (re)\n"):
2193 if el.endswith(b" (re)\n"):
2192 return (TTest.rematch(el[:-6], l) or retry), False
2194 return (TTest.rematch(el[:-6], l) or retry), False
2193 if el.endswith(b" (glob)\n"):
2195 if el.endswith(b" (glob)\n"):
2194 # ignore '(glob)' added to l by 'replacements'
2196 # ignore '(glob)' added to l by 'replacements'
2195 if l.endswith(b" (glob)\n"):
2197 if l.endswith(b" (glob)\n"):
2196 l = l[:-8] + b"\n"
2198 l = l[:-8] + b"\n"
2197 return (TTest.globmatch(el[:-8], l) or retry), False
2199 return (TTest.globmatch(el[:-8], l) or retry), False
2198 if os.altsep:
2200 if os.altsep:
2199 _l = l.replace(b'\\', b'/')
2201 _l = l.replace(b'\\', b'/')
2200 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2202 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2201 return True, True
2203 return True, True
2202 return retry, True
2204 return retry, True
2203
2205
2204 @staticmethod
2206 @staticmethod
2205 def parsehghaveoutput(lines):
2207 def parsehghaveoutput(lines):
2206 """Parse hghave log lines.
2208 """Parse hghave log lines.
2207
2209
2208 Return tuple of lists (missing, failed):
2210 Return tuple of lists (missing, failed):
2209 * the missing/unknown features
2211 * the missing/unknown features
2210 * the features for which existence check failed"""
2212 * the features for which existence check failed"""
2211 missing = []
2213 missing = []
2212 failed = []
2214 failed = []
2213 for line in lines:
2215 for line in lines:
2214 if line.startswith(TTest.SKIPPED_PREFIX):
2216 if line.startswith(TTest.SKIPPED_PREFIX):
2215 line = line.splitlines()[0]
2217 line = line.splitlines()[0]
2216 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2218 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2217 elif line.startswith(TTest.FAILED_PREFIX):
2219 elif line.startswith(TTest.FAILED_PREFIX):
2218 line = line.splitlines()[0]
2220 line = line.splitlines()[0]
2219 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2221 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2220
2222
2221 return missing, failed
2223 return missing, failed
2222
2224
2223 @staticmethod
2225 @staticmethod
2224 def _escapef(m):
2226 def _escapef(m):
2225 return TTest.ESCAPEMAP[m.group(0)]
2227 return TTest.ESCAPEMAP[m.group(0)]
2226
2228
2227 @staticmethod
2229 @staticmethod
2228 def _stringescape(s):
2230 def _stringescape(s):
2229 return TTest.ESCAPESUB(TTest._escapef, s)
2231 return TTest.ESCAPESUB(TTest._escapef, s)
2230
2232
2231
2233
2232 iolock = threading.RLock()
2234 iolock = threading.RLock()
2233 firstlock = threading.RLock()
2235 firstlock = threading.RLock()
2234 firsterror = False
2236 firsterror = False
2235
2237
2236
2238
2237 class TestResult(unittest._TextTestResult):
2239 class TestResult(unittest._TextTestResult):
2238 """Holds results when executing via unittest."""
2240 """Holds results when executing via unittest."""
2239
2241
2240 # Don't worry too much about accessing the non-public _TextTestResult.
2242 # Don't worry too much about accessing the non-public _TextTestResult.
2241 # It is relatively common in Python testing tools.
2243 # It is relatively common in Python testing tools.
2242 def __init__(self, options, *args, **kwargs):
2244 def __init__(self, options, *args, **kwargs):
2243 super(TestResult, self).__init__(*args, **kwargs)
2245 super(TestResult, self).__init__(*args, **kwargs)
2244
2246
2245 self._options = options
2247 self._options = options
2246
2248
2247 # unittest.TestResult didn't have skipped until 2.7. We need to
2249 # unittest.TestResult didn't have skipped until 2.7. We need to
2248 # polyfill it.
2250 # polyfill it.
2249 self.skipped = []
2251 self.skipped = []
2250
2252
2251 # We have a custom "ignored" result that isn't present in any Python
2253 # We have a custom "ignored" result that isn't present in any Python
2252 # unittest implementation. It is very similar to skipped. It may make
2254 # unittest implementation. It is very similar to skipped. It may make
2253 # sense to map it into skip some day.
2255 # sense to map it into skip some day.
2254 self.ignored = []
2256 self.ignored = []
2255
2257
2256 self.times = []
2258 self.times = []
2257 self._firststarttime = None
2259 self._firststarttime = None
2258 # Data stored for the benefit of generating xunit reports.
2260 # Data stored for the benefit of generating xunit reports.
2259 self.successes = []
2261 self.successes = []
2260 self.faildata = {}
2262 self.faildata = {}
2261
2263
2262 if options.color == 'auto':
2264 if options.color == 'auto':
2263 isatty = self.stream.isatty()
2265 isatty = self.stream.isatty()
2264 # For some reason, redirecting stdout on Windows disables the ANSI
2266 # For some reason, redirecting stdout on Windows disables the ANSI
2265 # color processing of stderr, which is what is used to print the
2267 # color processing of stderr, which is what is used to print the
2266 # output. Therefore, both must be tty on Windows to enable color.
2268 # output. Therefore, both must be tty on Windows to enable color.
2267 if WINDOWS:
2269 if WINDOWS:
2268 isatty = isatty and sys.stdout.isatty()
2270 isatty = isatty and sys.stdout.isatty()
2269 self.color = pygmentspresent and isatty
2271 self.color = pygmentspresent and isatty
2270 elif options.color == 'never':
2272 elif options.color == 'never':
2271 self.color = False
2273 self.color = False
2272 else: # 'always', for testing purposes
2274 else: # 'always', for testing purposes
2273 self.color = pygmentspresent
2275 self.color = pygmentspresent
2274
2276
2275 def onStart(self, test):
2277 def onStart(self, test):
2276 """Can be overriden by custom TestResult"""
2278 """Can be overriden by custom TestResult"""
2277
2279
2278 def onEnd(self):
2280 def onEnd(self):
2279 """Can be overriden by custom TestResult"""
2281 """Can be overriden by custom TestResult"""
2280
2282
2281 def addFailure(self, test, reason):
2283 def addFailure(self, test, reason):
2282 self.failures.append((test, reason))
2284 self.failures.append((test, reason))
2283
2285
2284 if self._options.first:
2286 if self._options.first:
2285 self.stop()
2287 self.stop()
2286 else:
2288 else:
2287 with iolock:
2289 with iolock:
2288 if reason == "timed out":
2290 if reason == "timed out":
2289 self.stream.write('t')
2291 self.stream.write('t')
2290 else:
2292 else:
2291 if not self._options.nodiff:
2293 if not self._options.nodiff:
2292 self.stream.write('\n')
2294 self.stream.write('\n')
2293 # Exclude the '\n' from highlighting to lex correctly
2295 # Exclude the '\n' from highlighting to lex correctly
2294 formatted = 'ERROR: %s output changed\n' % test
2296 formatted = 'ERROR: %s output changed\n' % test
2295 self.stream.write(highlightmsg(formatted, self.color))
2297 self.stream.write(highlightmsg(formatted, self.color))
2296 self.stream.write('!')
2298 self.stream.write('!')
2297
2299
2298 self.stream.flush()
2300 self.stream.flush()
2299
2301
2300 def addSuccess(self, test):
2302 def addSuccess(self, test):
2301 with iolock:
2303 with iolock:
2302 super(TestResult, self).addSuccess(test)
2304 super(TestResult, self).addSuccess(test)
2303 self.successes.append(test)
2305 self.successes.append(test)
2304
2306
2305 def addError(self, test, err):
2307 def addError(self, test, err):
2306 super(TestResult, self).addError(test, err)
2308 super(TestResult, self).addError(test, err)
2307 if self._options.first:
2309 if self._options.first:
2308 self.stop()
2310 self.stop()
2309
2311
2310 # Polyfill.
2312 # Polyfill.
2311 def addSkip(self, test, reason):
2313 def addSkip(self, test, reason):
2312 self.skipped.append((test, reason))
2314 self.skipped.append((test, reason))
2313 with iolock:
2315 with iolock:
2314 if self.showAll:
2316 if self.showAll:
2315 self.stream.writeln('skipped %s' % reason)
2317 self.stream.writeln('skipped %s' % reason)
2316 else:
2318 else:
2317 self.stream.write('s')
2319 self.stream.write('s')
2318 self.stream.flush()
2320 self.stream.flush()
2319
2321
2320 def addIgnore(self, test, reason):
2322 def addIgnore(self, test, reason):
2321 self.ignored.append((test, reason))
2323 self.ignored.append((test, reason))
2322 with iolock:
2324 with iolock:
2323 if self.showAll:
2325 if self.showAll:
2324 self.stream.writeln('ignored %s' % reason)
2326 self.stream.writeln('ignored %s' % reason)
2325 else:
2327 else:
2326 if reason not in ('not retesting', "doesn't match keyword"):
2328 if reason not in ('not retesting', "doesn't match keyword"):
2327 self.stream.write('i')
2329 self.stream.write('i')
2328 else:
2330 else:
2329 self.testsRun += 1
2331 self.testsRun += 1
2330 self.stream.flush()
2332 self.stream.flush()
2331
2333
2332 def addOutputMismatch(self, test, ret, got, expected):
2334 def addOutputMismatch(self, test, ret, got, expected):
2333 """Record a mismatch in test output for a particular test."""
2335 """Record a mismatch in test output for a particular test."""
2334 if self.shouldStop or firsterror:
2336 if self.shouldStop or firsterror:
2335 # don't print, some other test case already failed and
2337 # don't print, some other test case already failed and
2336 # printed, we're just stale and probably failed due to our
2338 # printed, we're just stale and probably failed due to our
2337 # temp dir getting cleaned up.
2339 # temp dir getting cleaned up.
2338 return
2340 return
2339
2341
2340 accepted = False
2342 accepted = False
2341 lines = []
2343 lines = []
2342
2344
2343 with iolock:
2345 with iolock:
2344 if self._options.nodiff:
2346 if self._options.nodiff:
2345 pass
2347 pass
2346 elif self._options.view:
2348 elif self._options.view:
2347 v = self._options.view
2349 v = self._options.view
2348 subprocess.call(
2350 subprocess.call(
2349 r'"%s" "%s" "%s"'
2351 r'"%s" "%s" "%s"'
2350 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2352 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2351 shell=True,
2353 shell=True,
2352 )
2354 )
2353 else:
2355 else:
2354 servefail, lines = getdiff(
2356 servefail, lines = getdiff(
2355 expected, got, test.refpath, test.errpath
2357 expected, got, test.refpath, test.errpath
2356 )
2358 )
2357 self.stream.write('\n')
2359 self.stream.write('\n')
2358 for line in lines:
2360 for line in lines:
2359 line = highlightdiff(line, self.color)
2361 line = highlightdiff(line, self.color)
2360 if PYTHON3:
2362 if PYTHON3:
2361 self.stream.flush()
2363 self.stream.flush()
2362 self.stream.buffer.write(line)
2364 self.stream.buffer.write(line)
2363 self.stream.buffer.flush()
2365 self.stream.buffer.flush()
2364 else:
2366 else:
2365 self.stream.write(line)
2367 self.stream.write(line)
2366 self.stream.flush()
2368 self.stream.flush()
2367
2369
2368 if servefail:
2370 if servefail:
2369 raise test.failureException(
2371 raise test.failureException(
2370 'server failed to start (HGPORT=%s)' % test._startport
2372 'server failed to start (HGPORT=%s)' % test._startport
2371 )
2373 )
2372
2374
2373 # handle interactive prompt without releasing iolock
2375 # handle interactive prompt without releasing iolock
2374 if self._options.interactive:
2376 if self._options.interactive:
2375 if test.readrefout() != expected:
2377 if test.readrefout() != expected:
2376 self.stream.write(
2378 self.stream.write(
2377 'Reference output has changed (run again to prompt '
2379 'Reference output has changed (run again to prompt '
2378 'changes)'
2380 'changes)'
2379 )
2381 )
2380 else:
2382 else:
2381 self.stream.write('Accept this change? [y/N] ')
2383 self.stream.write('Accept this change? [y/N] ')
2382 self.stream.flush()
2384 self.stream.flush()
2383 answer = sys.stdin.readline().strip()
2385 answer = sys.stdin.readline().strip()
2384 if answer.lower() in ('y', 'yes'):
2386 if answer.lower() in ('y', 'yes'):
2385 if test.path.endswith(b'.t'):
2387 if test.path.endswith(b'.t'):
2386 rename(test.errpath, test.path)
2388 rename(test.errpath, test.path)
2387 else:
2389 else:
2388 rename(test.errpath, b'%s.out' % test.path)
2390 rename(test.errpath, b'%s.out' % test.path)
2389 accepted = True
2391 accepted = True
2390 if not accepted:
2392 if not accepted:
2391 self.faildata[test.name] = b''.join(lines)
2393 self.faildata[test.name] = b''.join(lines)
2392
2394
2393 return accepted
2395 return accepted
2394
2396
2395 def startTest(self, test):
2397 def startTest(self, test):
2396 super(TestResult, self).startTest(test)
2398 super(TestResult, self).startTest(test)
2397
2399
2398 # os.times module computes the user time and system time spent by
2400 # os.times module computes the user time and system time spent by
2399 # child's processes along with real elapsed time taken by a process.
2401 # child's processes along with real elapsed time taken by a process.
2400 # This module has one limitation. It can only work for Linux user
2402 # This module has one limitation. It can only work for Linux user
2401 # and not for Windows. Hence why we fall back to another function
2403 # and not for Windows. Hence why we fall back to another function
2402 # for wall time calculations.
2404 # for wall time calculations.
2403 test.started_times = os.times()
2405 test.started_times = os.times()
2404 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2406 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2405 test.started_time = time.time()
2407 test.started_time = time.time()
2406 if self._firststarttime is None: # thread racy but irrelevant
2408 if self._firststarttime is None: # thread racy but irrelevant
2407 self._firststarttime = test.started_time
2409 self._firststarttime = test.started_time
2408
2410
2409 def stopTest(self, test, interrupted=False):
2411 def stopTest(self, test, interrupted=False):
2410 super(TestResult, self).stopTest(test)
2412 super(TestResult, self).stopTest(test)
2411
2413
2412 test.stopped_times = os.times()
2414 test.stopped_times = os.times()
2413 stopped_time = time.time()
2415 stopped_time = time.time()
2414
2416
2415 starttime = test.started_times
2417 starttime = test.started_times
2416 endtime = test.stopped_times
2418 endtime = test.stopped_times
2417 origin = self._firststarttime
2419 origin = self._firststarttime
2418 self.times.append(
2420 self.times.append(
2419 (
2421 (
2420 test.name,
2422 test.name,
2421 endtime[2] - starttime[2], # user space CPU time
2423 endtime[2] - starttime[2], # user space CPU time
2422 endtime[3] - starttime[3], # sys space CPU time
2424 endtime[3] - starttime[3], # sys space CPU time
2423 stopped_time - test.started_time, # real time
2425 stopped_time - test.started_time, # real time
2424 test.started_time - origin, # start date in run context
2426 test.started_time - origin, # start date in run context
2425 stopped_time - origin, # end date in run context
2427 stopped_time - origin, # end date in run context
2426 )
2428 )
2427 )
2429 )
2428
2430
2429 if interrupted:
2431 if interrupted:
2430 with iolock:
2432 with iolock:
2431 self.stream.writeln(
2433 self.stream.writeln(
2432 'INTERRUPTED: %s (after %d seconds)'
2434 'INTERRUPTED: %s (after %d seconds)'
2433 % (test.name, self.times[-1][3])
2435 % (test.name, self.times[-1][3])
2434 )
2436 )
2435
2437
2436
2438
2437 def getTestResult():
2439 def getTestResult():
2438 """
2440 """
2439 Returns the relevant test result
2441 Returns the relevant test result
2440 """
2442 """
2441 if "CUSTOM_TEST_RESULT" in os.environ:
2443 if "CUSTOM_TEST_RESULT" in os.environ:
2442 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2444 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2443 return testresultmodule.TestResult
2445 return testresultmodule.TestResult
2444 else:
2446 else:
2445 return TestResult
2447 return TestResult
2446
2448
2447
2449
2448 class TestSuite(unittest.TestSuite):
2450 class TestSuite(unittest.TestSuite):
2449 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2451 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2450
2452
2451 def __init__(
2453 def __init__(
2452 self,
2454 self,
2453 testdir,
2455 testdir,
2454 jobs=1,
2456 jobs=1,
2455 whitelist=None,
2457 whitelist=None,
2456 blacklist=None,
2458 blacklist=None,
2457 keywords=None,
2459 keywords=None,
2458 loop=False,
2460 loop=False,
2459 runs_per_test=1,
2461 runs_per_test=1,
2460 loadtest=None,
2462 loadtest=None,
2461 showchannels=False,
2463 showchannels=False,
2462 *args,
2464 *args,
2463 **kwargs
2465 **kwargs
2464 ):
2466 ):
2465 """Create a new instance that can run tests with a configuration.
2467 """Create a new instance that can run tests with a configuration.
2466
2468
2467 testdir specifies the directory where tests are executed from. This
2469 testdir specifies the directory where tests are executed from. This
2468 is typically the ``tests`` directory from Mercurial's source
2470 is typically the ``tests`` directory from Mercurial's source
2469 repository.
2471 repository.
2470
2472
2471 jobs specifies the number of jobs to run concurrently. Each test
2473 jobs specifies the number of jobs to run concurrently. Each test
2472 executes on its own thread. Tests actually spawn new processes, so
2474 executes on its own thread. Tests actually spawn new processes, so
2473 state mutation should not be an issue.
2475 state mutation should not be an issue.
2474
2476
2475 If there is only one job, it will use the main thread.
2477 If there is only one job, it will use the main thread.
2476
2478
2477 whitelist and blacklist denote tests that have been whitelisted and
2479 whitelist and blacklist denote tests that have been whitelisted and
2478 blacklisted, respectively. These arguments don't belong in TestSuite.
2480 blacklisted, respectively. These arguments don't belong in TestSuite.
2479 Instead, whitelist and blacklist should be handled by the thing that
2481 Instead, whitelist and blacklist should be handled by the thing that
2480 populates the TestSuite with tests. They are present to preserve
2482 populates the TestSuite with tests. They are present to preserve
2481 backwards compatible behavior which reports skipped tests as part
2483 backwards compatible behavior which reports skipped tests as part
2482 of the results.
2484 of the results.
2483
2485
2484 keywords denotes key words that will be used to filter which tests
2486 keywords denotes key words that will be used to filter which tests
2485 to execute. This arguably belongs outside of TestSuite.
2487 to execute. This arguably belongs outside of TestSuite.
2486
2488
2487 loop denotes whether to loop over tests forever.
2489 loop denotes whether to loop over tests forever.
2488 """
2490 """
2489 super(TestSuite, self).__init__(*args, **kwargs)
2491 super(TestSuite, self).__init__(*args, **kwargs)
2490
2492
2491 self._jobs = jobs
2493 self._jobs = jobs
2492 self._whitelist = whitelist
2494 self._whitelist = whitelist
2493 self._blacklist = blacklist
2495 self._blacklist = blacklist
2494 self._keywords = keywords
2496 self._keywords = keywords
2495 self._loop = loop
2497 self._loop = loop
2496 self._runs_per_test = runs_per_test
2498 self._runs_per_test = runs_per_test
2497 self._loadtest = loadtest
2499 self._loadtest = loadtest
2498 self._showchannels = showchannels
2500 self._showchannels = showchannels
2499
2501
2500 def run(self, result):
2502 def run(self, result):
2501 # We have a number of filters that need to be applied. We do this
2503 # We have a number of filters that need to be applied. We do this
2502 # here instead of inside Test because it makes the running logic for
2504 # here instead of inside Test because it makes the running logic for
2503 # Test simpler.
2505 # Test simpler.
2504 tests = []
2506 tests = []
2505 num_tests = [0]
2507 num_tests = [0]
2506 for test in self._tests:
2508 for test in self._tests:
2507
2509
2508 def get():
2510 def get():
2509 num_tests[0] += 1
2511 num_tests[0] += 1
2510 if getattr(test, 'should_reload', False):
2512 if getattr(test, 'should_reload', False):
2511 return self._loadtest(test, num_tests[0])
2513 return self._loadtest(test, num_tests[0])
2512 return test
2514 return test
2513
2515
2514 if not os.path.exists(test.path):
2516 if not os.path.exists(test.path):
2515 result.addSkip(test, "Doesn't exist")
2517 result.addSkip(test, "Doesn't exist")
2516 continue
2518 continue
2517
2519
2518 is_whitelisted = self._whitelist and (
2520 is_whitelisted = self._whitelist and (
2519 test.relpath in self._whitelist or test.bname in self._whitelist
2521 test.relpath in self._whitelist or test.bname in self._whitelist
2520 )
2522 )
2521 if not is_whitelisted:
2523 if not is_whitelisted:
2522 is_blacklisted = self._blacklist and (
2524 is_blacklisted = self._blacklist and (
2523 test.relpath in self._blacklist
2525 test.relpath in self._blacklist
2524 or test.bname in self._blacklist
2526 or test.bname in self._blacklist
2525 )
2527 )
2526 if is_blacklisted:
2528 if is_blacklisted:
2527 result.addSkip(test, 'blacklisted')
2529 result.addSkip(test, 'blacklisted')
2528 continue
2530 continue
2529 if self._keywords:
2531 if self._keywords:
2530 with open(test.path, 'rb') as f:
2532 with open(test.path, 'rb') as f:
2531 t = f.read().lower() + test.bname.lower()
2533 t = f.read().lower() + test.bname.lower()
2532 ignored = False
2534 ignored = False
2533 for k in self._keywords.lower().split():
2535 for k in self._keywords.lower().split():
2534 if k not in t:
2536 if k not in t:
2535 result.addIgnore(test, "doesn't match keyword")
2537 result.addIgnore(test, "doesn't match keyword")
2536 ignored = True
2538 ignored = True
2537 break
2539 break
2538
2540
2539 if ignored:
2541 if ignored:
2540 continue
2542 continue
2541 for _ in xrange(self._runs_per_test):
2543 for _ in xrange(self._runs_per_test):
2542 tests.append(get())
2544 tests.append(get())
2543
2545
2544 runtests = list(tests)
2546 runtests = list(tests)
2545 done = queue.Queue()
2547 done = queue.Queue()
2546 running = 0
2548 running = 0
2547
2549
2548 channels = [""] * self._jobs
2550 channels = [""] * self._jobs
2549
2551
2550 def job(test, result):
2552 def job(test, result):
2551 for n, v in enumerate(channels):
2553 for n, v in enumerate(channels):
2552 if not v:
2554 if not v:
2553 channel = n
2555 channel = n
2554 break
2556 break
2555 else:
2557 else:
2556 raise ValueError('Could not find output channel')
2558 raise ValueError('Could not find output channel')
2557 channels[channel] = "=" + test.name[5:].split(".")[0]
2559 channels[channel] = "=" + test.name[5:].split(".")[0]
2558 try:
2560 try:
2559 test(result)
2561 test(result)
2560 done.put(None)
2562 done.put(None)
2561 except KeyboardInterrupt:
2563 except KeyboardInterrupt:
2562 pass
2564 pass
2563 except: # re-raises
2565 except: # re-raises
2564 done.put(('!', test, 'run-test raised an error, see traceback'))
2566 done.put(('!', test, 'run-test raised an error, see traceback'))
2565 raise
2567 raise
2566 finally:
2568 finally:
2567 try:
2569 try:
2568 channels[channel] = ''
2570 channels[channel] = ''
2569 except IndexError:
2571 except IndexError:
2570 pass
2572 pass
2571
2573
2572 def stat():
2574 def stat():
2573 count = 0
2575 count = 0
2574 while channels:
2576 while channels:
2575 d = '\n%03s ' % count
2577 d = '\n%03s ' % count
2576 for n, v in enumerate(channels):
2578 for n, v in enumerate(channels):
2577 if v:
2579 if v:
2578 d += v[0]
2580 d += v[0]
2579 channels[n] = v[1:] or '.'
2581 channels[n] = v[1:] or '.'
2580 else:
2582 else:
2581 d += ' '
2583 d += ' '
2582 d += ' '
2584 d += ' '
2583 with iolock:
2585 with iolock:
2584 sys.stdout.write(d + ' ')
2586 sys.stdout.write(d + ' ')
2585 sys.stdout.flush()
2587 sys.stdout.flush()
2586 for x in xrange(10):
2588 for x in xrange(10):
2587 if channels:
2589 if channels:
2588 time.sleep(0.1)
2590 time.sleep(0.1)
2589 count += 1
2591 count += 1
2590
2592
2591 stoppedearly = False
2593 stoppedearly = False
2592
2594
2593 if self._showchannels:
2595 if self._showchannels:
2594 statthread = threading.Thread(target=stat, name="stat")
2596 statthread = threading.Thread(target=stat, name="stat")
2595 statthread.start()
2597 statthread.start()
2596
2598
2597 try:
2599 try:
2598 while tests or running:
2600 while tests or running:
2599 if not done.empty() or running == self._jobs or not tests:
2601 if not done.empty() or running == self._jobs or not tests:
2600 try:
2602 try:
2601 done.get(True, 1)
2603 done.get(True, 1)
2602 running -= 1
2604 running -= 1
2603 if result and result.shouldStop:
2605 if result and result.shouldStop:
2604 stoppedearly = True
2606 stoppedearly = True
2605 break
2607 break
2606 except queue.Empty:
2608 except queue.Empty:
2607 continue
2609 continue
2608 if tests and not running == self._jobs:
2610 if tests and not running == self._jobs:
2609 test = tests.pop(0)
2611 test = tests.pop(0)
2610 if self._loop:
2612 if self._loop:
2611 if getattr(test, 'should_reload', False):
2613 if getattr(test, 'should_reload', False):
2612 num_tests[0] += 1
2614 num_tests[0] += 1
2613 tests.append(self._loadtest(test, num_tests[0]))
2615 tests.append(self._loadtest(test, num_tests[0]))
2614 else:
2616 else:
2615 tests.append(test)
2617 tests.append(test)
2616 if self._jobs == 1:
2618 if self._jobs == 1:
2617 job(test, result)
2619 job(test, result)
2618 else:
2620 else:
2619 t = threading.Thread(
2621 t = threading.Thread(
2620 target=job, name=test.name, args=(test, result)
2622 target=job, name=test.name, args=(test, result)
2621 )
2623 )
2622 t.start()
2624 t.start()
2623 running += 1
2625 running += 1
2624
2626
2625 # If we stop early we still need to wait on started tests to
2627 # If we stop early we still need to wait on started tests to
2626 # finish. Otherwise, there is a race between the test completing
2628 # finish. Otherwise, there is a race between the test completing
2627 # and the test's cleanup code running. This could result in the
2629 # and the test's cleanup code running. This could result in the
2628 # test reporting incorrect.
2630 # test reporting incorrect.
2629 if stoppedearly:
2631 if stoppedearly:
2630 while running:
2632 while running:
2631 try:
2633 try:
2632 done.get(True, 1)
2634 done.get(True, 1)
2633 running -= 1
2635 running -= 1
2634 except queue.Empty:
2636 except queue.Empty:
2635 continue
2637 continue
2636 except KeyboardInterrupt:
2638 except KeyboardInterrupt:
2637 for test in runtests:
2639 for test in runtests:
2638 test.abort()
2640 test.abort()
2639
2641
2640 channels = []
2642 channels = []
2641
2643
2642 return result
2644 return result
2643
2645
2644
2646
2645 # Save the most recent 5 wall-clock runtimes of each test to a
2647 # Save the most recent 5 wall-clock runtimes of each test to a
2646 # human-readable text file named .testtimes. Tests are sorted
2648 # human-readable text file named .testtimes. Tests are sorted
2647 # alphabetically, while times for each test are listed from oldest to
2649 # alphabetically, while times for each test are listed from oldest to
2648 # newest.
2650 # newest.
2649
2651
2650
2652
2651 def loadtimes(outputdir):
2653 def loadtimes(outputdir):
2652 times = []
2654 times = []
2653 try:
2655 try:
2654 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2656 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2655 for line in fp:
2657 for line in fp:
2656 m = re.match('(.*?) ([0-9. ]+)', line)
2658 m = re.match('(.*?) ([0-9. ]+)', line)
2657 times.append(
2659 times.append(
2658 (m.group(1), [float(t) for t in m.group(2).split()])
2660 (m.group(1), [float(t) for t in m.group(2).split()])
2659 )
2661 )
2660 except IOError as err:
2662 except IOError as err:
2661 if err.errno != errno.ENOENT:
2663 if err.errno != errno.ENOENT:
2662 raise
2664 raise
2663 return times
2665 return times
2664
2666
2665
2667
2666 def savetimes(outputdir, result):
2668 def savetimes(outputdir, result):
2667 saved = dict(loadtimes(outputdir))
2669 saved = dict(loadtimes(outputdir))
2668 maxruns = 5
2670 maxruns = 5
2669 skipped = {str(t[0]) for t in result.skipped}
2671 skipped = {str(t[0]) for t in result.skipped}
2670 for tdata in result.times:
2672 for tdata in result.times:
2671 test, real = tdata[0], tdata[3]
2673 test, real = tdata[0], tdata[3]
2672 if test not in skipped:
2674 if test not in skipped:
2673 ts = saved.setdefault(test, [])
2675 ts = saved.setdefault(test, [])
2674 ts.append(real)
2676 ts.append(real)
2675 ts[:] = ts[-maxruns:]
2677 ts[:] = ts[-maxruns:]
2676
2678
2677 fd, tmpname = tempfile.mkstemp(
2679 fd, tmpname = tempfile.mkstemp(
2678 prefix=b'.testtimes', dir=outputdir, text=True
2680 prefix=b'.testtimes', dir=outputdir, text=True
2679 )
2681 )
2680 with os.fdopen(fd, 'w') as fp:
2682 with os.fdopen(fd, 'w') as fp:
2681 for name, ts in sorted(saved.items()):
2683 for name, ts in sorted(saved.items()):
2682 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2684 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2683 timepath = os.path.join(outputdir, b'.testtimes')
2685 timepath = os.path.join(outputdir, b'.testtimes')
2684 try:
2686 try:
2685 os.unlink(timepath)
2687 os.unlink(timepath)
2686 except OSError:
2688 except OSError:
2687 pass
2689 pass
2688 try:
2690 try:
2689 os.rename(tmpname, timepath)
2691 os.rename(tmpname, timepath)
2690 except OSError:
2692 except OSError:
2691 pass
2693 pass
2692
2694
2693
2695
2694 class TextTestRunner(unittest.TextTestRunner):
2696 class TextTestRunner(unittest.TextTestRunner):
2695 """Custom unittest test runner that uses appropriate settings."""
2697 """Custom unittest test runner that uses appropriate settings."""
2696
2698
2697 def __init__(self, runner, *args, **kwargs):
2699 def __init__(self, runner, *args, **kwargs):
2698 super(TextTestRunner, self).__init__(*args, **kwargs)
2700 super(TextTestRunner, self).__init__(*args, **kwargs)
2699
2701
2700 self._runner = runner
2702 self._runner = runner
2701
2703
2702 self._result = getTestResult()(
2704 self._result = getTestResult()(
2703 self._runner.options, self.stream, self.descriptions, self.verbosity
2705 self._runner.options, self.stream, self.descriptions, self.verbosity
2704 )
2706 )
2705
2707
2706 def listtests(self, test):
2708 def listtests(self, test):
2707 test = sorted(test, key=lambda t: t.name)
2709 test = sorted(test, key=lambda t: t.name)
2708
2710
2709 self._result.onStart(test)
2711 self._result.onStart(test)
2710
2712
2711 for t in test:
2713 for t in test:
2712 print(t.name)
2714 print(t.name)
2713 self._result.addSuccess(t)
2715 self._result.addSuccess(t)
2714
2716
2715 if self._runner.options.xunit:
2717 if self._runner.options.xunit:
2716 with open(self._runner.options.xunit, "wb") as xuf:
2718 with open(self._runner.options.xunit, "wb") as xuf:
2717 self._writexunit(self._result, xuf)
2719 self._writexunit(self._result, xuf)
2718
2720
2719 if self._runner.options.json:
2721 if self._runner.options.json:
2720 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2722 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2721 with open(jsonpath, 'w') as fp:
2723 with open(jsonpath, 'w') as fp:
2722 self._writejson(self._result, fp)
2724 self._writejson(self._result, fp)
2723
2725
2724 return self._result
2726 return self._result
2725
2727
2726 def run(self, test):
2728 def run(self, test):
2727 self._result.onStart(test)
2729 self._result.onStart(test)
2728 test(self._result)
2730 test(self._result)
2729
2731
2730 failed = len(self._result.failures)
2732 failed = len(self._result.failures)
2731 skipped = len(self._result.skipped)
2733 skipped = len(self._result.skipped)
2732 ignored = len(self._result.ignored)
2734 ignored = len(self._result.ignored)
2733
2735
2734 with iolock:
2736 with iolock:
2735 self.stream.writeln('')
2737 self.stream.writeln('')
2736
2738
2737 if not self._runner.options.noskips:
2739 if not self._runner.options.noskips:
2738 for test, msg in sorted(
2740 for test, msg in sorted(
2739 self._result.skipped, key=lambda s: s[0].name
2741 self._result.skipped, key=lambda s: s[0].name
2740 ):
2742 ):
2741 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2743 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2742 msg = highlightmsg(formatted, self._result.color)
2744 msg = highlightmsg(formatted, self._result.color)
2743 self.stream.write(msg)
2745 self.stream.write(msg)
2744 for test, msg in sorted(
2746 for test, msg in sorted(
2745 self._result.failures, key=lambda f: f[0].name
2747 self._result.failures, key=lambda f: f[0].name
2746 ):
2748 ):
2747 formatted = 'Failed %s: %s\n' % (test.name, msg)
2749 formatted = 'Failed %s: %s\n' % (test.name, msg)
2748 self.stream.write(highlightmsg(formatted, self._result.color))
2750 self.stream.write(highlightmsg(formatted, self._result.color))
2749 for test, msg in sorted(
2751 for test, msg in sorted(
2750 self._result.errors, key=lambda e: e[0].name
2752 self._result.errors, key=lambda e: e[0].name
2751 ):
2753 ):
2752 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2754 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2753
2755
2754 if self._runner.options.xunit:
2756 if self._runner.options.xunit:
2755 with open(self._runner.options.xunit, "wb") as xuf:
2757 with open(self._runner.options.xunit, "wb") as xuf:
2756 self._writexunit(self._result, xuf)
2758 self._writexunit(self._result, xuf)
2757
2759
2758 if self._runner.options.json:
2760 if self._runner.options.json:
2759 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2761 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2760 with open(jsonpath, 'w') as fp:
2762 with open(jsonpath, 'w') as fp:
2761 self._writejson(self._result, fp)
2763 self._writejson(self._result, fp)
2762
2764
2763 self._runner._checkhglib('Tested')
2765 self._runner._checkhglib('Tested')
2764
2766
2765 savetimes(self._runner._outputdir, self._result)
2767 savetimes(self._runner._outputdir, self._result)
2766
2768
2767 if failed and self._runner.options.known_good_rev:
2769 if failed and self._runner.options.known_good_rev:
2768 self._bisecttests(t for t, m in self._result.failures)
2770 self._bisecttests(t for t, m in self._result.failures)
2769 self.stream.writeln(
2771 self.stream.writeln(
2770 '# Ran %d tests, %d skipped, %d failed.'
2772 '# Ran %d tests, %d skipped, %d failed.'
2771 % (self._result.testsRun, skipped + ignored, failed)
2773 % (self._result.testsRun, skipped + ignored, failed)
2772 )
2774 )
2773 if failed:
2775 if failed:
2774 self.stream.writeln(
2776 self.stream.writeln(
2775 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2777 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2776 )
2778 )
2777 if self._runner.options.time:
2779 if self._runner.options.time:
2778 self.printtimes(self._result.times)
2780 self.printtimes(self._result.times)
2779
2781
2780 if self._runner.options.exceptions:
2782 if self._runner.options.exceptions:
2781 exceptions = aggregateexceptions(
2783 exceptions = aggregateexceptions(
2782 os.path.join(self._runner._outputdir, b'exceptions')
2784 os.path.join(self._runner._outputdir, b'exceptions')
2783 )
2785 )
2784
2786
2785 self.stream.writeln('Exceptions Report:')
2787 self.stream.writeln('Exceptions Report:')
2786 self.stream.writeln(
2788 self.stream.writeln(
2787 '%d total from %d frames'
2789 '%d total from %d frames'
2788 % (exceptions['total'], len(exceptions['exceptioncounts']))
2790 % (exceptions['total'], len(exceptions['exceptioncounts']))
2789 )
2791 )
2790 combined = exceptions['combined']
2792 combined = exceptions['combined']
2791 for key in sorted(combined, key=combined.get, reverse=True):
2793 for key in sorted(combined, key=combined.get, reverse=True):
2792 frame, line, exc = key
2794 frame, line, exc = key
2793 totalcount, testcount, leastcount, leasttest = combined[key]
2795 totalcount, testcount, leastcount, leasttest = combined[key]
2794
2796
2795 self.stream.writeln(
2797 self.stream.writeln(
2796 '%d (%d tests)\t%s: %s (%s - %d total)'
2798 '%d (%d tests)\t%s: %s (%s - %d total)'
2797 % (
2799 % (
2798 totalcount,
2800 totalcount,
2799 testcount,
2801 testcount,
2800 frame,
2802 frame,
2801 exc,
2803 exc,
2802 leasttest,
2804 leasttest,
2803 leastcount,
2805 leastcount,
2804 )
2806 )
2805 )
2807 )
2806
2808
2807 self.stream.flush()
2809 self.stream.flush()
2808
2810
2809 return self._result
2811 return self._result
2810
2812
2811 def _bisecttests(self, tests):
2813 def _bisecttests(self, tests):
2812 bisectcmd = ['hg', 'bisect']
2814 bisectcmd = ['hg', 'bisect']
2813 bisectrepo = self._runner.options.bisect_repo
2815 bisectrepo = self._runner.options.bisect_repo
2814 if bisectrepo:
2816 if bisectrepo:
2815 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2817 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2816
2818
2817 def pread(args):
2819 def pread(args):
2818 env = os.environ.copy()
2820 env = os.environ.copy()
2819 env['HGPLAIN'] = '1'
2821 env['HGPLAIN'] = '1'
2820 p = subprocess.Popen(
2822 p = subprocess.Popen(
2821 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2823 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2822 )
2824 )
2823 data = p.stdout.read()
2825 data = p.stdout.read()
2824 p.wait()
2826 p.wait()
2825 return data
2827 return data
2826
2828
2827 for test in tests:
2829 for test in tests:
2828 pread(bisectcmd + ['--reset']),
2830 pread(bisectcmd + ['--reset']),
2829 pread(bisectcmd + ['--bad', '.'])
2831 pread(bisectcmd + ['--bad', '.'])
2830 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2832 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2831 # TODO: we probably need to forward more options
2833 # TODO: we probably need to forward more options
2832 # that alter hg's behavior inside the tests.
2834 # that alter hg's behavior inside the tests.
2833 opts = ''
2835 opts = ''
2834 withhg = self._runner.options.with_hg
2836 withhg = self._runner.options.with_hg
2835 if withhg:
2837 if withhg:
2836 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2838 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2837 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2839 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2838 data = pread(bisectcmd + ['--command', rtc])
2840 data = pread(bisectcmd + ['--command', rtc])
2839 m = re.search(
2841 m = re.search(
2840 (
2842 (
2841 br'\nThe first (?P<goodbad>bad|good) revision '
2843 br'\nThe first (?P<goodbad>bad|good) revision '
2842 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2844 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2843 br'summary: +(?P<summary>[^\n]+)\n'
2845 br'summary: +(?P<summary>[^\n]+)\n'
2844 ),
2846 ),
2845 data,
2847 data,
2846 (re.MULTILINE | re.DOTALL),
2848 (re.MULTILINE | re.DOTALL),
2847 )
2849 )
2848 if m is None:
2850 if m is None:
2849 self.stream.writeln(
2851 self.stream.writeln(
2850 'Failed to identify failure point for %s' % test
2852 'Failed to identify failure point for %s' % test
2851 )
2853 )
2852 continue
2854 continue
2853 dat = m.groupdict()
2855 dat = m.groupdict()
2854 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2856 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2855 self.stream.writeln(
2857 self.stream.writeln(
2856 '%s %s by %s (%s)'
2858 '%s %s by %s (%s)'
2857 % (
2859 % (
2858 test,
2860 test,
2859 verb,
2861 verb,
2860 dat['node'].decode('ascii'),
2862 dat['node'].decode('ascii'),
2861 dat['summary'].decode('utf8', 'ignore'),
2863 dat['summary'].decode('utf8', 'ignore'),
2862 )
2864 )
2863 )
2865 )
2864
2866
2865 def printtimes(self, times):
2867 def printtimes(self, times):
2866 # iolock held by run
2868 # iolock held by run
2867 self.stream.writeln('# Producing time report')
2869 self.stream.writeln('# Producing time report')
2868 times.sort(key=lambda t: (t[3]))
2870 times.sort(key=lambda t: (t[3]))
2869 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2871 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2870 self.stream.writeln(
2872 self.stream.writeln(
2871 '%-7s %-7s %-7s %-7s %-7s %s'
2873 '%-7s %-7s %-7s %-7s %-7s %s'
2872 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2874 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2873 )
2875 )
2874 for tdata in times:
2876 for tdata in times:
2875 test = tdata[0]
2877 test = tdata[0]
2876 cuser, csys, real, start, end = tdata[1:6]
2878 cuser, csys, real, start, end = tdata[1:6]
2877 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2879 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2878
2880
2879 @staticmethod
2881 @staticmethod
2880 def _writexunit(result, outf):
2882 def _writexunit(result, outf):
2881 # See http://llg.cubic.org/docs/junit/ for a reference.
2883 # See http://llg.cubic.org/docs/junit/ for a reference.
2882 timesd = {t[0]: t[3] for t in result.times}
2884 timesd = {t[0]: t[3] for t in result.times}
2883 doc = minidom.Document()
2885 doc = minidom.Document()
2884 s = doc.createElement('testsuite')
2886 s = doc.createElement('testsuite')
2885 s.setAttribute('errors', "0") # TODO
2887 s.setAttribute('errors', "0") # TODO
2886 s.setAttribute('failures', str(len(result.failures)))
2888 s.setAttribute('failures', str(len(result.failures)))
2887 s.setAttribute('name', 'run-tests')
2889 s.setAttribute('name', 'run-tests')
2888 s.setAttribute(
2890 s.setAttribute(
2889 'skipped', str(len(result.skipped) + len(result.ignored))
2891 'skipped', str(len(result.skipped) + len(result.ignored))
2890 )
2892 )
2891 s.setAttribute('tests', str(result.testsRun))
2893 s.setAttribute('tests', str(result.testsRun))
2892 doc.appendChild(s)
2894 doc.appendChild(s)
2893 for tc in result.successes:
2895 for tc in result.successes:
2894 t = doc.createElement('testcase')
2896 t = doc.createElement('testcase')
2895 t.setAttribute('name', tc.name)
2897 t.setAttribute('name', tc.name)
2896 tctime = timesd.get(tc.name)
2898 tctime = timesd.get(tc.name)
2897 if tctime is not None:
2899 if tctime is not None:
2898 t.setAttribute('time', '%.3f' % tctime)
2900 t.setAttribute('time', '%.3f' % tctime)
2899 s.appendChild(t)
2901 s.appendChild(t)
2900 for tc, err in sorted(result.faildata.items()):
2902 for tc, err in sorted(result.faildata.items()):
2901 t = doc.createElement('testcase')
2903 t = doc.createElement('testcase')
2902 t.setAttribute('name', tc)
2904 t.setAttribute('name', tc)
2903 tctime = timesd.get(tc)
2905 tctime = timesd.get(tc)
2904 if tctime is not None:
2906 if tctime is not None:
2905 t.setAttribute('time', '%.3f' % tctime)
2907 t.setAttribute('time', '%.3f' % tctime)
2906 # createCDATASection expects a unicode or it will
2908 # createCDATASection expects a unicode or it will
2907 # convert using default conversion rules, which will
2909 # convert using default conversion rules, which will
2908 # fail if string isn't ASCII.
2910 # fail if string isn't ASCII.
2909 err = cdatasafe(err).decode('utf-8', 'replace')
2911 err = cdatasafe(err).decode('utf-8', 'replace')
2910 cd = doc.createCDATASection(err)
2912 cd = doc.createCDATASection(err)
2911 # Use 'failure' here instead of 'error' to match errors = 0,
2913 # Use 'failure' here instead of 'error' to match errors = 0,
2912 # failures = len(result.failures) in the testsuite element.
2914 # failures = len(result.failures) in the testsuite element.
2913 failelem = doc.createElement('failure')
2915 failelem = doc.createElement('failure')
2914 failelem.setAttribute('message', 'output changed')
2916 failelem.setAttribute('message', 'output changed')
2915 failelem.setAttribute('type', 'output-mismatch')
2917 failelem.setAttribute('type', 'output-mismatch')
2916 failelem.appendChild(cd)
2918 failelem.appendChild(cd)
2917 t.appendChild(failelem)
2919 t.appendChild(failelem)
2918 s.appendChild(t)
2920 s.appendChild(t)
2919 for tc, message in result.skipped:
2921 for tc, message in result.skipped:
2920 # According to the schema, 'skipped' has no attributes. So store
2922 # According to the schema, 'skipped' has no attributes. So store
2921 # the skip message as a text node instead.
2923 # the skip message as a text node instead.
2922 t = doc.createElement('testcase')
2924 t = doc.createElement('testcase')
2923 t.setAttribute('name', tc.name)
2925 t.setAttribute('name', tc.name)
2924 binmessage = message.encode('utf-8')
2926 binmessage = message.encode('utf-8')
2925 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2927 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2926 cd = doc.createCDATASection(message)
2928 cd = doc.createCDATASection(message)
2927 skipelem = doc.createElement('skipped')
2929 skipelem = doc.createElement('skipped')
2928 skipelem.appendChild(cd)
2930 skipelem.appendChild(cd)
2929 t.appendChild(skipelem)
2931 t.appendChild(skipelem)
2930 s.appendChild(t)
2932 s.appendChild(t)
2931 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2933 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2932
2934
2933 @staticmethod
2935 @staticmethod
2934 def _writejson(result, outf):
2936 def _writejson(result, outf):
2935 timesd = {}
2937 timesd = {}
2936 for tdata in result.times:
2938 for tdata in result.times:
2937 test = tdata[0]
2939 test = tdata[0]
2938 timesd[test] = tdata[1:]
2940 timesd[test] = tdata[1:]
2939
2941
2940 outcome = {}
2942 outcome = {}
2941 groups = [
2943 groups = [
2942 ('success', ((tc, None) for tc in result.successes)),
2944 ('success', ((tc, None) for tc in result.successes)),
2943 ('failure', result.failures),
2945 ('failure', result.failures),
2944 ('skip', result.skipped),
2946 ('skip', result.skipped),
2945 ]
2947 ]
2946 for res, testcases in groups:
2948 for res, testcases in groups:
2947 for tc, __ in testcases:
2949 for tc, __ in testcases:
2948 if tc.name in timesd:
2950 if tc.name in timesd:
2949 diff = result.faildata.get(tc.name, b'')
2951 diff = result.faildata.get(tc.name, b'')
2950 try:
2952 try:
2951 diff = diff.decode('unicode_escape')
2953 diff = diff.decode('unicode_escape')
2952 except UnicodeDecodeError as e:
2954 except UnicodeDecodeError as e:
2953 diff = '%r decoding diff, sorry' % e
2955 diff = '%r decoding diff, sorry' % e
2954 tres = {
2956 tres = {
2955 'result': res,
2957 'result': res,
2956 'time': ('%0.3f' % timesd[tc.name][2]),
2958 'time': ('%0.3f' % timesd[tc.name][2]),
2957 'cuser': ('%0.3f' % timesd[tc.name][0]),
2959 'cuser': ('%0.3f' % timesd[tc.name][0]),
2958 'csys': ('%0.3f' % timesd[tc.name][1]),
2960 'csys': ('%0.3f' % timesd[tc.name][1]),
2959 'start': ('%0.3f' % timesd[tc.name][3]),
2961 'start': ('%0.3f' % timesd[tc.name][3]),
2960 'end': ('%0.3f' % timesd[tc.name][4]),
2962 'end': ('%0.3f' % timesd[tc.name][4]),
2961 'diff': diff,
2963 'diff': diff,
2962 }
2964 }
2963 else:
2965 else:
2964 # blacklisted test
2966 # blacklisted test
2965 tres = {'result': res}
2967 tres = {'result': res}
2966
2968
2967 outcome[tc.name] = tres
2969 outcome[tc.name] = tres
2968 jsonout = json.dumps(
2970 jsonout = json.dumps(
2969 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2971 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2970 )
2972 )
2971 outf.writelines(("testreport =", jsonout))
2973 outf.writelines(("testreport =", jsonout))
2972
2974
2973
2975
2974 def sorttests(testdescs, previoustimes, shuffle=False):
2976 def sorttests(testdescs, previoustimes, shuffle=False):
2975 """Do an in-place sort of tests."""
2977 """Do an in-place sort of tests."""
2976 if shuffle:
2978 if shuffle:
2977 random.shuffle(testdescs)
2979 random.shuffle(testdescs)
2978 return
2980 return
2979
2981
2980 if previoustimes:
2982 if previoustimes:
2981
2983
2982 def sortkey(f):
2984 def sortkey(f):
2983 f = f['path']
2985 f = f['path']
2984 if f in previoustimes:
2986 if f in previoustimes:
2985 # Use most recent time as estimate
2987 # Use most recent time as estimate
2986 return -(previoustimes[f][-1])
2988 return -(previoustimes[f][-1])
2987 else:
2989 else:
2988 # Default to a rather arbitrary value of 1 second for new tests
2990 # Default to a rather arbitrary value of 1 second for new tests
2989 return -1.0
2991 return -1.0
2990
2992
2991 else:
2993 else:
2992 # keywords for slow tests
2994 # keywords for slow tests
2993 slow = {
2995 slow = {
2994 b'svn': 10,
2996 b'svn': 10,
2995 b'cvs': 10,
2997 b'cvs': 10,
2996 b'hghave': 10,
2998 b'hghave': 10,
2997 b'largefiles-update': 10,
2999 b'largefiles-update': 10,
2998 b'run-tests': 10,
3000 b'run-tests': 10,
2999 b'corruption': 10,
3001 b'corruption': 10,
3000 b'race': 10,
3002 b'race': 10,
3001 b'i18n': 10,
3003 b'i18n': 10,
3002 b'check': 100,
3004 b'check': 100,
3003 b'gendoc': 100,
3005 b'gendoc': 100,
3004 b'contrib-perf': 200,
3006 b'contrib-perf': 200,
3005 b'merge-combination': 100,
3007 b'merge-combination': 100,
3006 }
3008 }
3007 perf = {}
3009 perf = {}
3008
3010
3009 def sortkey(f):
3011 def sortkey(f):
3010 # run largest tests first, as they tend to take the longest
3012 # run largest tests first, as they tend to take the longest
3011 f = f['path']
3013 f = f['path']
3012 try:
3014 try:
3013 return perf[f]
3015 return perf[f]
3014 except KeyError:
3016 except KeyError:
3015 try:
3017 try:
3016 val = -os.stat(f).st_size
3018 val = -os.stat(f).st_size
3017 except OSError as e:
3019 except OSError as e:
3018 if e.errno != errno.ENOENT:
3020 if e.errno != errno.ENOENT:
3019 raise
3021 raise
3020 perf[f] = -1e9 # file does not exist, tell early
3022 perf[f] = -1e9 # file does not exist, tell early
3021 return -1e9
3023 return -1e9
3022 for kw, mul in slow.items():
3024 for kw, mul in slow.items():
3023 if kw in f:
3025 if kw in f:
3024 val *= mul
3026 val *= mul
3025 if f.endswith(b'.py'):
3027 if f.endswith(b'.py'):
3026 val /= 10.0
3028 val /= 10.0
3027 perf[f] = val / 1000.0
3029 perf[f] = val / 1000.0
3028 return perf[f]
3030 return perf[f]
3029
3031
3030 testdescs.sort(key=sortkey)
3032 testdescs.sort(key=sortkey)
3031
3033
3032
3034
3033 class TestRunner(object):
3035 class TestRunner(object):
3034 """Holds context for executing tests.
3036 """Holds context for executing tests.
3035
3037
3036 Tests rely on a lot of state. This object holds it for them.
3038 Tests rely on a lot of state. This object holds it for them.
3037 """
3039 """
3038
3040
3039 # Programs required to run tests.
3041 # Programs required to run tests.
3040 REQUIREDTOOLS = [
3042 REQUIREDTOOLS = [
3041 b'diff',
3043 b'diff',
3042 b'grep',
3044 b'grep',
3043 b'unzip',
3045 b'unzip',
3044 b'gunzip',
3046 b'gunzip',
3045 b'bunzip2',
3047 b'bunzip2',
3046 b'sed',
3048 b'sed',
3047 ]
3049 ]
3048
3050
3049 # Maps file extensions to test class.
3051 # Maps file extensions to test class.
3050 TESTTYPES = [
3052 TESTTYPES = [
3051 (b'.py', PythonTest),
3053 (b'.py', PythonTest),
3052 (b'.t', TTest),
3054 (b'.t', TTest),
3053 ]
3055 ]
3054
3056
3055 def __init__(self):
3057 def __init__(self):
3056 self.options = None
3058 self.options = None
3057 self._hgroot = None
3059 self._hgroot = None
3058 self._testdir = None
3060 self._testdir = None
3059 self._outputdir = None
3061 self._outputdir = None
3060 self._hgtmp = None
3062 self._hgtmp = None
3061 self._installdir = None
3063 self._installdir = None
3062 self._bindir = None
3064 self._bindir = None
3063 # a place for run-tests.py to generate executable it needs
3065 # a place for run-tests.py to generate executable it needs
3064 self._custom_bin_dir = None
3066 self._custom_bin_dir = None
3065 self._pythondir = None
3067 self._pythondir = None
3066 # True if we had to infer the pythondir from --with-hg
3068 # True if we had to infer the pythondir from --with-hg
3067 self._pythondir_inferred = False
3069 self._pythondir_inferred = False
3068 self._coveragefile = None
3070 self._coveragefile = None
3069 self._createdfiles = []
3071 self._createdfiles = []
3070 self._hgcommand = None
3072 self._hgcommand = None
3071 self._hgpath = None
3073 self._hgpath = None
3072 self._portoffset = 0
3074 self._portoffset = 0
3073 self._ports = {}
3075 self._ports = {}
3074
3076
3075 def run(self, args, parser=None):
3077 def run(self, args, parser=None):
3076 """Run the test suite."""
3078 """Run the test suite."""
3077 oldmask = os.umask(0o22)
3079 oldmask = os.umask(0o22)
3078 try:
3080 try:
3079 parser = parser or getparser()
3081 parser = parser or getparser()
3080 options = parseargs(args, parser)
3082 options = parseargs(args, parser)
3081 tests = [_sys2bytes(a) for a in options.tests]
3083 tests = [_sys2bytes(a) for a in options.tests]
3082 if options.test_list is not None:
3084 if options.test_list is not None:
3083 for listfile in options.test_list:
3085 for listfile in options.test_list:
3084 with open(listfile, 'rb') as f:
3086 with open(listfile, 'rb') as f:
3085 tests.extend(t for t in f.read().splitlines() if t)
3087 tests.extend(t for t in f.read().splitlines() if t)
3086 self.options = options
3088 self.options = options
3087
3089
3088 self._checktools()
3090 self._checktools()
3089 testdescs = self.findtests(tests)
3091 testdescs = self.findtests(tests)
3090 if options.profile_runner:
3092 if options.profile_runner:
3091 import statprof
3093 import statprof
3092
3094
3093 statprof.start()
3095 statprof.start()
3094 result = self._run(testdescs)
3096 result = self._run(testdescs)
3095 if options.profile_runner:
3097 if options.profile_runner:
3096 statprof.stop()
3098 statprof.stop()
3097 statprof.display()
3099 statprof.display()
3098 return result
3100 return result
3099
3101
3100 finally:
3102 finally:
3101 os.umask(oldmask)
3103 os.umask(oldmask)
3102
3104
3103 def _run(self, testdescs):
3105 def _run(self, testdescs):
3104 testdir = getcwdb()
3106 testdir = getcwdb()
3105 # assume all tests in same folder for now
3107 # assume all tests in same folder for now
3106 if testdescs:
3108 if testdescs:
3107 pathname = os.path.dirname(testdescs[0]['path'])
3109 pathname = os.path.dirname(testdescs[0]['path'])
3108 if pathname:
3110 if pathname:
3109 testdir = os.path.join(testdir, pathname)
3111 testdir = os.path.join(testdir, pathname)
3110 self._testdir = osenvironb[b'TESTDIR'] = testdir
3112 self._testdir = osenvironb[b'TESTDIR'] = testdir
3111 if self.options.outputdir:
3113 if self.options.outputdir:
3112 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3114 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3113 else:
3115 else:
3114 self._outputdir = getcwdb()
3116 self._outputdir = getcwdb()
3115 if testdescs and pathname:
3117 if testdescs and pathname:
3116 self._outputdir = os.path.join(self._outputdir, pathname)
3118 self._outputdir = os.path.join(self._outputdir, pathname)
3117 previoustimes = {}
3119 previoustimes = {}
3118 if self.options.order_by_runtime:
3120 if self.options.order_by_runtime:
3119 previoustimes = dict(loadtimes(self._outputdir))
3121 previoustimes = dict(loadtimes(self._outputdir))
3120 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3122 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3121
3123
3122 if 'PYTHONHASHSEED' not in os.environ:
3124 if 'PYTHONHASHSEED' not in os.environ:
3123 # use a random python hash seed all the time
3125 # use a random python hash seed all the time
3124 # we do the randomness ourself to know what seed is used
3126 # we do the randomness ourself to know what seed is used
3125 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3127 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3126
3128
3127 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3129 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3128 # by default, causing thrashing on high-cpu-count systems.
3130 # by default, causing thrashing on high-cpu-count systems.
3129 # Setting its limit to 3 during tests should still let us uncover
3131 # Setting its limit to 3 during tests should still let us uncover
3130 # multi-threading bugs while keeping the thrashing reasonable.
3132 # multi-threading bugs while keeping the thrashing reasonable.
3131 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3133 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3132
3134
3133 if self.options.tmpdir:
3135 if self.options.tmpdir:
3134 self.options.keep_tmpdir = True
3136 self.options.keep_tmpdir = True
3135 tmpdir = _sys2bytes(self.options.tmpdir)
3137 tmpdir = _sys2bytes(self.options.tmpdir)
3136 if os.path.exists(tmpdir):
3138 if os.path.exists(tmpdir):
3137 # Meaning of tmpdir has changed since 1.3: we used to create
3139 # Meaning of tmpdir has changed since 1.3: we used to create
3138 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3140 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3139 # tmpdir already exists.
3141 # tmpdir already exists.
3140 print("error: temp dir %r already exists" % tmpdir)
3142 print("error: temp dir %r already exists" % tmpdir)
3141 return 1
3143 return 1
3142
3144
3143 os.makedirs(tmpdir)
3145 os.makedirs(tmpdir)
3144 else:
3146 else:
3145 d = None
3147 d = None
3146 if WINDOWS:
3148 if WINDOWS:
3147 # without this, we get the default temp dir location, but
3149 # without this, we get the default temp dir location, but
3148 # in all lowercase, which causes troubles with paths (issue3490)
3150 # in all lowercase, which causes troubles with paths (issue3490)
3149 d = osenvironb.get(b'TMP', None)
3151 d = osenvironb.get(b'TMP', None)
3150 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3152 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3151
3153
3152 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3154 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3153
3155
3154 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3156 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3155 os.makedirs(self._custom_bin_dir)
3157 os.makedirs(self._custom_bin_dir)
3156
3158
3157 if self.options.with_hg:
3159 if self.options.with_hg:
3158 self._installdir = None
3160 self._installdir = None
3159 whg = self.options.with_hg
3161 whg = self.options.with_hg
3160 self._bindir = os.path.dirname(os.path.realpath(whg))
3162 self._bindir = os.path.dirname(os.path.realpath(whg))
3161 assert isinstance(self._bindir, bytes)
3163 assert isinstance(self._bindir, bytes)
3162 self._hgcommand = os.path.basename(whg)
3164 self._hgcommand = os.path.basename(whg)
3163
3165
3164 normbin = os.path.normpath(os.path.abspath(whg))
3166 normbin = os.path.normpath(os.path.abspath(whg))
3165 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3167 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3166
3168
3167 # Other Python scripts in the test harness need to
3169 # Other Python scripts in the test harness need to
3168 # `import mercurial`. If `hg` is a Python script, we assume
3170 # `import mercurial`. If `hg` is a Python script, we assume
3169 # the Mercurial modules are relative to its path and tell the tests
3171 # the Mercurial modules are relative to its path and tell the tests
3170 # to load Python modules from its directory.
3172 # to load Python modules from its directory.
3171 with open(whg, 'rb') as fh:
3173 with open(whg, 'rb') as fh:
3172 initial = fh.read(1024)
3174 initial = fh.read(1024)
3173
3175
3174 if re.match(b'#!.*python', initial):
3176 if re.match(b'#!.*python', initial):
3175 self._pythondir = self._bindir
3177 self._pythondir = self._bindir
3176 # If it looks like our in-repo Rust binary, use the source root.
3178 # If it looks like our in-repo Rust binary, use the source root.
3177 # This is a bit hacky. But rhg is still not supported outside the
3179 # This is a bit hacky. But rhg is still not supported outside the
3178 # source directory. So until it is, do the simple thing.
3180 # source directory. So until it is, do the simple thing.
3179 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3181 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3180 self._pythondir = os.path.dirname(self._testdir)
3182 self._pythondir = os.path.dirname(self._testdir)
3181 # Fall back to the legacy behavior.
3183 # Fall back to the legacy behavior.
3182 else:
3184 else:
3183 self._pythondir = self._bindir
3185 self._pythondir = self._bindir
3184 self._pythondir_inferred = True
3186 self._pythondir_inferred = True
3185
3187
3186 else:
3188 else:
3187 self._installdir = os.path.join(self._hgtmp, b"install")
3189 self._installdir = os.path.join(self._hgtmp, b"install")
3188 self._bindir = os.path.join(self._installdir, b"bin")
3190 self._bindir = os.path.join(self._installdir, b"bin")
3189 self._hgcommand = b'hg'
3191 self._hgcommand = b'hg'
3190 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3192 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3191
3193
3192 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3194 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3193 # a python script and feed it to python.exe. Legacy stdio is force
3195 # a python script and feed it to python.exe. Legacy stdio is force
3194 # enabled by hg.exe, and this is a more realistic way to launch hg
3196 # enabled by hg.exe, and this is a more realistic way to launch hg
3195 # anyway.
3197 # anyway.
3196 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3198 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3197 self._hgcommand += b'.exe'
3199 self._hgcommand += b'.exe'
3198
3200
3199 real_hg = os.path.join(self._bindir, self._hgcommand)
3201 real_hg = os.path.join(self._bindir, self._hgcommand)
3200 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3202 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3201 # set CHGHG, then replace "hg" command by "chg"
3203 # set CHGHG, then replace "hg" command by "chg"
3202 chgbindir = self._bindir
3204 chgbindir = self._bindir
3203 if self.options.chg or self.options.with_chg:
3205 if self.options.chg or self.options.with_chg:
3204 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3206 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3205 osenvironb[b'CHGHG'] = real_hg
3207 osenvironb[b'CHGHG'] = real_hg
3206 else:
3208 else:
3207 # drop flag for hghave
3209 # drop flag for hghave
3208 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3210 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3209 if self.options.chg:
3211 if self.options.chg:
3210 self._hgcommand = b'chg'
3212 self._hgcommand = b'chg'
3211 elif self.options.with_chg:
3213 elif self.options.with_chg:
3212 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3214 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3213 self._hgcommand = os.path.basename(self.options.with_chg)
3215 self._hgcommand = os.path.basename(self.options.with_chg)
3214
3216
3215 # configure fallback and replace "hg" command by "rhg"
3217 # configure fallback and replace "hg" command by "rhg"
3216 rhgbindir = self._bindir
3218 rhgbindir = self._bindir
3217 if self.options.rhg or self.options.with_rhg:
3219 if self.options.rhg or self.options.with_rhg:
3218 # Affects hghave.py
3220 # Affects hghave.py
3219 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3221 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3220 # Affects configuration. Alternatives would be setting configuration through
3222 # Affects configuration. Alternatives would be setting configuration through
3221 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3223 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3222 # `--config` but that disrupts tests that print command lines and check expected
3224 # `--config` but that disrupts tests that print command lines and check expected
3223 # output.
3225 # output.
3224 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3226 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3225 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3227 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3226 else:
3228 else:
3227 # drop flag for hghave
3229 # drop flag for hghave
3228 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3230 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3229 if self.options.rhg:
3231 if self.options.rhg:
3230 self._hgcommand = b'rhg'
3232 self._hgcommand = b'rhg'
3231 elif self.options.with_rhg:
3233 elif self.options.with_rhg:
3232 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3234 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3233 self._hgcommand = os.path.basename(self.options.with_rhg)
3235 self._hgcommand = os.path.basename(self.options.with_rhg)
3234
3236
3235 if self.options.pyoxidized:
3237 if self.options.pyoxidized:
3236 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3238 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3237 reporootdir = os.path.dirname(testdir)
3239 reporootdir = os.path.dirname(testdir)
3238 # XXX we should ideally install stuff instead of using the local build
3240 # XXX we should ideally install stuff instead of using the local build
3239 bin_path = (
3241 bin_path = (
3240 b'build/pyoxidizer/x86_64-pc-windows-msvc/release/app/hg.exe'
3242 b'build/pyoxidizer/x86_64-pc-windows-msvc/release/app/hg.exe'
3241 )
3243 )
3242 full_path = os.path.join(reporootdir, bin_path)
3244 full_path = os.path.join(reporootdir, bin_path)
3243 self._hgcommand = full_path
3245 self._hgcommand = full_path
3244 # Affects hghave.py
3246 # Affects hghave.py
3245 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3247 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3246 else:
3248 else:
3247 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3249 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3248
3250
3249 osenvironb[b"BINDIR"] = self._bindir
3251 osenvironb[b"BINDIR"] = self._bindir
3250 osenvironb[b"PYTHON"] = PYTHON
3252 osenvironb[b"PYTHON"] = PYTHON
3251
3253
3252 fileb = _sys2bytes(__file__)
3254 fileb = _sys2bytes(__file__)
3253 runtestdir = os.path.abspath(os.path.dirname(fileb))
3255 runtestdir = os.path.abspath(os.path.dirname(fileb))
3254 osenvironb[b'RUNTESTDIR'] = runtestdir
3256 osenvironb[b'RUNTESTDIR'] = runtestdir
3255 if PYTHON3:
3257 if PYTHON3:
3256 sepb = _sys2bytes(os.pathsep)
3258 sepb = _sys2bytes(os.pathsep)
3257 else:
3259 else:
3258 sepb = os.pathsep
3260 sepb = os.pathsep
3259 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3261 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3260 if os.path.islink(__file__):
3262 if os.path.islink(__file__):
3261 # test helper will likely be at the end of the symlink
3263 # test helper will likely be at the end of the symlink
3262 realfile = os.path.realpath(fileb)
3264 realfile = os.path.realpath(fileb)
3263 realdir = os.path.abspath(os.path.dirname(realfile))
3265 realdir = os.path.abspath(os.path.dirname(realfile))
3264 path.insert(2, realdir)
3266 path.insert(2, realdir)
3265 if chgbindir != self._bindir:
3267 if chgbindir != self._bindir:
3266 path.insert(1, chgbindir)
3268 path.insert(1, chgbindir)
3267 if rhgbindir != self._bindir:
3269 if rhgbindir != self._bindir:
3268 path.insert(1, rhgbindir)
3270 path.insert(1, rhgbindir)
3269 if self._testdir != runtestdir:
3271 if self._testdir != runtestdir:
3270 path = [self._testdir] + path
3272 path = [self._testdir] + path
3271 path = [self._custom_bin_dir] + path
3273 path = [self._custom_bin_dir] + path
3272 osenvironb[b"PATH"] = sepb.join(path)
3274 osenvironb[b"PATH"] = sepb.join(path)
3273
3275
3274 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3276 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3275 # can run .../tests/run-tests.py test-foo where test-foo
3277 # can run .../tests/run-tests.py test-foo where test-foo
3276 # adds an extension to HGRC. Also include run-test.py directory to
3278 # adds an extension to HGRC. Also include run-test.py directory to
3277 # import modules like heredoctest.
3279 # import modules like heredoctest.
3278 pypath = [self._pythondir, self._testdir, runtestdir]
3280 pypath = [self._pythondir, self._testdir, runtestdir]
3279 # We have to augment PYTHONPATH, rather than simply replacing
3281 # We have to augment PYTHONPATH, rather than simply replacing
3280 # it, in case external libraries are only available via current
3282 # it, in case external libraries are only available via current
3281 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3283 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3282 # are in /opt/subversion.)
3284 # are in /opt/subversion.)
3283 oldpypath = osenvironb.get(IMPL_PATH)
3285 oldpypath = osenvironb.get(IMPL_PATH)
3284 if oldpypath:
3286 if oldpypath:
3285 pypath.append(oldpypath)
3287 pypath.append(oldpypath)
3286 osenvironb[IMPL_PATH] = sepb.join(pypath)
3288 osenvironb[IMPL_PATH] = sepb.join(pypath)
3287
3289
3288 if self.options.pure:
3290 if self.options.pure:
3289 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3291 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3290 os.environ["HGMODULEPOLICY"] = "py"
3292 os.environ["HGMODULEPOLICY"] = "py"
3291 if self.options.rust:
3293 if self.options.rust:
3292 os.environ["HGMODULEPOLICY"] = "rust+c"
3294 os.environ["HGMODULEPOLICY"] = "rust+c"
3293 if self.options.no_rust:
3295 if self.options.no_rust:
3294 current_policy = os.environ.get("HGMODULEPOLICY", "")
3296 current_policy = os.environ.get("HGMODULEPOLICY", "")
3295 if current_policy.startswith("rust+"):
3297 if current_policy.startswith("rust+"):
3296 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3298 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3297 os.environ.pop("HGWITHRUSTEXT", None)
3299 os.environ.pop("HGWITHRUSTEXT", None)
3298
3300
3299 if self.options.allow_slow_tests:
3301 if self.options.allow_slow_tests:
3300 os.environ["HGTEST_SLOW"] = "slow"
3302 os.environ["HGTEST_SLOW"] = "slow"
3301 elif 'HGTEST_SLOW' in os.environ:
3303 elif 'HGTEST_SLOW' in os.environ:
3302 del os.environ['HGTEST_SLOW']
3304 del os.environ['HGTEST_SLOW']
3303
3305
3304 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3306 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3305
3307
3306 if self.options.exceptions:
3308 if self.options.exceptions:
3307 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3309 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3308 try:
3310 try:
3309 os.makedirs(exceptionsdir)
3311 os.makedirs(exceptionsdir)
3310 except OSError as e:
3312 except OSError as e:
3311 if e.errno != errno.EEXIST:
3313 if e.errno != errno.EEXIST:
3312 raise
3314 raise
3313
3315
3314 # Remove all existing exception reports.
3316 # Remove all existing exception reports.
3315 for f in os.listdir(exceptionsdir):
3317 for f in os.listdir(exceptionsdir):
3316 os.unlink(os.path.join(exceptionsdir, f))
3318 os.unlink(os.path.join(exceptionsdir, f))
3317
3319
3318 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3320 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3319 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3321 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3320 self.options.extra_config_opt.append(
3322 self.options.extra_config_opt.append(
3321 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3323 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3322 )
3324 )
3323
3325
3324 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3326 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3325 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3327 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3326 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3328 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3327 vlog("# Using PATH", os.environ["PATH"])
3329 vlog("# Using PATH", os.environ["PATH"])
3328 vlog(
3330 vlog(
3329 "# Using",
3331 "# Using",
3330 _bytes2sys(IMPL_PATH),
3332 _bytes2sys(IMPL_PATH),
3331 _bytes2sys(osenvironb[IMPL_PATH]),
3333 _bytes2sys(osenvironb[IMPL_PATH]),
3332 )
3334 )
3333 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3335 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3334
3336
3335 try:
3337 try:
3336 return self._runtests(testdescs) or 0
3338 return self._runtests(testdescs) or 0
3337 finally:
3339 finally:
3338 time.sleep(0.1)
3340 time.sleep(0.1)
3339 self._cleanup()
3341 self._cleanup()
3340
3342
3341 def findtests(self, args):
3343 def findtests(self, args):
3342 """Finds possible test files from arguments.
3344 """Finds possible test files from arguments.
3343
3345
3344 If you wish to inject custom tests into the test harness, this would
3346 If you wish to inject custom tests into the test harness, this would
3345 be a good function to monkeypatch or override in a derived class.
3347 be a good function to monkeypatch or override in a derived class.
3346 """
3348 """
3347 if not args:
3349 if not args:
3348 if self.options.changed:
3350 if self.options.changed:
3349 proc = Popen4(
3351 proc = Popen4(
3350 b'hg st --rev "%s" -man0 .'
3352 b'hg st --rev "%s" -man0 .'
3351 % _sys2bytes(self.options.changed),
3353 % _sys2bytes(self.options.changed),
3352 None,
3354 None,
3353 0,
3355 0,
3354 )
3356 )
3355 stdout, stderr = proc.communicate()
3357 stdout, stderr = proc.communicate()
3356 args = stdout.strip(b'\0').split(b'\0')
3358 args = stdout.strip(b'\0').split(b'\0')
3357 else:
3359 else:
3358 args = os.listdir(b'.')
3360 args = os.listdir(b'.')
3359
3361
3360 expanded_args = []
3362 expanded_args = []
3361 for arg in args:
3363 for arg in args:
3362 if os.path.isdir(arg):
3364 if os.path.isdir(arg):
3363 if not arg.endswith(b'/'):
3365 if not arg.endswith(b'/'):
3364 arg += b'/'
3366 arg += b'/'
3365 expanded_args.extend([arg + a for a in os.listdir(arg)])
3367 expanded_args.extend([arg + a for a in os.listdir(arg)])
3366 else:
3368 else:
3367 expanded_args.append(arg)
3369 expanded_args.append(arg)
3368 args = expanded_args
3370 args = expanded_args
3369
3371
3370 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3372 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3371 tests = []
3373 tests = []
3372 for t in args:
3374 for t in args:
3373 case = []
3375 case = []
3374
3376
3375 if not (
3377 if not (
3376 os.path.basename(t).startswith(b'test-')
3378 os.path.basename(t).startswith(b'test-')
3377 and (t.endswith(b'.py') or t.endswith(b'.t'))
3379 and (t.endswith(b'.py') or t.endswith(b'.t'))
3378 ):
3380 ):
3379
3381
3380 m = testcasepattern.match(os.path.basename(t))
3382 m = testcasepattern.match(os.path.basename(t))
3381 if m is not None:
3383 if m is not None:
3382 t_basename, casestr = m.groups()
3384 t_basename, casestr = m.groups()
3383 t = os.path.join(os.path.dirname(t), t_basename)
3385 t = os.path.join(os.path.dirname(t), t_basename)
3384 if casestr:
3386 if casestr:
3385 case = casestr.split(b'#')
3387 case = casestr.split(b'#')
3386 else:
3388 else:
3387 continue
3389 continue
3388
3390
3389 if t.endswith(b'.t'):
3391 if t.endswith(b'.t'):
3390 # .t file may contain multiple test cases
3392 # .t file may contain multiple test cases
3391 casedimensions = parsettestcases(t)
3393 casedimensions = parsettestcases(t)
3392 if casedimensions:
3394 if casedimensions:
3393 cases = []
3395 cases = []
3394
3396
3395 def addcases(case, casedimensions):
3397 def addcases(case, casedimensions):
3396 if not casedimensions:
3398 if not casedimensions:
3397 cases.append(case)
3399 cases.append(case)
3398 else:
3400 else:
3399 for c in casedimensions[0]:
3401 for c in casedimensions[0]:
3400 addcases(case + [c], casedimensions[1:])
3402 addcases(case + [c], casedimensions[1:])
3401
3403
3402 addcases([], casedimensions)
3404 addcases([], casedimensions)
3403 if case and case in cases:
3405 if case and case in cases:
3404 cases = [case]
3406 cases = [case]
3405 elif case:
3407 elif case:
3406 # Ignore invalid cases
3408 # Ignore invalid cases
3407 cases = []
3409 cases = []
3408 else:
3410 else:
3409 pass
3411 pass
3410 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3412 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3411 else:
3413 else:
3412 tests.append({'path': t})
3414 tests.append({'path': t})
3413 else:
3415 else:
3414 tests.append({'path': t})
3416 tests.append({'path': t})
3415
3417
3416 if self.options.retest:
3418 if self.options.retest:
3417 retest_args = []
3419 retest_args = []
3418 for test in tests:
3420 for test in tests:
3419 errpath = self._geterrpath(test)
3421 errpath = self._geterrpath(test)
3420 if os.path.exists(errpath):
3422 if os.path.exists(errpath):
3421 retest_args.append(test)
3423 retest_args.append(test)
3422 tests = retest_args
3424 tests = retest_args
3423 return tests
3425 return tests
3424
3426
3425 def _runtests(self, testdescs):
3427 def _runtests(self, testdescs):
3426 def _reloadtest(test, i):
3428 def _reloadtest(test, i):
3427 # convert a test back to its description dict
3429 # convert a test back to its description dict
3428 desc = {'path': test.path}
3430 desc = {'path': test.path}
3429 case = getattr(test, '_case', [])
3431 case = getattr(test, '_case', [])
3430 if case:
3432 if case:
3431 desc['case'] = case
3433 desc['case'] = case
3432 return self._gettest(desc, i)
3434 return self._gettest(desc, i)
3433
3435
3434 try:
3436 try:
3435 if self.options.restart:
3437 if self.options.restart:
3436 orig = list(testdescs)
3438 orig = list(testdescs)
3437 while testdescs:
3439 while testdescs:
3438 desc = testdescs[0]
3440 desc = testdescs[0]
3439 errpath = self._geterrpath(desc)
3441 errpath = self._geterrpath(desc)
3440 if os.path.exists(errpath):
3442 if os.path.exists(errpath):
3441 break
3443 break
3442 testdescs.pop(0)
3444 testdescs.pop(0)
3443 if not testdescs:
3445 if not testdescs:
3444 print("running all tests")
3446 print("running all tests")
3445 testdescs = orig
3447 testdescs = orig
3446
3448
3447 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3449 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3448 num_tests = len(tests) * self.options.runs_per_test
3450 num_tests = len(tests) * self.options.runs_per_test
3449
3451
3450 jobs = min(num_tests, self.options.jobs)
3452 jobs = min(num_tests, self.options.jobs)
3451
3453
3452 failed = False
3454 failed = False
3453 kws = self.options.keywords
3455 kws = self.options.keywords
3454 if kws is not None and PYTHON3:
3456 if kws is not None and PYTHON3:
3455 kws = kws.encode('utf-8')
3457 kws = kws.encode('utf-8')
3456
3458
3457 suite = TestSuite(
3459 suite = TestSuite(
3458 self._testdir,
3460 self._testdir,
3459 jobs=jobs,
3461 jobs=jobs,
3460 whitelist=self.options.whitelisted,
3462 whitelist=self.options.whitelisted,
3461 blacklist=self.options.blacklist,
3463 blacklist=self.options.blacklist,
3462 keywords=kws,
3464 keywords=kws,
3463 loop=self.options.loop,
3465 loop=self.options.loop,
3464 runs_per_test=self.options.runs_per_test,
3466 runs_per_test=self.options.runs_per_test,
3465 showchannels=self.options.showchannels,
3467 showchannels=self.options.showchannels,
3466 tests=tests,
3468 tests=tests,
3467 loadtest=_reloadtest,
3469 loadtest=_reloadtest,
3468 )
3470 )
3469 verbosity = 1
3471 verbosity = 1
3470 if self.options.list_tests:
3472 if self.options.list_tests:
3471 verbosity = 0
3473 verbosity = 0
3472 elif self.options.verbose:
3474 elif self.options.verbose:
3473 verbosity = 2
3475 verbosity = 2
3474 runner = TextTestRunner(self, verbosity=verbosity)
3476 runner = TextTestRunner(self, verbosity=verbosity)
3475
3477
3476 if self.options.list_tests:
3478 if self.options.list_tests:
3477 result = runner.listtests(suite)
3479 result = runner.listtests(suite)
3478 else:
3480 else:
3479 self._usecorrectpython()
3481 self._usecorrectpython()
3480 if self._installdir:
3482 if self._installdir:
3481 self._installhg()
3483 self._installhg()
3482 self._checkhglib("Testing")
3484 self._checkhglib("Testing")
3483 if self.options.chg:
3485 if self.options.chg:
3484 assert self._installdir
3486 assert self._installdir
3485 self._installchg()
3487 self._installchg()
3486 if self.options.rhg:
3488 if self.options.rhg:
3487 assert self._installdir
3489 assert self._installdir
3488 self._installrhg()
3490 self._installrhg()
3489 elif self.options.pyoxidized:
3491 elif self.options.pyoxidized:
3490 self._build_pyoxidized()
3492 self._build_pyoxidized()
3491 self._use_correct_mercurial()
3493 self._use_correct_mercurial()
3492
3494
3493 log(
3495 log(
3494 'running %d tests using %d parallel processes'
3496 'running %d tests using %d parallel processes'
3495 % (num_tests, jobs)
3497 % (num_tests, jobs)
3496 )
3498 )
3497
3499
3498 result = runner.run(suite)
3500 result = runner.run(suite)
3499
3501
3500 if result.failures or result.errors:
3502 if result.failures or result.errors:
3501 failed = True
3503 failed = True
3502
3504
3503 result.onEnd()
3505 result.onEnd()
3504
3506
3505 if self.options.anycoverage:
3507 if self.options.anycoverage:
3506 self._outputcoverage()
3508 self._outputcoverage()
3507 except KeyboardInterrupt:
3509 except KeyboardInterrupt:
3508 failed = True
3510 failed = True
3509 print("\ninterrupted!")
3511 print("\ninterrupted!")
3510
3512
3511 if failed:
3513 if failed:
3512 return 1
3514 return 1
3513
3515
3514 def _geterrpath(self, test):
3516 def _geterrpath(self, test):
3515 # test['path'] is a relative path
3517 # test['path'] is a relative path
3516 if 'case' in test:
3518 if 'case' in test:
3517 # for multiple dimensions test cases
3519 # for multiple dimensions test cases
3518 casestr = b'#'.join(test['case'])
3520 casestr = b'#'.join(test['case'])
3519 errpath = b'%s#%s.err' % (test['path'], casestr)
3521 errpath = b'%s#%s.err' % (test['path'], casestr)
3520 else:
3522 else:
3521 errpath = b'%s.err' % test['path']
3523 errpath = b'%s.err' % test['path']
3522 if self.options.outputdir:
3524 if self.options.outputdir:
3523 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3525 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3524 errpath = os.path.join(self._outputdir, errpath)
3526 errpath = os.path.join(self._outputdir, errpath)
3525 return errpath
3527 return errpath
3526
3528
3527 def _getport(self, count):
3529 def _getport(self, count):
3528 port = self._ports.get(count) # do we have a cached entry?
3530 port = self._ports.get(count) # do we have a cached entry?
3529 if port is None:
3531 if port is None:
3530 portneeded = 3
3532 portneeded = 3
3531 # above 100 tries we just give up and let test reports failure
3533 # above 100 tries we just give up and let test reports failure
3532 for tries in xrange(100):
3534 for tries in xrange(100):
3533 allfree = True
3535 allfree = True
3534 port = self.options.port + self._portoffset
3536 port = self.options.port + self._portoffset
3535 for idx in xrange(portneeded):
3537 for idx in xrange(portneeded):
3536 if not checkportisavailable(port + idx):
3538 if not checkportisavailable(port + idx):
3537 allfree = False
3539 allfree = False
3538 break
3540 break
3539 self._portoffset += portneeded
3541 self._portoffset += portneeded
3540 if allfree:
3542 if allfree:
3541 break
3543 break
3542 self._ports[count] = port
3544 self._ports[count] = port
3543 return port
3545 return port
3544
3546
3545 def _gettest(self, testdesc, count):
3547 def _gettest(self, testdesc, count):
3546 """Obtain a Test by looking at its filename.
3548 """Obtain a Test by looking at its filename.
3547
3549
3548 Returns a Test instance. The Test may not be runnable if it doesn't
3550 Returns a Test instance. The Test may not be runnable if it doesn't
3549 map to a known type.
3551 map to a known type.
3550 """
3552 """
3551 path = testdesc['path']
3553 path = testdesc['path']
3552 lctest = path.lower()
3554 lctest = path.lower()
3553 testcls = Test
3555 testcls = Test
3554
3556
3555 for ext, cls in self.TESTTYPES:
3557 for ext, cls in self.TESTTYPES:
3556 if lctest.endswith(ext):
3558 if lctest.endswith(ext):
3557 testcls = cls
3559 testcls = cls
3558 break
3560 break
3559
3561
3560 refpath = os.path.join(getcwdb(), path)
3562 refpath = os.path.join(getcwdb(), path)
3561 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3563 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3562
3564
3563 # extra keyword parameters. 'case' is used by .t tests
3565 # extra keyword parameters. 'case' is used by .t tests
3564 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3566 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3565
3567
3566 t = testcls(
3568 t = testcls(
3567 refpath,
3569 refpath,
3568 self._outputdir,
3570 self._outputdir,
3569 tmpdir,
3571 tmpdir,
3570 keeptmpdir=self.options.keep_tmpdir,
3572 keeptmpdir=self.options.keep_tmpdir,
3571 debug=self.options.debug,
3573 debug=self.options.debug,
3572 first=self.options.first,
3574 first=self.options.first,
3573 timeout=self.options.timeout,
3575 timeout=self.options.timeout,
3574 startport=self._getport(count),
3576 startport=self._getport(count),
3575 extraconfigopts=self.options.extra_config_opt,
3577 extraconfigopts=self.options.extra_config_opt,
3576 shell=self.options.shell,
3578 shell=self.options.shell,
3577 hgcommand=self._hgcommand,
3579 hgcommand=self._hgcommand,
3578 usechg=bool(self.options.with_chg or self.options.chg),
3580 usechg=bool(self.options.with_chg or self.options.chg),
3579 chgdebug=self.options.chg_debug,
3581 chgdebug=self.options.chg_debug,
3580 useipv6=useipv6,
3582 useipv6=useipv6,
3581 **kwds
3583 **kwds
3582 )
3584 )
3583 t.should_reload = True
3585 t.should_reload = True
3584 return t
3586 return t
3585
3587
3586 def _cleanup(self):
3588 def _cleanup(self):
3587 """Clean up state from this test invocation."""
3589 """Clean up state from this test invocation."""
3588 if self.options.keep_tmpdir:
3590 if self.options.keep_tmpdir:
3589 return
3591 return
3590
3592
3591 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3593 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3592 shutil.rmtree(self._hgtmp, True)
3594 shutil.rmtree(self._hgtmp, True)
3593 for f in self._createdfiles:
3595 for f in self._createdfiles:
3594 try:
3596 try:
3595 os.remove(f)
3597 os.remove(f)
3596 except OSError:
3598 except OSError:
3597 pass
3599 pass
3598
3600
3599 def _usecorrectpython(self):
3601 def _usecorrectpython(self):
3600 """Configure the environment to use the appropriate Python in tests."""
3602 """Configure the environment to use the appropriate Python in tests."""
3601 # Tests must use the same interpreter as us or bad things will happen.
3603 # Tests must use the same interpreter as us or bad things will happen.
3602 if WINDOWS and PYTHON3:
3604 if WINDOWS and PYTHON3:
3603 pyexe_names = [b'python', b'python3', b'python.exe']
3605 pyexe_names = [b'python', b'python3', b'python.exe']
3604 elif WINDOWS:
3606 elif WINDOWS:
3605 pyexe_names = [b'python', b'python.exe']
3607 pyexe_names = [b'python', b'python.exe']
3606 elif PYTHON3:
3608 elif PYTHON3:
3607 pyexe_names = [b'python', b'python3']
3609 pyexe_names = [b'python', b'python3']
3608 else:
3610 else:
3609 pyexe_names = [b'python', b'python2']
3611 pyexe_names = [b'python', b'python2']
3610
3612
3611 # os.symlink() is a thing with py3 on Windows, but it requires
3613 # os.symlink() is a thing with py3 on Windows, but it requires
3612 # Administrator rights.
3614 # Administrator rights.
3613 if not WINDOWS and getattr(os, 'symlink', None):
3615 if not WINDOWS and getattr(os, 'symlink', None):
3614 msg = "# Making python executable in test path a symlink to '%s'"
3616 msg = "# Making python executable in test path a symlink to '%s'"
3615 msg %= sysexecutable
3617 msg %= sysexecutable
3616 vlog(msg)
3618 vlog(msg)
3617 for pyexename in pyexe_names:
3619 for pyexename in pyexe_names:
3618 mypython = os.path.join(self._custom_bin_dir, pyexename)
3620 mypython = os.path.join(self._custom_bin_dir, pyexename)
3619 try:
3621 try:
3620 if os.readlink(mypython) == sysexecutable:
3622 if os.readlink(mypython) == sysexecutable:
3621 continue
3623 continue
3622 os.unlink(mypython)
3624 os.unlink(mypython)
3623 except OSError as err:
3625 except OSError as err:
3624 if err.errno != errno.ENOENT:
3626 if err.errno != errno.ENOENT:
3625 raise
3627 raise
3626 if self._findprogram(pyexename) != sysexecutable:
3628 if self._findprogram(pyexename) != sysexecutable:
3627 try:
3629 try:
3628 os.symlink(sysexecutable, mypython)
3630 os.symlink(sysexecutable, mypython)
3629 self._createdfiles.append(mypython)
3631 self._createdfiles.append(mypython)
3630 except OSError as err:
3632 except OSError as err:
3631 # child processes may race, which is harmless
3633 # child processes may race, which is harmless
3632 if err.errno != errno.EEXIST:
3634 if err.errno != errno.EEXIST:
3633 raise
3635 raise
3634 elif WINDOWS and not os.getenv('MSYSTEM'):
3636 elif WINDOWS and not os.getenv('MSYSTEM'):
3635 raise AssertionError('cannot run test on Windows without MSYSTEM')
3637 raise AssertionError('cannot run test on Windows without MSYSTEM')
3636 else:
3638 else:
3637 # Generate explicit file instead of symlink
3639 # Generate explicit file instead of symlink
3638 #
3640 #
3639 # This is especially important as Windows doesn't have
3641 # This is especially important as Windows doesn't have
3640 # `python3.exe`, and MSYS cannot understand the reparse point with
3642 # `python3.exe`, and MSYS cannot understand the reparse point with
3641 # that name provided by Microsoft. Create a simple script on PATH
3643 # that name provided by Microsoft. Create a simple script on PATH
3642 # with that name that delegates to the py3 launcher so the shebang
3644 # with that name that delegates to the py3 launcher so the shebang
3643 # lines work.
3645 # lines work.
3644 esc_executable = _sys2bytes(shellquote(sysexecutable))
3646 esc_executable = _sys2bytes(shellquote(sysexecutable))
3645 for pyexename in pyexe_names:
3647 for pyexename in pyexe_names:
3646 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3648 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3647 with open(stub_exec_path, 'wb') as f:
3649 with open(stub_exec_path, 'wb') as f:
3648 f.write(b'#!/bin/sh\n')
3650 f.write(b'#!/bin/sh\n')
3649 f.write(b'%s "$@"\n' % esc_executable)
3651 f.write(b'%s "$@"\n' % esc_executable)
3650
3652
3651 if WINDOWS:
3653 if WINDOWS:
3652 if not PYTHON3:
3654 if not PYTHON3:
3653 # lets try to build a valid python3 executable for the
3655 # lets try to build a valid python3 executable for the
3654 # scrip that requires it.
3656 # scrip that requires it.
3655 py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
3657 py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
3656 with open(py3exe_name, 'wb') as f:
3658 with open(py3exe_name, 'wb') as f:
3657 f.write(b'#!/bin/sh\n')
3659 f.write(b'#!/bin/sh\n')
3658 f.write(b'py -3 "$@"\n')
3660 f.write(b'py -3 "$@"\n')
3659
3661
3660 # adjust the path to make sur the main python finds it own dll
3662 # adjust the path to make sur the main python finds it own dll
3661 path = os.environ['PATH'].split(os.pathsep)
3663 path = os.environ['PATH'].split(os.pathsep)
3662 main_exec_dir = os.path.dirname(sysexecutable)
3664 main_exec_dir = os.path.dirname(sysexecutable)
3663 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3665 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3664
3666
3665 # Binaries installed by pip into the user area like pylint.exe may
3667 # Binaries installed by pip into the user area like pylint.exe may
3666 # not be in PATH by default.
3668 # not be in PATH by default.
3667 appdata = os.environ.get('APPDATA')
3669 appdata = os.environ.get('APPDATA')
3668 vi = sys.version_info
3670 vi = sys.version_info
3669 if appdata is not None:
3671 if appdata is not None:
3670 python_dir = 'Python%d%d' % (vi[0], vi[1])
3672 python_dir = 'Python%d%d' % (vi[0], vi[1])
3671 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3673 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3672 if not PYTHON3:
3674 if not PYTHON3:
3673 scripts_path = [appdata, 'Python', 'Scripts']
3675 scripts_path = [appdata, 'Python', 'Scripts']
3674 scripts_dir = os.path.join(*scripts_path)
3676 scripts_dir = os.path.join(*scripts_path)
3675 extra_paths.append(scripts_dir)
3677 extra_paths.append(scripts_dir)
3676
3678
3677 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3679 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3678
3680
3679 def _use_correct_mercurial(self):
3681 def _use_correct_mercurial(self):
3680 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3682 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3681 if self._hgcommand != b'hg':
3683 if self._hgcommand != b'hg':
3682 # shutil.which only accept bytes from 3.8
3684 # shutil.which only accept bytes from 3.8
3683 real_exec = which(self._hgcommand)
3685 real_exec = which(self._hgcommand)
3684 if real_exec is None:
3686 if real_exec is None:
3685 raise ValueError('could not find exec path for "%s"', real_exec)
3687 raise ValueError('could not find exec path for "%s"', real_exec)
3686 if real_exec == target_exec:
3688 if real_exec == target_exec:
3687 # do not overwrite something with itself
3689 # do not overwrite something with itself
3688 return
3690 return
3689 if WINDOWS:
3691 if WINDOWS:
3690 with open(target_exec, 'wb') as f:
3692 with open(target_exec, 'wb') as f:
3691 f.write(b'#!/bin/sh\n')
3693 f.write(b'#!/bin/sh\n')
3692 escaped_exec = shellquote(_bytes2sys(real_exec))
3694 escaped_exec = shellquote(_bytes2sys(real_exec))
3693 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3695 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3694 else:
3696 else:
3695 os.symlink(real_exec, target_exec)
3697 os.symlink(real_exec, target_exec)
3696 self._createdfiles.append(target_exec)
3698 self._createdfiles.append(target_exec)
3697
3699
3698 def _installhg(self):
3700 def _installhg(self):
3699 """Install hg into the test environment.
3701 """Install hg into the test environment.
3700
3702
3701 This will also configure hg with the appropriate testing settings.
3703 This will also configure hg with the appropriate testing settings.
3702 """
3704 """
3703 vlog("# Performing temporary installation of HG")
3705 vlog("# Performing temporary installation of HG")
3704 installerrs = os.path.join(self._hgtmp, b"install.err")
3706 installerrs = os.path.join(self._hgtmp, b"install.err")
3705 compiler = ''
3707 compiler = ''
3706 if self.options.compiler:
3708 if self.options.compiler:
3707 compiler = '--compiler ' + self.options.compiler
3709 compiler = '--compiler ' + self.options.compiler
3708 setup_opts = b""
3710 setup_opts = b""
3709 if self.options.pure:
3711 if self.options.pure:
3710 setup_opts = b"--pure"
3712 setup_opts = b"--pure"
3711 elif self.options.rust:
3713 elif self.options.rust:
3712 setup_opts = b"--rust"
3714 setup_opts = b"--rust"
3713 elif self.options.no_rust:
3715 elif self.options.no_rust:
3714 setup_opts = b"--no-rust"
3716 setup_opts = b"--no-rust"
3715
3717
3716 # Run installer in hg root
3718 # Run installer in hg root
3717 script = os.path.realpath(sys.argv[0])
3719 script = os.path.realpath(sys.argv[0])
3718 exe = sysexecutable
3720 exe = sysexecutable
3719 if PYTHON3:
3721 if PYTHON3:
3720 compiler = _sys2bytes(compiler)
3722 compiler = _sys2bytes(compiler)
3721 script = _sys2bytes(script)
3723 script = _sys2bytes(script)
3722 exe = _sys2bytes(exe)
3724 exe = _sys2bytes(exe)
3723 hgroot = os.path.dirname(os.path.dirname(script))
3725 hgroot = os.path.dirname(os.path.dirname(script))
3724 self._hgroot = hgroot
3726 self._hgroot = hgroot
3725 os.chdir(hgroot)
3727 os.chdir(hgroot)
3726 nohome = b'--home=""'
3728 nohome = b'--home=""'
3727 if WINDOWS:
3729 if WINDOWS:
3728 # The --home="" trick works only on OS where os.sep == '/'
3730 # The --home="" trick works only on OS where os.sep == '/'
3729 # because of a distutils convert_path() fast-path. Avoid it at
3731 # because of a distutils convert_path() fast-path. Avoid it at
3730 # least on Windows for now, deal with .pydistutils.cfg bugs
3732 # least on Windows for now, deal with .pydistutils.cfg bugs
3731 # when they happen.
3733 # when they happen.
3732 nohome = b''
3734 nohome = b''
3733 cmd = (
3735 cmd = (
3734 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3736 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3735 b' build %(compiler)s --build-base="%(base)s"'
3737 b' build %(compiler)s --build-base="%(base)s"'
3736 b' install --force --prefix="%(prefix)s"'
3738 b' install --force --prefix="%(prefix)s"'
3737 b' --install-lib="%(libdir)s"'
3739 b' --install-lib="%(libdir)s"'
3738 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3740 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3739 % {
3741 % {
3740 b'exe': exe,
3742 b'exe': exe,
3741 b'setup_opts': setup_opts,
3743 b'setup_opts': setup_opts,
3742 b'compiler': compiler,
3744 b'compiler': compiler,
3743 b'base': os.path.join(self._hgtmp, b"build"),
3745 b'base': os.path.join(self._hgtmp, b"build"),
3744 b'prefix': self._installdir,
3746 b'prefix': self._installdir,
3745 b'libdir': self._pythondir,
3747 b'libdir': self._pythondir,
3746 b'bindir': self._bindir,
3748 b'bindir': self._bindir,
3747 b'nohome': nohome,
3749 b'nohome': nohome,
3748 b'logfile': installerrs,
3750 b'logfile': installerrs,
3749 }
3751 }
3750 )
3752 )
3751
3753
3752 # setuptools requires install directories to exist.
3754 # setuptools requires install directories to exist.
3753 def makedirs(p):
3755 def makedirs(p):
3754 try:
3756 try:
3755 os.makedirs(p)
3757 os.makedirs(p)
3756 except OSError as e:
3758 except OSError as e:
3757 if e.errno != errno.EEXIST:
3759 if e.errno != errno.EEXIST:
3758 raise
3760 raise
3759
3761
3760 makedirs(self._pythondir)
3762 makedirs(self._pythondir)
3761 makedirs(self._bindir)
3763 makedirs(self._bindir)
3762
3764
3763 vlog("# Running", cmd.decode("utf-8"))
3765 vlog("# Running", cmd.decode("utf-8"))
3764 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3766 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3765 if not self.options.verbose:
3767 if not self.options.verbose:
3766 try:
3768 try:
3767 os.remove(installerrs)
3769 os.remove(installerrs)
3768 except OSError as e:
3770 except OSError as e:
3769 if e.errno != errno.ENOENT:
3771 if e.errno != errno.ENOENT:
3770 raise
3772 raise
3771 else:
3773 else:
3772 with open(installerrs, 'rb') as f:
3774 with open(installerrs, 'rb') as f:
3773 for line in f:
3775 for line in f:
3774 if PYTHON3:
3776 if PYTHON3:
3775 sys.stdout.buffer.write(line)
3777 sys.stdout.buffer.write(line)
3776 else:
3778 else:
3777 sys.stdout.write(line)
3779 sys.stdout.write(line)
3778 sys.exit(1)
3780 sys.exit(1)
3779 os.chdir(self._testdir)
3781 os.chdir(self._testdir)
3780
3782
3781 hgbat = os.path.join(self._bindir, b'hg.bat')
3783 hgbat = os.path.join(self._bindir, b'hg.bat')
3782 if os.path.isfile(hgbat):
3784 if os.path.isfile(hgbat):
3783 # hg.bat expects to be put in bin/scripts while run-tests.py
3785 # hg.bat expects to be put in bin/scripts while run-tests.py
3784 # installation layout put it in bin/ directly. Fix it
3786 # installation layout put it in bin/ directly. Fix it
3785 with open(hgbat, 'rb') as f:
3787 with open(hgbat, 'rb') as f:
3786 data = f.read()
3788 data = f.read()
3787 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3789 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3788 data = data.replace(
3790 data = data.replace(
3789 br'"%~dp0..\python" "%~dp0hg" %*',
3791 br'"%~dp0..\python" "%~dp0hg" %*',
3790 b'"%~dp0python" "%~dp0hg" %*',
3792 b'"%~dp0python" "%~dp0hg" %*',
3791 )
3793 )
3792 with open(hgbat, 'wb') as f:
3794 with open(hgbat, 'wb') as f:
3793 f.write(data)
3795 f.write(data)
3794 else:
3796 else:
3795 print('WARNING: cannot fix hg.bat reference to python.exe')
3797 print('WARNING: cannot fix hg.bat reference to python.exe')
3796
3798
3797 if self.options.anycoverage:
3799 if self.options.anycoverage:
3798 custom = os.path.join(
3800 custom = os.path.join(
3799 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3801 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3800 )
3802 )
3801 target = os.path.join(self._pythondir, b'sitecustomize.py')
3803 target = os.path.join(self._pythondir, b'sitecustomize.py')
3802 vlog('# Installing coverage trigger to %s' % target)
3804 vlog('# Installing coverage trigger to %s' % target)
3803 shutil.copyfile(custom, target)
3805 shutil.copyfile(custom, target)
3804 rc = os.path.join(self._testdir, b'.coveragerc')
3806 rc = os.path.join(self._testdir, b'.coveragerc')
3805 vlog('# Installing coverage rc to %s' % rc)
3807 vlog('# Installing coverage rc to %s' % rc)
3806 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3808 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3807 covdir = os.path.join(self._installdir, b'..', b'coverage')
3809 covdir = os.path.join(self._installdir, b'..', b'coverage')
3808 try:
3810 try:
3809 os.mkdir(covdir)
3811 os.mkdir(covdir)
3810 except OSError as e:
3812 except OSError as e:
3811 if e.errno != errno.EEXIST:
3813 if e.errno != errno.EEXIST:
3812 raise
3814 raise
3813
3815
3814 osenvironb[b'COVERAGE_DIR'] = covdir
3816 osenvironb[b'COVERAGE_DIR'] = covdir
3815
3817
3816 def _checkhglib(self, verb):
3818 def _checkhglib(self, verb):
3817 """Ensure that the 'mercurial' package imported by python is
3819 """Ensure that the 'mercurial' package imported by python is
3818 the one we expect it to be. If not, print a warning to stderr."""
3820 the one we expect it to be. If not, print a warning to stderr."""
3819 if self._pythondir_inferred:
3821 if self._pythondir_inferred:
3820 # The pythondir has been inferred from --with-hg flag.
3822 # The pythondir has been inferred from --with-hg flag.
3821 # We cannot expect anything sensible here.
3823 # We cannot expect anything sensible here.
3822 return
3824 return
3823 expecthg = os.path.join(self._pythondir, b'mercurial')
3825 expecthg = os.path.join(self._pythondir, b'mercurial')
3824 actualhg = self._gethgpath()
3826 actualhg = self._gethgpath()
3825 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3827 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3826 sys.stderr.write(
3828 sys.stderr.write(
3827 'warning: %s with unexpected mercurial lib: %s\n'
3829 'warning: %s with unexpected mercurial lib: %s\n'
3828 ' (expected %s)\n' % (verb, actualhg, expecthg)
3830 ' (expected %s)\n' % (verb, actualhg, expecthg)
3829 )
3831 )
3830
3832
3831 def _gethgpath(self):
3833 def _gethgpath(self):
3832 """Return the path to the mercurial package that is actually found by
3834 """Return the path to the mercurial package that is actually found by
3833 the current Python interpreter."""
3835 the current Python interpreter."""
3834 if self._hgpath is not None:
3836 if self._hgpath is not None:
3835 return self._hgpath
3837 return self._hgpath
3836
3838
3837 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3839 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3838 cmd = cmd % PYTHON
3840 cmd = cmd % PYTHON
3839 if PYTHON3:
3841 if PYTHON3:
3840 cmd = _bytes2sys(cmd)
3842 cmd = _bytes2sys(cmd)
3841
3843
3842 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3844 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3843 out, err = p.communicate()
3845 out, err = p.communicate()
3844
3846
3845 self._hgpath = out.strip()
3847 self._hgpath = out.strip()
3846
3848
3847 return self._hgpath
3849 return self._hgpath
3848
3850
3849 def _installchg(self):
3851 def _installchg(self):
3850 """Install chg into the test environment"""
3852 """Install chg into the test environment"""
3851 vlog('# Performing temporary installation of CHG')
3853 vlog('# Performing temporary installation of CHG')
3852 assert os.path.dirname(self._bindir) == self._installdir
3854 assert os.path.dirname(self._bindir) == self._installdir
3853 assert self._hgroot, 'must be called after _installhg()'
3855 assert self._hgroot, 'must be called after _installhg()'
3854 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3856 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3855 b'make': b'make', # TODO: switch by option or environment?
3857 b'make': b'make', # TODO: switch by option or environment?
3856 b'prefix': self._installdir,
3858 b'prefix': self._installdir,
3857 }
3859 }
3858 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3860 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3859 vlog("# Running", cmd)
3861 vlog("# Running", cmd)
3860 proc = subprocess.Popen(
3862 proc = subprocess.Popen(
3861 cmd,
3863 cmd,
3862 shell=True,
3864 shell=True,
3863 cwd=cwd,
3865 cwd=cwd,
3864 stdin=subprocess.PIPE,
3866 stdin=subprocess.PIPE,
3865 stdout=subprocess.PIPE,
3867 stdout=subprocess.PIPE,
3866 stderr=subprocess.STDOUT,
3868 stderr=subprocess.STDOUT,
3867 )
3869 )
3868 out, _err = proc.communicate()
3870 out, _err = proc.communicate()
3869 if proc.returncode != 0:
3871 if proc.returncode != 0:
3870 if PYTHON3:
3872 if PYTHON3:
3871 sys.stdout.buffer.write(out)
3873 sys.stdout.buffer.write(out)
3872 else:
3874 else:
3873 sys.stdout.write(out)
3875 sys.stdout.write(out)
3874 sys.exit(1)
3876 sys.exit(1)
3875
3877
3876 def _installrhg(self):
3878 def _installrhg(self):
3877 """Install rhg into the test environment"""
3879 """Install rhg into the test environment"""
3878 vlog('# Performing temporary installation of rhg')
3880 vlog('# Performing temporary installation of rhg')
3879 assert os.path.dirname(self._bindir) == self._installdir
3881 assert os.path.dirname(self._bindir) == self._installdir
3880 assert self._hgroot, 'must be called after _installhg()'
3882 assert self._hgroot, 'must be called after _installhg()'
3881 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3883 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3882 b'make': b'make', # TODO: switch by option or environment?
3884 b'make': b'make', # TODO: switch by option or environment?
3883 b'prefix': self._installdir,
3885 b'prefix': self._installdir,
3884 }
3886 }
3885 cwd = self._hgroot
3887 cwd = self._hgroot
3886 vlog("# Running", cmd)
3888 vlog("# Running", cmd)
3887 proc = subprocess.Popen(
3889 proc = subprocess.Popen(
3888 cmd,
3890 cmd,
3889 shell=True,
3891 shell=True,
3890 cwd=cwd,
3892 cwd=cwd,
3891 stdin=subprocess.PIPE,
3893 stdin=subprocess.PIPE,
3892 stdout=subprocess.PIPE,
3894 stdout=subprocess.PIPE,
3893 stderr=subprocess.STDOUT,
3895 stderr=subprocess.STDOUT,
3894 )
3896 )
3895 out, _err = proc.communicate()
3897 out, _err = proc.communicate()
3896 if proc.returncode != 0:
3898 if proc.returncode != 0:
3897 if PYTHON3:
3899 if PYTHON3:
3898 sys.stdout.buffer.write(out)
3900 sys.stdout.buffer.write(out)
3899 else:
3901 else:
3900 sys.stdout.write(out)
3902 sys.stdout.write(out)
3901 sys.exit(1)
3903 sys.exit(1)
3902
3904
3903 def _build_pyoxidized(self):
3905 def _build_pyoxidized(self):
3904 """build a pyoxidized version of mercurial into the test environment
3906 """build a pyoxidized version of mercurial into the test environment
3905
3907
3906 Ideally this function would be `install_pyoxidier` and would both build
3908 Ideally this function would be `install_pyoxidier` and would both build
3907 and install pyoxidier. However we are starting small to get pyoxidizer
3909 and install pyoxidier. However we are starting small to get pyoxidizer
3908 build binary to testing quickly.
3910 build binary to testing quickly.
3909 """
3911 """
3910 vlog('# build a pyoxidized version of Mercurial')
3912 vlog('# build a pyoxidized version of Mercurial')
3911 assert os.path.dirname(self._bindir) == self._installdir
3913 assert os.path.dirname(self._bindir) == self._installdir
3912 assert self._hgroot, 'must be called after _installhg()'
3914 assert self._hgroot, 'must be called after _installhg()'
3913 cmd = b'"%(make)s" pyoxidizer-windows-tests' % {
3915 cmd = b'"%(make)s" pyoxidizer-windows-tests' % {
3914 b'make': b'make',
3916 b'make': b'make',
3915 }
3917 }
3916 cwd = self._hgroot
3918 cwd = self._hgroot
3917 vlog("# Running", cmd)
3919 vlog("# Running", cmd)
3918 proc = subprocess.Popen(
3920 proc = subprocess.Popen(
3919 _bytes2sys(cmd),
3921 _bytes2sys(cmd),
3920 shell=True,
3922 shell=True,
3921 cwd=_bytes2sys(cwd),
3923 cwd=_bytes2sys(cwd),
3922 stdin=subprocess.PIPE,
3924 stdin=subprocess.PIPE,
3923 stdout=subprocess.PIPE,
3925 stdout=subprocess.PIPE,
3924 stderr=subprocess.STDOUT,
3926 stderr=subprocess.STDOUT,
3925 )
3927 )
3926 out, _err = proc.communicate()
3928 out, _err = proc.communicate()
3927 if proc.returncode != 0:
3929 if proc.returncode != 0:
3928 if PYTHON3:
3930 if PYTHON3:
3929 sys.stdout.buffer.write(out)
3931 sys.stdout.buffer.write(out)
3930 else:
3932 else:
3931 sys.stdout.write(out)
3933 sys.stdout.write(out)
3932 sys.exit(1)
3934 sys.exit(1)
3933
3935
3934 def _outputcoverage(self):
3936 def _outputcoverage(self):
3935 """Produce code coverage output."""
3937 """Produce code coverage output."""
3936 import coverage
3938 import coverage
3937
3939
3938 coverage = coverage.coverage
3940 coverage = coverage.coverage
3939
3941
3940 vlog('# Producing coverage report')
3942 vlog('# Producing coverage report')
3941 # chdir is the easiest way to get short, relative paths in the
3943 # chdir is the easiest way to get short, relative paths in the
3942 # output.
3944 # output.
3943 os.chdir(self._hgroot)
3945 os.chdir(self._hgroot)
3944 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3946 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3945 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3947 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3946
3948
3947 # Map install directory paths back to source directory.
3949 # Map install directory paths back to source directory.
3948 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3950 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3949
3951
3950 cov.combine()
3952 cov.combine()
3951
3953
3952 omit = [
3954 omit = [
3953 _bytes2sys(os.path.join(x, b'*'))
3955 _bytes2sys(os.path.join(x, b'*'))
3954 for x in [self._bindir, self._testdir]
3956 for x in [self._bindir, self._testdir]
3955 ]
3957 ]
3956 cov.report(ignore_errors=True, omit=omit)
3958 cov.report(ignore_errors=True, omit=omit)
3957
3959
3958 if self.options.htmlcov:
3960 if self.options.htmlcov:
3959 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3961 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3960 cov.html_report(directory=htmldir, omit=omit)
3962 cov.html_report(directory=htmldir, omit=omit)
3961 if self.options.annotate:
3963 if self.options.annotate:
3962 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3964 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3963 if not os.path.isdir(adir):
3965 if not os.path.isdir(adir):
3964 os.mkdir(adir)
3966 os.mkdir(adir)
3965 cov.annotate(directory=adir, omit=omit)
3967 cov.annotate(directory=adir, omit=omit)
3966
3968
3967 def _findprogram(self, program):
3969 def _findprogram(self, program):
3968 """Search PATH for a executable program"""
3970 """Search PATH for a executable program"""
3969 dpb = _sys2bytes(os.defpath)
3971 dpb = _sys2bytes(os.defpath)
3970 sepb = _sys2bytes(os.pathsep)
3972 sepb = _sys2bytes(os.pathsep)
3971 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3973 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3972 name = os.path.join(p, program)
3974 name = os.path.join(p, program)
3973 if WINDOWS or os.access(name, os.X_OK):
3975 if WINDOWS or os.access(name, os.X_OK):
3974 return _bytes2sys(name)
3976 return _bytes2sys(name)
3975 return None
3977 return None
3976
3978
3977 def _checktools(self):
3979 def _checktools(self):
3978 """Ensure tools required to run tests are present."""
3980 """Ensure tools required to run tests are present."""
3979 for p in self.REQUIREDTOOLS:
3981 for p in self.REQUIREDTOOLS:
3980 if WINDOWS and not p.endswith(b'.exe'):
3982 if WINDOWS and not p.endswith(b'.exe'):
3981 p += b'.exe'
3983 p += b'.exe'
3982 found = self._findprogram(p)
3984 found = self._findprogram(p)
3983 p = p.decode("utf-8")
3985 p = p.decode("utf-8")
3984 if found:
3986 if found:
3985 vlog("# Found prerequisite", p, "at", found)
3987 vlog("# Found prerequisite", p, "at", found)
3986 else:
3988 else:
3987 print("WARNING: Did not find prerequisite tool: %s " % p)
3989 print("WARNING: Did not find prerequisite tool: %s " % p)
3988
3990
3989
3991
3990 def aggregateexceptions(path):
3992 def aggregateexceptions(path):
3991 exceptioncounts = collections.Counter()
3993 exceptioncounts = collections.Counter()
3992 testsbyfailure = collections.defaultdict(set)
3994 testsbyfailure = collections.defaultdict(set)
3993 failuresbytest = collections.defaultdict(set)
3995 failuresbytest = collections.defaultdict(set)
3994
3996
3995 for f in os.listdir(path):
3997 for f in os.listdir(path):
3996 with open(os.path.join(path, f), 'rb') as fh:
3998 with open(os.path.join(path, f), 'rb') as fh:
3997 data = fh.read().split(b'\0')
3999 data = fh.read().split(b'\0')
3998 if len(data) != 5:
4000 if len(data) != 5:
3999 continue
4001 continue
4000
4002
4001 exc, mainframe, hgframe, hgline, testname = data
4003 exc, mainframe, hgframe, hgline, testname = data
4002 exc = exc.decode('utf-8')
4004 exc = exc.decode('utf-8')
4003 mainframe = mainframe.decode('utf-8')
4005 mainframe = mainframe.decode('utf-8')
4004 hgframe = hgframe.decode('utf-8')
4006 hgframe = hgframe.decode('utf-8')
4005 hgline = hgline.decode('utf-8')
4007 hgline = hgline.decode('utf-8')
4006 testname = testname.decode('utf-8')
4008 testname = testname.decode('utf-8')
4007
4009
4008 key = (hgframe, hgline, exc)
4010 key = (hgframe, hgline, exc)
4009 exceptioncounts[key] += 1
4011 exceptioncounts[key] += 1
4010 testsbyfailure[key].add(testname)
4012 testsbyfailure[key].add(testname)
4011 failuresbytest[testname].add(key)
4013 failuresbytest[testname].add(key)
4012
4014
4013 # Find test having fewest failures for each failure.
4015 # Find test having fewest failures for each failure.
4014 leastfailing = {}
4016 leastfailing = {}
4015 for key, tests in testsbyfailure.items():
4017 for key, tests in testsbyfailure.items():
4016 fewesttest = None
4018 fewesttest = None
4017 fewestcount = 99999999
4019 fewestcount = 99999999
4018 for test in sorted(tests):
4020 for test in sorted(tests):
4019 if len(failuresbytest[test]) < fewestcount:
4021 if len(failuresbytest[test]) < fewestcount:
4020 fewesttest = test
4022 fewesttest = test
4021 fewestcount = len(failuresbytest[test])
4023 fewestcount = len(failuresbytest[test])
4022
4024
4023 leastfailing[key] = (fewestcount, fewesttest)
4025 leastfailing[key] = (fewestcount, fewesttest)
4024
4026
4025 # Create a combined counter so we can sort by total occurrences and
4027 # Create a combined counter so we can sort by total occurrences and
4026 # impacted tests.
4028 # impacted tests.
4027 combined = {}
4029 combined = {}
4028 for key in exceptioncounts:
4030 for key in exceptioncounts:
4029 combined[key] = (
4031 combined[key] = (
4030 exceptioncounts[key],
4032 exceptioncounts[key],
4031 len(testsbyfailure[key]),
4033 len(testsbyfailure[key]),
4032 leastfailing[key][0],
4034 leastfailing[key][0],
4033 leastfailing[key][1],
4035 leastfailing[key][1],
4034 )
4036 )
4035
4037
4036 return {
4038 return {
4037 'exceptioncounts': exceptioncounts,
4039 'exceptioncounts': exceptioncounts,
4038 'total': sum(exceptioncounts.values()),
4040 'total': sum(exceptioncounts.values()),
4039 'combined': combined,
4041 'combined': combined,
4040 'leastfailing': leastfailing,
4042 'leastfailing': leastfailing,
4041 'byfailure': testsbyfailure,
4043 'byfailure': testsbyfailure,
4042 'bytest': failuresbytest,
4044 'bytest': failuresbytest,
4043 }
4045 }
4044
4046
4045
4047
4046 if __name__ == '__main__':
4048 if __name__ == '__main__':
4047 if WINDOWS and not os.getenv('MSYSTEM'):
4049 if WINDOWS and not os.getenv('MSYSTEM'):
4048 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4050 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4049 print(
4051 print(
4050 '(if you need to do so contact the mercurial devs: '
4052 '(if you need to do so contact the mercurial devs: '
4051 'mercurial@mercurial-scm.org)',
4053 'mercurial@mercurial-scm.org)',
4052 file=sys.stderr,
4054 file=sys.stderr,
4053 )
4055 )
4054 sys.exit(255)
4056 sys.exit(255)
4055
4057
4056 runner = TestRunner()
4058 runner = TestRunner()
4057
4059
4058 try:
4060 try:
4059 import msvcrt
4061 import msvcrt
4060
4062
4061 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4063 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4062 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4064 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4063 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4065 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4064 except ImportError:
4066 except ImportError:
4065 pass
4067 pass
4066
4068
4067 sys.exit(runner.run(sys.argv[1:]))
4069 sys.exit(runner.run(sys.argv[1:]))
@@ -1,256 +1,257 b''
1 Create a repository:
1 Create a repository:
2
2
3 #if no-extraextensions
3 #if no-extraextensions
4 $ hg config
4 $ hg config
5 chgserver.idletimeout=60
5 chgserver.idletimeout=60
6 devel.all-warnings=true
6 devel.all-warnings=true
7 devel.default-date=0 0
7 devel.default-date=0 0
8 extensions.fsmonitor= (fsmonitor !)
8 extensions.fsmonitor= (fsmonitor !)
9 format.exp-dirstate-v2=1 (dirstate-v2 !)
9 format.exp-dirstate-v2=1 (dirstate-v2 !)
10 largefiles.usercache=$TESTTMP/.cache/largefiles
10 largefiles.usercache=$TESTTMP/.cache/largefiles
11 lfs.usercache=$TESTTMP/.cache/lfs
11 lfs.usercache=$TESTTMP/.cache/lfs
12 ui.slash=True
12 ui.slash=True
13 ui.interactive=False
13 ui.interactive=False
14 ui.detailed-exit-code=True
14 ui.detailed-exit-code=True
15 ui.merge=internal:merge
15 ui.merge=internal:merge
16 ui.mergemarkers=detailed
16 ui.mergemarkers=detailed
17 ui.promptecho=True
17 ui.promptecho=True
18 ui.ssh=* (glob)
18 ui.timeout.warn=15
19 ui.timeout.warn=15
19 web.address=localhost
20 web.address=localhost
20 web\.ipv6=(?:True|False) (re)
21 web\.ipv6=(?:True|False) (re)
21 web.server-header=testing stub value
22 web.server-header=testing stub value
22 #endif
23 #endif
23
24
24 $ hg init t
25 $ hg init t
25 $ cd t
26 $ cd t
26
27
27 Prepare a changeset:
28 Prepare a changeset:
28
29
29 $ echo a > a
30 $ echo a > a
30 $ hg add a
31 $ hg add a
31
32
32 $ hg status
33 $ hg status
33 A a
34 A a
34
35
35 Writes to stdio succeed and fail appropriately
36 Writes to stdio succeed and fail appropriately
36
37
37 #if devfull
38 #if devfull
38 $ hg status 2>/dev/full
39 $ hg status 2>/dev/full
39 A a
40 A a
40
41
41 $ hg status >/dev/full
42 $ hg status >/dev/full
42 abort: No space left on device
43 abort: No space left on device
43 [255]
44 [255]
44 #endif
45 #endif
45
46
46 #if devfull
47 #if devfull
47 $ hg status >/dev/full 2>&1
48 $ hg status >/dev/full 2>&1
48 [255]
49 [255]
49
50
50 $ hg status ENOENT 2>/dev/full
51 $ hg status ENOENT 2>/dev/full
51 [255]
52 [255]
52 #endif
53 #endif
53
54
54 On Python 3, stdio may be None:
55 On Python 3, stdio may be None:
55
56
56 $ hg debuguiprompt --config ui.interactive=true 0<&-
57 $ hg debuguiprompt --config ui.interactive=true 0<&-
57 abort: Bad file descriptor
58 abort: Bad file descriptor
58 [255]
59 [255]
59 $ hg version -q 0<&-
60 $ hg version -q 0<&-
60 Mercurial Distributed SCM * (glob)
61 Mercurial Distributed SCM * (glob)
61
62
62 #if py3
63 #if py3
63 $ hg version -q 1>&-
64 $ hg version -q 1>&-
64 abort: Bad file descriptor
65 abort: Bad file descriptor
65 [255]
66 [255]
66 #else
67 #else
67 $ hg version -q 1>&-
68 $ hg version -q 1>&-
68 #endif
69 #endif
69 $ hg unknown -q 1>&-
70 $ hg unknown -q 1>&-
70 hg: unknown command 'unknown'
71 hg: unknown command 'unknown'
71 (did you mean debugknown?)
72 (did you mean debugknown?)
72 [10]
73 [10]
73
74
74 $ hg version -q 2>&-
75 $ hg version -q 2>&-
75 Mercurial Distributed SCM * (glob)
76 Mercurial Distributed SCM * (glob)
76 $ hg unknown -q 2>&-
77 $ hg unknown -q 2>&-
77 [10]
78 [10]
78
79
79 $ hg commit -m test
80 $ hg commit -m test
80
81
81 This command is ancient:
82 This command is ancient:
82
83
83 $ hg history
84 $ hg history
84 changeset: 0:acb14030fe0a
85 changeset: 0:acb14030fe0a
85 tag: tip
86 tag: tip
86 user: test
87 user: test
87 date: Thu Jan 01 00:00:00 1970 +0000
88 date: Thu Jan 01 00:00:00 1970 +0000
88 summary: test
89 summary: test
89
90
90
91
91 Verify that updating to revision 0 via commands.update() works properly
92 Verify that updating to revision 0 via commands.update() works properly
92
93
93 $ cat <<EOF > update_to_rev0.py
94 $ cat <<EOF > update_to_rev0.py
94 > from mercurial import commands, hg, ui as uimod
95 > from mercurial import commands, hg, ui as uimod
95 > myui = uimod.ui.load()
96 > myui = uimod.ui.load()
96 > repo = hg.repository(myui, path=b'.')
97 > repo = hg.repository(myui, path=b'.')
97 > commands.update(myui, repo, rev=b"0")
98 > commands.update(myui, repo, rev=b"0")
98 > EOF
99 > EOF
99 $ hg up null
100 $ hg up null
100 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
101 $ "$PYTHON" ./update_to_rev0.py
102 $ "$PYTHON" ./update_to_rev0.py
102 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 $ hg identify -n
104 $ hg identify -n
104 0
105 0
105
106
106
107
107 Poke around at hashes:
108 Poke around at hashes:
108
109
109 $ hg manifest --debug
110 $ hg manifest --debug
110 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
111 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
111
112
112 $ hg cat a
113 $ hg cat a
113 a
114 a
114
115
115 Verify should succeed:
116 Verify should succeed:
116
117
117 $ hg verify
118 $ hg verify
118 checking changesets
119 checking changesets
119 checking manifests
120 checking manifests
120 crosschecking files in changesets and manifests
121 crosschecking files in changesets and manifests
121 checking files
122 checking files
122 checked 1 changesets with 1 changes to 1 files
123 checked 1 changesets with 1 changes to 1 files
123
124
124 Repository root:
125 Repository root:
125
126
126 $ hg root
127 $ hg root
127 $TESTTMP/t
128 $TESTTMP/t
128 $ hg log -l1 -T '{reporoot}\n'
129 $ hg log -l1 -T '{reporoot}\n'
129 $TESTTMP/t
130 $TESTTMP/t
130 $ hg root -Tjson | sed 's|\\\\|\\|g'
131 $ hg root -Tjson | sed 's|\\\\|\\|g'
131 [
132 [
132 {
133 {
133 "hgpath": "$TESTTMP/t/.hg",
134 "hgpath": "$TESTTMP/t/.hg",
134 "reporoot": "$TESTTMP/t",
135 "reporoot": "$TESTTMP/t",
135 "storepath": "$TESTTMP/t/.hg/store"
136 "storepath": "$TESTTMP/t/.hg/store"
136 }
137 }
137 ]
138 ]
138
139
139 At the end...
140 At the end...
140
141
141 $ cd ..
142 $ cd ..
142
143
143 Status message redirection:
144 Status message redirection:
144
145
145 $ hg init empty
146 $ hg init empty
146
147
147 status messages are sent to stdout by default:
148 status messages are sent to stdout by default:
148
149
149 $ hg outgoing -R t empty -Tjson 2>/dev/null
150 $ hg outgoing -R t empty -Tjson 2>/dev/null
150 comparing with empty
151 comparing with empty
151 searching for changes
152 searching for changes
152 [
153 [
153 {
154 {
154 "bookmarks": [],
155 "bookmarks": [],
155 "branch": "default",
156 "branch": "default",
156 "date": [0, 0],
157 "date": [0, 0],
157 "desc": "test",
158 "desc": "test",
158 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
159 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
159 "parents": ["0000000000000000000000000000000000000000"],
160 "parents": ["0000000000000000000000000000000000000000"],
160 "phase": "draft",
161 "phase": "draft",
161 "rev": 0,
162 "rev": 0,
162 "tags": ["tip"],
163 "tags": ["tip"],
163 "user": "test"
164 "user": "test"
164 }
165 }
165 ]
166 ]
166
167
167 which can be configured to send to stderr, so the output wouldn't be
168 which can be configured to send to stderr, so the output wouldn't be
168 interleaved:
169 interleaved:
169
170
170 $ cat <<'EOF' >> "$HGRCPATH"
171 $ cat <<'EOF' >> "$HGRCPATH"
171 > [ui]
172 > [ui]
172 > message-output = stderr
173 > message-output = stderr
173 > EOF
174 > EOF
174 $ hg outgoing -R t empty -Tjson 2>/dev/null
175 $ hg outgoing -R t empty -Tjson 2>/dev/null
175 [
176 [
176 {
177 {
177 "bookmarks": [],
178 "bookmarks": [],
178 "branch": "default",
179 "branch": "default",
179 "date": [0, 0],
180 "date": [0, 0],
180 "desc": "test",
181 "desc": "test",
181 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
182 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
182 "parents": ["0000000000000000000000000000000000000000"],
183 "parents": ["0000000000000000000000000000000000000000"],
183 "phase": "draft",
184 "phase": "draft",
184 "rev": 0,
185 "rev": 0,
185 "tags": ["tip"],
186 "tags": ["tip"],
186 "user": "test"
187 "user": "test"
187 }
188 }
188 ]
189 ]
189 $ hg outgoing -R t empty -Tjson >/dev/null
190 $ hg outgoing -R t empty -Tjson >/dev/null
190 comparing with empty
191 comparing with empty
191 searching for changes
192 searching for changes
192
193
193 this option should be turned off by HGPLAIN= since it may break scripting use:
194 this option should be turned off by HGPLAIN= since it may break scripting use:
194
195
195 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
196 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
196 comparing with empty
197 comparing with empty
197 searching for changes
198 searching for changes
198 [
199 [
199 {
200 {
200 "bookmarks": [],
201 "bookmarks": [],
201 "branch": "default",
202 "branch": "default",
202 "date": [0, 0],
203 "date": [0, 0],
203 "desc": "test",
204 "desc": "test",
204 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
205 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
205 "parents": ["0000000000000000000000000000000000000000"],
206 "parents": ["0000000000000000000000000000000000000000"],
206 "phase": "draft",
207 "phase": "draft",
207 "rev": 0,
208 "rev": 0,
208 "tags": ["tip"],
209 "tags": ["tip"],
209 "user": "test"
210 "user": "test"
210 }
211 }
211 ]
212 ]
212
213
213 but still overridden by --config:
214 but still overridden by --config:
214
215
215 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
216 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
216 > 2>/dev/null
217 > 2>/dev/null
217 [
218 [
218 {
219 {
219 "bookmarks": [],
220 "bookmarks": [],
220 "branch": "default",
221 "branch": "default",
221 "date": [0, 0],
222 "date": [0, 0],
222 "desc": "test",
223 "desc": "test",
223 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
224 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
224 "parents": ["0000000000000000000000000000000000000000"],
225 "parents": ["0000000000000000000000000000000000000000"],
225 "phase": "draft",
226 "phase": "draft",
226 "rev": 0,
227 "rev": 0,
227 "tags": ["tip"],
228 "tags": ["tip"],
228 "user": "test"
229 "user": "test"
229 }
230 }
230 ]
231 ]
231
232
232 Invalid ui.message-output option:
233 Invalid ui.message-output option:
233
234
234 $ hg log -R t --config ui.message-output=bad
235 $ hg log -R t --config ui.message-output=bad
235 abort: invalid ui.message-output destination: bad
236 abort: invalid ui.message-output destination: bad
236 [255]
237 [255]
237
238
238 Underlying message streams should be updated when ui.fout/ferr are set:
239 Underlying message streams should be updated when ui.fout/ferr are set:
239
240
240 $ cat <<'EOF' > capui.py
241 $ cat <<'EOF' > capui.py
241 > from mercurial import pycompat, registrar
242 > from mercurial import pycompat, registrar
242 > cmdtable = {}
243 > cmdtable = {}
243 > command = registrar.command(cmdtable)
244 > command = registrar.command(cmdtable)
244 > @command(b'capui', norepo=True)
245 > @command(b'capui', norepo=True)
245 > def capui(ui):
246 > def capui(ui):
246 > out = ui.fout
247 > out = ui.fout
247 > ui.fout = pycompat.bytesio()
248 > ui.fout = pycompat.bytesio()
248 > ui.status(b'status\n')
249 > ui.status(b'status\n')
249 > ui.ferr = pycompat.bytesio()
250 > ui.ferr = pycompat.bytesio()
250 > ui.warn(b'warn\n')
251 > ui.warn(b'warn\n')
251 > out.write(b'stdout: %s' % ui.fout.getvalue())
252 > out.write(b'stdout: %s' % ui.fout.getvalue())
252 > out.write(b'stderr: %s' % ui.ferr.getvalue())
253 > out.write(b'stderr: %s' % ui.ferr.getvalue())
253 > EOF
254 > EOF
254 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
255 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
255 stdout: status
256 stdout: status
256 stderr: warn
257 stderr: warn
@@ -1,1172 +1,1174 b''
1 #require no-rhg no-chg
1 #require no-rhg no-chg
2
2
3 XXX-RHG this test hangs if `hg` is really `rhg`. This was hidden by the use of
3 XXX-RHG this test hangs if `hg` is really `rhg`. This was hidden by the use of
4 `alias hg=rhg` by run-tests.py. With such alias removed, this test is revealed
4 `alias hg=rhg` by run-tests.py. With such alias removed, this test is revealed
5 buggy. This need to be resolved sooner than later.
5 buggy. This need to be resolved sooner than later.
6
6
7 XXX-CHG this test hangs if `hg` is really `chg`. This was hidden by the use of
7 XXX-CHG this test hangs if `hg` is really `chg`. This was hidden by the use of
8 `alias hg=chg` by run-tests.py. With such alias removed, this test is revealed
8 `alias hg=chg` by run-tests.py. With such alias removed, this test is revealed
9 buggy. This need to be resolved sooner than later.
9 buggy. This need to be resolved sooner than later.
10
10
11 #if windows
11 #if windows
12 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
12 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
13 #else
13 #else
14 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
14 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
15 #endif
15 #endif
16 $ export PYTHONPATH
16 $ export PYTHONPATH
17
17
18 typical client does not want echo-back messages, so test without it:
18 typical client does not want echo-back messages, so test without it:
19
19
20 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
20 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
21 $ mv $HGRCPATH.new $HGRCPATH
21 $ mv $HGRCPATH.new $HGRCPATH
22
22
23 $ hg init repo
23 $ hg init repo
24 $ cd repo
24 $ cd repo
25
25
26 >>> from __future__ import absolute_import
26 >>> from __future__ import absolute_import
27 >>> import os
27 >>> import os
28 >>> import sys
28 >>> import sys
29 >>> from hgclient import bprint, check, readchannel, runcommand
29 >>> from hgclient import bprint, check, readchannel, runcommand
30 >>> @check
30 >>> @check
31 ... def hellomessage(server):
31 ... def hellomessage(server):
32 ... ch, data = readchannel(server)
32 ... ch, data = readchannel(server)
33 ... bprint(b'%c, %r' % (ch, data))
33 ... bprint(b'%c, %r' % (ch, data))
34 ... # run an arbitrary command to make sure the next thing the server
34 ... # run an arbitrary command to make sure the next thing the server
35 ... # sends isn't part of the hello message
35 ... # sends isn't part of the hello message
36 ... runcommand(server, [b'id'])
36 ... runcommand(server, [b'id'])
37 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
37 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
38 *** runcommand id
38 *** runcommand id
39 000000000000 tip
39 000000000000 tip
40
40
41 >>> from hgclient import check
41 >>> from hgclient import check
42 >>> @check
42 >>> @check
43 ... def unknowncommand(server):
43 ... def unknowncommand(server):
44 ... server.stdin.write(b'unknowncommand\n')
44 ... server.stdin.write(b'unknowncommand\n')
45 abort: unknown command unknowncommand
45 abort: unknown command unknowncommand
46
46
47 >>> from hgclient import check, readchannel, runcommand
47 >>> from hgclient import check, readchannel, runcommand
48 >>> @check
48 >>> @check
49 ... def checkruncommand(server):
49 ... def checkruncommand(server):
50 ... # hello block
50 ... # hello block
51 ... readchannel(server)
51 ... readchannel(server)
52 ...
52 ...
53 ... # no args
53 ... # no args
54 ... runcommand(server, [])
54 ... runcommand(server, [])
55 ...
55 ...
56 ... # global options
56 ... # global options
57 ... runcommand(server, [b'id', b'--quiet'])
57 ... runcommand(server, [b'id', b'--quiet'])
58 ...
58 ...
59 ... # make sure global options don't stick through requests
59 ... # make sure global options don't stick through requests
60 ... runcommand(server, [b'id'])
60 ... runcommand(server, [b'id'])
61 ...
61 ...
62 ... # --config
62 ... # --config
63 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
63 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
64 ...
64 ...
65 ... # make sure --config doesn't stick
65 ... # make sure --config doesn't stick
66 ... runcommand(server, [b'id'])
66 ... runcommand(server, [b'id'])
67 ...
67 ...
68 ... # negative return code should be masked
68 ... # negative return code should be masked
69 ... runcommand(server, [b'id', b'-runknown'])
69 ... runcommand(server, [b'id', b'-runknown'])
70 *** runcommand
70 *** runcommand
71 Mercurial Distributed SCM
71 Mercurial Distributed SCM
72
72
73 basic commands:
73 basic commands:
74
74
75 add add the specified files on the next commit
75 add add the specified files on the next commit
76 annotate show changeset information by line for each file
76 annotate show changeset information by line for each file
77 clone make a copy of an existing repository
77 clone make a copy of an existing repository
78 commit commit the specified files or all outstanding changes
78 commit commit the specified files or all outstanding changes
79 diff diff repository (or selected files)
79 diff diff repository (or selected files)
80 export dump the header and diffs for one or more changesets
80 export dump the header and diffs for one or more changesets
81 forget forget the specified files on the next commit
81 forget forget the specified files on the next commit
82 init create a new repository in the given directory
82 init create a new repository in the given directory
83 log show revision history of entire repository or files
83 log show revision history of entire repository or files
84 merge merge another revision into working directory
84 merge merge another revision into working directory
85 pull pull changes from the specified source
85 pull pull changes from the specified source
86 push push changes to the specified destination
86 push push changes to the specified destination
87 remove remove the specified files on the next commit
87 remove remove the specified files on the next commit
88 serve start stand-alone webserver
88 serve start stand-alone webserver
89 status show changed files in the working directory
89 status show changed files in the working directory
90 summary summarize working directory state
90 summary summarize working directory state
91 update update working directory (or switch revisions)
91 update update working directory (or switch revisions)
92
92
93 (use 'hg help' for the full list of commands or 'hg -v' for details)
93 (use 'hg help' for the full list of commands or 'hg -v' for details)
94 *** runcommand id --quiet
94 *** runcommand id --quiet
95 000000000000
95 000000000000
96 *** runcommand id
96 *** runcommand id
97 000000000000 tip
97 000000000000 tip
98 *** runcommand id --config ui.quiet=True
98 *** runcommand id --config ui.quiet=True
99 000000000000
99 000000000000
100 *** runcommand id
100 *** runcommand id
101 000000000000 tip
101 000000000000 tip
102 *** runcommand id -runknown
102 *** runcommand id -runknown
103 abort: unknown revision 'unknown'
103 abort: unknown revision 'unknown'
104 [255]
104 [255]
105
105
106 >>> from hgclient import bprint, check, readchannel
106 >>> from hgclient import bprint, check, readchannel
107 >>> @check
107 >>> @check
108 ... def inputeof(server):
108 ... def inputeof(server):
109 ... readchannel(server)
109 ... readchannel(server)
110 ... server.stdin.write(b'runcommand\n')
110 ... server.stdin.write(b'runcommand\n')
111 ... # close stdin while server is waiting for input
111 ... # close stdin while server is waiting for input
112 ... server.stdin.close()
112 ... server.stdin.close()
113 ...
113 ...
114 ... # server exits with 1 if the pipe closed while reading the command
114 ... # server exits with 1 if the pipe closed while reading the command
115 ... bprint(b'server exit code =', b'%d' % server.wait())
115 ... bprint(b'server exit code =', b'%d' % server.wait())
116 server exit code = 1
116 server exit code = 1
117
117
118 >>> from hgclient import check, readchannel, runcommand, stringio
118 >>> from hgclient import check, readchannel, runcommand, stringio
119 >>> @check
119 >>> @check
120 ... def serverinput(server):
120 ... def serverinput(server):
121 ... readchannel(server)
121 ... readchannel(server)
122 ...
122 ...
123 ... patch = b"""
123 ... patch = b"""
124 ... # HG changeset patch
124 ... # HG changeset patch
125 ... # User test
125 ... # User test
126 ... # Date 0 0
126 ... # Date 0 0
127 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
127 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
128 ... # Parent 0000000000000000000000000000000000000000
128 ... # Parent 0000000000000000000000000000000000000000
129 ... 1
129 ... 1
130 ...
130 ...
131 ... diff -r 000000000000 -r c103a3dec114 a
131 ... diff -r 000000000000 -r c103a3dec114 a
132 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
132 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
133 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
133 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
134 ... @@ -0,0 +1,1 @@
134 ... @@ -0,0 +1,1 @@
135 ... +1
135 ... +1
136 ... """
136 ... """
137 ...
137 ...
138 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
138 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
139 ... runcommand(server, [b'log'])
139 ... runcommand(server, [b'log'])
140 *** runcommand import -
140 *** runcommand import -
141 applying patch from stdin
141 applying patch from stdin
142 *** runcommand log
142 *** runcommand log
143 changeset: 0:eff892de26ec
143 changeset: 0:eff892de26ec
144 tag: tip
144 tag: tip
145 user: test
145 user: test
146 date: Thu Jan 01 00:00:00 1970 +0000
146 date: Thu Jan 01 00:00:00 1970 +0000
147 summary: 1
147 summary: 1
148
148
149
149
150 check strict parsing of early options:
150 check strict parsing of early options:
151
151
152 >>> import os
152 >>> import os
153 >>> from hgclient import check, readchannel, runcommand
153 >>> from hgclient import check, readchannel, runcommand
154 >>> os.environ['HGPLAIN'] = '+strictflags'
154 >>> os.environ['HGPLAIN'] = '+strictflags'
155 >>> @check
155 >>> @check
156 ... def cwd(server):
156 ... def cwd(server):
157 ... readchannel(server)
157 ... readchannel(server)
158 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
158 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
159 ... b'default'])
159 ... b'default'])
160 *** runcommand log -b --config=alias.log=!echo pwned default
160 *** runcommand log -b --config=alias.log=!echo pwned default
161 abort: unknown revision '--config=alias.log=!echo pwned'
161 abort: unknown revision '--config=alias.log=!echo pwned'
162 [255]
162 [255]
163
163
164 check that "histedit --commands=-" can read rules from the input channel:
164 check that "histedit --commands=-" can read rules from the input channel:
165
165
166 >>> from hgclient import check, readchannel, runcommand, stringio
166 >>> from hgclient import check, readchannel, runcommand, stringio
167 >>> @check
167 >>> @check
168 ... def serverinput(server):
168 ... def serverinput(server):
169 ... readchannel(server)
169 ... readchannel(server)
170 ... rules = b'pick eff892de26ec\n'
170 ... rules = b'pick eff892de26ec\n'
171 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
171 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
172 ... b'--config', b'extensions.histedit='],
172 ... b'--config', b'extensions.histedit='],
173 ... input=stringio(rules))
173 ... input=stringio(rules))
174 *** runcommand histedit 0 --commands=- --config extensions.histedit=
174 *** runcommand histedit 0 --commands=- --config extensions.histedit=
175
175
176 check that --cwd doesn't persist between requests:
176 check that --cwd doesn't persist between requests:
177
177
178 $ mkdir foo
178 $ mkdir foo
179 $ touch foo/bar
179 $ touch foo/bar
180 >>> from hgclient import check, readchannel, runcommand
180 >>> from hgclient import check, readchannel, runcommand
181 >>> @check
181 >>> @check
182 ... def cwd(server):
182 ... def cwd(server):
183 ... readchannel(server)
183 ... readchannel(server)
184 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
184 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
185 ... runcommand(server, [b'st', b'foo/bar'])
185 ... runcommand(server, [b'st', b'foo/bar'])
186 *** runcommand --cwd foo st bar
186 *** runcommand --cwd foo st bar
187 ? bar
187 ? bar
188 *** runcommand st foo/bar
188 *** runcommand st foo/bar
189 ? foo/bar
189 ? foo/bar
190
190
191 $ rm foo/bar
191 $ rm foo/bar
192
192
193
193
194 check that local configs for the cached repo aren't inherited when -R is used:
194 check that local configs for the cached repo aren't inherited when -R is used:
195
195
196 $ cat <<EOF >> .hg/hgrc
196 $ cat <<EOF >> .hg/hgrc
197 > [ui]
197 > [ui]
198 > foo = bar
198 > foo = bar
199 > EOF
199 > EOF
200
200
201 #if no-extraextensions
201 #if no-extraextensions
202
202
203 >>> from hgclient import check, readchannel, runcommand, sep
203 >>> from hgclient import check, readchannel, runcommand, sep
204 >>> @check
204 >>> @check
205 ... def localhgrc(server):
205 ... def localhgrc(server):
206 ... readchannel(server)
206 ... readchannel(server)
207 ...
207 ...
208 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
208 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
209 ... # show it
209 ... # show it
210 ... runcommand(server, [b'showconfig'], outfilter=sep)
210 ... runcommand(server, [b'showconfig'], outfilter=sep)
211 ...
211 ...
212 ... # but not for this repo
212 ... # but not for this repo
213 ... runcommand(server, [b'init', b'foo'])
213 ... runcommand(server, [b'init', b'foo'])
214 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
214 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
215 *** runcommand showconfig
215 *** runcommand showconfig
216 bundle.mainreporoot=$TESTTMP/repo
216 bundle.mainreporoot=$TESTTMP/repo
217 chgserver.idletimeout=60
217 chgserver.idletimeout=60
218 devel.all-warnings=true
218 devel.all-warnings=true
219 devel.default-date=0 0
219 devel.default-date=0 0
220 extensions.fsmonitor= (fsmonitor !)
220 extensions.fsmonitor= (fsmonitor !)
221 format.exp-dirstate-v2=1 (dirstate-v2 !)
221 format.exp-dirstate-v2=1 (dirstate-v2 !)
222 largefiles.usercache=$TESTTMP/.cache/largefiles
222 largefiles.usercache=$TESTTMP/.cache/largefiles
223 lfs.usercache=$TESTTMP/.cache/lfs
223 lfs.usercache=$TESTTMP/.cache/lfs
224 ui.slash=True
224 ui.slash=True
225 ui.interactive=False
225 ui.interactive=False
226 ui.detailed-exit-code=True
226 ui.detailed-exit-code=True
227 ui.merge=internal:merge
227 ui.merge=internal:merge
228 ui.mergemarkers=detailed
228 ui.mergemarkers=detailed
229 ui.ssh=* (glob)
229 ui.timeout.warn=15
230 ui.timeout.warn=15
230 ui.foo=bar
231 ui.foo=bar
231 ui.nontty=true
232 ui.nontty=true
232 web.address=localhost
233 web.address=localhost
233 web\.ipv6=(?:True|False) (re)
234 web\.ipv6=(?:True|False) (re)
234 web.server-header=testing stub value
235 web.server-header=testing stub value
235 *** runcommand init foo
236 *** runcommand init foo
236 *** runcommand -R foo showconfig ui defaults
237 *** runcommand -R foo showconfig ui defaults
237 ui.slash=True
238 ui.slash=True
238 ui.interactive=False
239 ui.interactive=False
239 ui.detailed-exit-code=True
240 ui.detailed-exit-code=True
240 ui.merge=internal:merge
241 ui.merge=internal:merge
241 ui.mergemarkers=detailed
242 ui.mergemarkers=detailed
243 ui.ssh=* (glob)
242 ui.timeout.warn=15
244 ui.timeout.warn=15
243 ui.nontty=true
245 ui.nontty=true
244 #endif
246 #endif
245
247
246 $ rm -R foo
248 $ rm -R foo
247
249
248 #if windows
250 #if windows
249 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
251 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
250 #else
252 #else
251 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
253 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
252 #endif
254 #endif
253
255
254 $ cat <<EOF > hook.py
256 $ cat <<EOF > hook.py
255 > import sys
257 > import sys
256 > from hgclient import bprint
258 > from hgclient import bprint
257 > def hook(**args):
259 > def hook(**args):
258 > bprint(b'hook talking')
260 > bprint(b'hook talking')
259 > bprint(b'now try to read something: %r' % sys.stdin.read())
261 > bprint(b'now try to read something: %r' % sys.stdin.read())
260 > EOF
262 > EOF
261
263
262 >>> from hgclient import check, readchannel, runcommand, stringio
264 >>> from hgclient import check, readchannel, runcommand, stringio
263 >>> @check
265 >>> @check
264 ... def hookoutput(server):
266 ... def hookoutput(server):
265 ... readchannel(server)
267 ... readchannel(server)
266 ... runcommand(server, [b'--config',
268 ... runcommand(server, [b'--config',
267 ... b'hooks.pre-identify=python:hook.hook',
269 ... b'hooks.pre-identify=python:hook.hook',
268 ... b'id'],
270 ... b'id'],
269 ... input=stringio(b'some input'))
271 ... input=stringio(b'some input'))
270 *** runcommand --config hooks.pre-identify=python:hook.hook id
272 *** runcommand --config hooks.pre-identify=python:hook.hook id
271 eff892de26ec tip
273 eff892de26ec tip
272 hook talking
274 hook talking
273 now try to read something: ''
275 now try to read something: ''
274
276
275 Clean hook cached version
277 Clean hook cached version
276 $ rm hook.py*
278 $ rm hook.py*
277 $ rm -Rf __pycache__
279 $ rm -Rf __pycache__
278
280
279 $ echo a >> a
281 $ echo a >> a
280 >>> import os
282 >>> import os
281 >>> from hgclient import check, readchannel, runcommand
283 >>> from hgclient import check, readchannel, runcommand
282 >>> @check
284 >>> @check
283 ... def outsidechanges(server):
285 ... def outsidechanges(server):
284 ... readchannel(server)
286 ... readchannel(server)
285 ... runcommand(server, [b'status'])
287 ... runcommand(server, [b'status'])
286 ... os.system('hg ci -Am2')
288 ... os.system('hg ci -Am2')
287 ... runcommand(server, [b'tip'])
289 ... runcommand(server, [b'tip'])
288 ... runcommand(server, [b'status'])
290 ... runcommand(server, [b'status'])
289 *** runcommand status
291 *** runcommand status
290 M a
292 M a
291 *** runcommand tip
293 *** runcommand tip
292 changeset: 1:d3a0a68be6de
294 changeset: 1:d3a0a68be6de
293 tag: tip
295 tag: tip
294 user: test
296 user: test
295 date: Thu Jan 01 00:00:00 1970 +0000
297 date: Thu Jan 01 00:00:00 1970 +0000
296 summary: 2
298 summary: 2
297
299
298 *** runcommand status
300 *** runcommand status
299
301
300 >>> import os
302 >>> import os
301 >>> from hgclient import bprint, check, readchannel, runcommand
303 >>> from hgclient import bprint, check, readchannel, runcommand
302 >>> @check
304 >>> @check
303 ... def bookmarks(server):
305 ... def bookmarks(server):
304 ... readchannel(server)
306 ... readchannel(server)
305 ... runcommand(server, [b'bookmarks'])
307 ... runcommand(server, [b'bookmarks'])
306 ...
308 ...
307 ... # changes .hg/bookmarks
309 ... # changes .hg/bookmarks
308 ... os.system('hg bookmark -i bm1')
310 ... os.system('hg bookmark -i bm1')
309 ... os.system('hg bookmark -i bm2')
311 ... os.system('hg bookmark -i bm2')
310 ... runcommand(server, [b'bookmarks'])
312 ... runcommand(server, [b'bookmarks'])
311 ...
313 ...
312 ... # changes .hg/bookmarks.current
314 ... # changes .hg/bookmarks.current
313 ... os.system('hg upd bm1 -q')
315 ... os.system('hg upd bm1 -q')
314 ... runcommand(server, [b'bookmarks'])
316 ... runcommand(server, [b'bookmarks'])
315 ...
317 ...
316 ... runcommand(server, [b'bookmarks', b'bm3'])
318 ... runcommand(server, [b'bookmarks', b'bm3'])
317 ... f = open('a', 'ab')
319 ... f = open('a', 'ab')
318 ... f.write(b'a\n') and None
320 ... f.write(b'a\n') and None
319 ... f.close()
321 ... f.close()
320 ... runcommand(server, [b'commit', b'-Amm'])
322 ... runcommand(server, [b'commit', b'-Amm'])
321 ... runcommand(server, [b'bookmarks'])
323 ... runcommand(server, [b'bookmarks'])
322 ... bprint(b'')
324 ... bprint(b'')
323 *** runcommand bookmarks
325 *** runcommand bookmarks
324 no bookmarks set
326 no bookmarks set
325 *** runcommand bookmarks
327 *** runcommand bookmarks
326 bm1 1:d3a0a68be6de
328 bm1 1:d3a0a68be6de
327 bm2 1:d3a0a68be6de
329 bm2 1:d3a0a68be6de
328 *** runcommand bookmarks
330 *** runcommand bookmarks
329 * bm1 1:d3a0a68be6de
331 * bm1 1:d3a0a68be6de
330 bm2 1:d3a0a68be6de
332 bm2 1:d3a0a68be6de
331 *** runcommand bookmarks bm3
333 *** runcommand bookmarks bm3
332 *** runcommand commit -Amm
334 *** runcommand commit -Amm
333 *** runcommand bookmarks
335 *** runcommand bookmarks
334 bm1 1:d3a0a68be6de
336 bm1 1:d3a0a68be6de
335 bm2 1:d3a0a68be6de
337 bm2 1:d3a0a68be6de
336 * bm3 2:aef17e88f5f0
338 * bm3 2:aef17e88f5f0
337
339
338
340
339 >>> import os
341 >>> import os
340 >>> from hgclient import check, readchannel, runcommand
342 >>> from hgclient import check, readchannel, runcommand
341 >>> @check
343 >>> @check
342 ... def tagscache(server):
344 ... def tagscache(server):
343 ... readchannel(server)
345 ... readchannel(server)
344 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
346 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
345 ... os.system('hg tag -r 0 foo')
347 ... os.system('hg tag -r 0 foo')
346 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
348 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
347 *** runcommand id -t -r 0
349 *** runcommand id -t -r 0
348
350
349 *** runcommand id -t -r 0
351 *** runcommand id -t -r 0
350 foo
352 foo
351
353
352 >>> import os
354 >>> import os
353 >>> from hgclient import check, readchannel, runcommand
355 >>> from hgclient import check, readchannel, runcommand
354 >>> @check
356 >>> @check
355 ... def setphase(server):
357 ... def setphase(server):
356 ... readchannel(server)
358 ... readchannel(server)
357 ... runcommand(server, [b'phase', b'-r', b'.'])
359 ... runcommand(server, [b'phase', b'-r', b'.'])
358 ... os.system('hg phase -r . -p')
360 ... os.system('hg phase -r . -p')
359 ... runcommand(server, [b'phase', b'-r', b'.'])
361 ... runcommand(server, [b'phase', b'-r', b'.'])
360 *** runcommand phase -r .
362 *** runcommand phase -r .
361 3: draft
363 3: draft
362 *** runcommand phase -r .
364 *** runcommand phase -r .
363 3: public
365 3: public
364
366
365 $ echo a >> a
367 $ echo a >> a
366 >>> from hgclient import bprint, check, readchannel, runcommand
368 >>> from hgclient import bprint, check, readchannel, runcommand
367 >>> @check
369 >>> @check
368 ... def rollback(server):
370 ... def rollback(server):
369 ... readchannel(server)
371 ... readchannel(server)
370 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
372 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
371 ... runcommand(server, [b'commit', b'-Am.'])
373 ... runcommand(server, [b'commit', b'-Am.'])
372 ... runcommand(server, [b'rollback'])
374 ... runcommand(server, [b'rollback'])
373 ... runcommand(server, [b'phase', b'-r', b'.'])
375 ... runcommand(server, [b'phase', b'-r', b'.'])
374 ... bprint(b'')
376 ... bprint(b'')
375 *** runcommand phase -r . -p
377 *** runcommand phase -r . -p
376 no phases changed
378 no phases changed
377 *** runcommand commit -Am.
379 *** runcommand commit -Am.
378 *** runcommand rollback
380 *** runcommand rollback
379 repository tip rolled back to revision 3 (undo commit)
381 repository tip rolled back to revision 3 (undo commit)
380 working directory now based on revision 3
382 working directory now based on revision 3
381 *** runcommand phase -r .
383 *** runcommand phase -r .
382 3: public
384 3: public
383
385
384
386
385 >>> import os
387 >>> import os
386 >>> from hgclient import check, readchannel, runcommand
388 >>> from hgclient import check, readchannel, runcommand
387 >>> @check
389 >>> @check
388 ... def branch(server):
390 ... def branch(server):
389 ... readchannel(server)
391 ... readchannel(server)
390 ... runcommand(server, [b'branch'])
392 ... runcommand(server, [b'branch'])
391 ... os.system('hg branch foo')
393 ... os.system('hg branch foo')
392 ... runcommand(server, [b'branch'])
394 ... runcommand(server, [b'branch'])
393 ... os.system('hg branch default')
395 ... os.system('hg branch default')
394 *** runcommand branch
396 *** runcommand branch
395 default
397 default
396 marked working directory as branch foo
398 marked working directory as branch foo
397 (branches are permanent and global, did you want a bookmark?)
399 (branches are permanent and global, did you want a bookmark?)
398 *** runcommand branch
400 *** runcommand branch
399 foo
401 foo
400 marked working directory as branch default
402 marked working directory as branch default
401 (branches are permanent and global, did you want a bookmark?)
403 (branches are permanent and global, did you want a bookmark?)
402
404
403 $ touch .hgignore
405 $ touch .hgignore
404 >>> import os
406 >>> import os
405 >>> from hgclient import bprint, check, readchannel, runcommand
407 >>> from hgclient import bprint, check, readchannel, runcommand
406 >>> @check
408 >>> @check
407 ... def hgignore(server):
409 ... def hgignore(server):
408 ... readchannel(server)
410 ... readchannel(server)
409 ... runcommand(server, [b'commit', b'-Am.'])
411 ... runcommand(server, [b'commit', b'-Am.'])
410 ... f = open('ignored-file', 'ab')
412 ... f = open('ignored-file', 'ab')
411 ... f.write(b'') and None
413 ... f.write(b'') and None
412 ... f.close()
414 ... f.close()
413 ... f = open('.hgignore', 'ab')
415 ... f = open('.hgignore', 'ab')
414 ... f.write(b'ignored-file')
416 ... f.write(b'ignored-file')
415 ... f.close()
417 ... f.close()
416 ... runcommand(server, [b'status', b'-i', b'-u'])
418 ... runcommand(server, [b'status', b'-i', b'-u'])
417 ... bprint(b'')
419 ... bprint(b'')
418 *** runcommand commit -Am.
420 *** runcommand commit -Am.
419 adding .hgignore
421 adding .hgignore
420 *** runcommand status -i -u
422 *** runcommand status -i -u
421 I ignored-file
423 I ignored-file
422
424
423
425
424 cache of non-public revisions should be invalidated on repository change
426 cache of non-public revisions should be invalidated on repository change
425 (issue4855):
427 (issue4855):
426
428
427 >>> import os
429 >>> import os
428 >>> from hgclient import bprint, check, readchannel, runcommand
430 >>> from hgclient import bprint, check, readchannel, runcommand
429 >>> @check
431 >>> @check
430 ... def phasesetscacheaftercommit(server):
432 ... def phasesetscacheaftercommit(server):
431 ... readchannel(server)
433 ... readchannel(server)
432 ... # load _phasecache._phaserevs and _phasesets
434 ... # load _phasecache._phaserevs and _phasesets
433 ... runcommand(server, [b'log', b'-qr', b'draft()'])
435 ... runcommand(server, [b'log', b'-qr', b'draft()'])
434 ... # create draft commits by another process
436 ... # create draft commits by another process
435 ... for i in range(5, 7):
437 ... for i in range(5, 7):
436 ... f = open('a', 'ab')
438 ... f = open('a', 'ab')
437 ... f.seek(0, os.SEEK_END)
439 ... f.seek(0, os.SEEK_END)
438 ... f.write(b'a\n') and None
440 ... f.write(b'a\n') and None
439 ... f.close()
441 ... f.close()
440 ... os.system('hg commit -Aqm%d' % i)
442 ... os.system('hg commit -Aqm%d' % i)
441 ... # new commits should be listed as draft revisions
443 ... # new commits should be listed as draft revisions
442 ... runcommand(server, [b'log', b'-qr', b'draft()'])
444 ... runcommand(server, [b'log', b'-qr', b'draft()'])
443 ... bprint(b'')
445 ... bprint(b'')
444 *** runcommand log -qr draft()
446 *** runcommand log -qr draft()
445 4:7966c8e3734d
447 4:7966c8e3734d
446 *** runcommand log -qr draft()
448 *** runcommand log -qr draft()
447 4:7966c8e3734d
449 4:7966c8e3734d
448 5:41f6602d1c4f
450 5:41f6602d1c4f
449 6:10501e202c35
451 6:10501e202c35
450
452
451
453
452 >>> import os
454 >>> import os
453 >>> from hgclient import bprint, check, readchannel, runcommand
455 >>> from hgclient import bprint, check, readchannel, runcommand
454 >>> @check
456 >>> @check
455 ... def phasesetscacheafterstrip(server):
457 ... def phasesetscacheafterstrip(server):
456 ... readchannel(server)
458 ... readchannel(server)
457 ... # load _phasecache._phaserevs and _phasesets
459 ... # load _phasecache._phaserevs and _phasesets
458 ... runcommand(server, [b'log', b'-qr', b'draft()'])
460 ... runcommand(server, [b'log', b'-qr', b'draft()'])
459 ... # strip cached revisions by another process
461 ... # strip cached revisions by another process
460 ... os.system('hg --config extensions.strip= strip -q 5')
462 ... os.system('hg --config extensions.strip= strip -q 5')
461 ... # shouldn't abort by "unknown revision '6'"
463 ... # shouldn't abort by "unknown revision '6'"
462 ... runcommand(server, [b'log', b'-qr', b'draft()'])
464 ... runcommand(server, [b'log', b'-qr', b'draft()'])
463 ... bprint(b'')
465 ... bprint(b'')
464 *** runcommand log -qr draft()
466 *** runcommand log -qr draft()
465 4:7966c8e3734d
467 4:7966c8e3734d
466 5:41f6602d1c4f
468 5:41f6602d1c4f
467 6:10501e202c35
469 6:10501e202c35
468 *** runcommand log -qr draft()
470 *** runcommand log -qr draft()
469 4:7966c8e3734d
471 4:7966c8e3734d
470
472
471
473
472 cache of phase roots should be invalidated on strip (issue3827):
474 cache of phase roots should be invalidated on strip (issue3827):
473
475
474 >>> import os
476 >>> import os
475 >>> from hgclient import check, readchannel, runcommand, sep
477 >>> from hgclient import check, readchannel, runcommand, sep
476 >>> @check
478 >>> @check
477 ... def phasecacheafterstrip(server):
479 ... def phasecacheafterstrip(server):
478 ... readchannel(server)
480 ... readchannel(server)
479 ...
481 ...
480 ... # create new head, 5:731265503d86
482 ... # create new head, 5:731265503d86
481 ... runcommand(server, [b'update', b'-C', b'0'])
483 ... runcommand(server, [b'update', b'-C', b'0'])
482 ... f = open('a', 'ab')
484 ... f = open('a', 'ab')
483 ... f.write(b'a\n') and None
485 ... f.write(b'a\n') and None
484 ... f.close()
486 ... f.close()
485 ... runcommand(server, [b'commit', b'-Am.', b'a'])
487 ... runcommand(server, [b'commit', b'-Am.', b'a'])
486 ... runcommand(server, [b'log', b'-Gq'])
488 ... runcommand(server, [b'log', b'-Gq'])
487 ...
489 ...
488 ... # make it public; draft marker moves to 4:7966c8e3734d
490 ... # make it public; draft marker moves to 4:7966c8e3734d
489 ... runcommand(server, [b'phase', b'-p', b'.'])
491 ... runcommand(server, [b'phase', b'-p', b'.'])
490 ... # load _phasecache.phaseroots
492 ... # load _phasecache.phaseroots
491 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
493 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
492 ...
494 ...
493 ... # strip 1::4 outside server
495 ... # strip 1::4 outside server
494 ... os.system('hg -q --config extensions.mq= strip 1')
496 ... os.system('hg -q --config extensions.mq= strip 1')
495 ...
497 ...
496 ... # shouldn't raise "7966c8e3734d: no node!"
498 ... # shouldn't raise "7966c8e3734d: no node!"
497 ... runcommand(server, [b'branches'])
499 ... runcommand(server, [b'branches'])
498 *** runcommand update -C 0
500 *** runcommand update -C 0
499 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
501 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
500 (leaving bookmark bm3)
502 (leaving bookmark bm3)
501 *** runcommand commit -Am. a
503 *** runcommand commit -Am. a
502 created new head
504 created new head
503 *** runcommand log -Gq
505 *** runcommand log -Gq
504 @ 5:731265503d86
506 @ 5:731265503d86
505 |
507 |
506 | o 4:7966c8e3734d
508 | o 4:7966c8e3734d
507 | |
509 | |
508 | o 3:b9b85890c400
510 | o 3:b9b85890c400
509 | |
511 | |
510 | o 2:aef17e88f5f0
512 | o 2:aef17e88f5f0
511 | |
513 | |
512 | o 1:d3a0a68be6de
514 | o 1:d3a0a68be6de
513 |/
515 |/
514 o 0:eff892de26ec
516 o 0:eff892de26ec
515
517
516 *** runcommand phase -p .
518 *** runcommand phase -p .
517 *** runcommand phase .
519 *** runcommand phase .
518 5: public
520 5: public
519 *** runcommand branches
521 *** runcommand branches
520 default 1:731265503d86
522 default 1:731265503d86
521
523
522 in-memory cache must be reloaded if transaction is aborted. otherwise
524 in-memory cache must be reloaded if transaction is aborted. otherwise
523 changelog and manifest would have invalid node:
525 changelog and manifest would have invalid node:
524
526
525 $ echo a >> a
527 $ echo a >> a
526 >>> from hgclient import check, readchannel, runcommand
528 >>> from hgclient import check, readchannel, runcommand
527 >>> @check
529 >>> @check
528 ... def txabort(server):
530 ... def txabort(server):
529 ... readchannel(server)
531 ... readchannel(server)
530 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
532 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
531 ... b'-mfoo'])
533 ... b'-mfoo'])
532 ... runcommand(server, [b'verify'])
534 ... runcommand(server, [b'verify'])
533 *** runcommand commit --config hooks.pretxncommit=false -mfoo
535 *** runcommand commit --config hooks.pretxncommit=false -mfoo
534 transaction abort!
536 transaction abort!
535 rollback completed
537 rollback completed
536 abort: pretxncommit hook exited with status 1
538 abort: pretxncommit hook exited with status 1
537 [40]
539 [40]
538 *** runcommand verify
540 *** runcommand verify
539 checking changesets
541 checking changesets
540 checking manifests
542 checking manifests
541 crosschecking files in changesets and manifests
543 crosschecking files in changesets and manifests
542 checking files
544 checking files
543 checked 2 changesets with 2 changes to 1 files
545 checked 2 changesets with 2 changes to 1 files
544 $ hg revert --no-backup -aq
546 $ hg revert --no-backup -aq
545
547
546 $ cat >> .hg/hgrc << EOF
548 $ cat >> .hg/hgrc << EOF
547 > [experimental]
549 > [experimental]
548 > evolution.createmarkers=True
550 > evolution.createmarkers=True
549 > EOF
551 > EOF
550
552
551 >>> import os
553 >>> import os
552 >>> from hgclient import check, readchannel, runcommand
554 >>> from hgclient import check, readchannel, runcommand
553 >>> @check
555 >>> @check
554 ... def obsolete(server):
556 ... def obsolete(server):
555 ... readchannel(server)
557 ... readchannel(server)
556 ...
558 ...
557 ... runcommand(server, [b'up', b'null'])
559 ... runcommand(server, [b'up', b'null'])
558 ... runcommand(server, [b'phase', b'-df', b'tip'])
560 ... runcommand(server, [b'phase', b'-df', b'tip'])
559 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
561 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
560 ... if os.name == 'nt':
562 ... if os.name == 'nt':
561 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
563 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
562 ... os.system(cmd)
564 ... os.system(cmd)
563 ... runcommand(server, [b'log', b'--hidden'])
565 ... runcommand(server, [b'log', b'--hidden'])
564 ... runcommand(server, [b'log'])
566 ... runcommand(server, [b'log'])
565 *** runcommand up null
567 *** runcommand up null
566 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
568 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
567 *** runcommand phase -df tip
569 *** runcommand phase -df tip
568 1 new obsolescence markers
570 1 new obsolescence markers
569 obsoleted 1 changesets
571 obsoleted 1 changesets
570 *** runcommand log --hidden
572 *** runcommand log --hidden
571 changeset: 1:731265503d86
573 changeset: 1:731265503d86
572 tag: tip
574 tag: tip
573 user: test
575 user: test
574 date: Thu Jan 01 00:00:00 1970 +0000
576 date: Thu Jan 01 00:00:00 1970 +0000
575 obsolete: pruned
577 obsolete: pruned
576 summary: .
578 summary: .
577
579
578 changeset: 0:eff892de26ec
580 changeset: 0:eff892de26ec
579 bookmark: bm1
581 bookmark: bm1
580 bookmark: bm2
582 bookmark: bm2
581 bookmark: bm3
583 bookmark: bm3
582 user: test
584 user: test
583 date: Thu Jan 01 00:00:00 1970 +0000
585 date: Thu Jan 01 00:00:00 1970 +0000
584 summary: 1
586 summary: 1
585
587
586 *** runcommand log
588 *** runcommand log
587 changeset: 0:eff892de26ec
589 changeset: 0:eff892de26ec
588 bookmark: bm1
590 bookmark: bm1
589 bookmark: bm2
591 bookmark: bm2
590 bookmark: bm3
592 bookmark: bm3
591 tag: tip
593 tag: tip
592 user: test
594 user: test
593 date: Thu Jan 01 00:00:00 1970 +0000
595 date: Thu Jan 01 00:00:00 1970 +0000
594 summary: 1
596 summary: 1
595
597
596
598
597 $ cat <<EOF >> .hg/hgrc
599 $ cat <<EOF >> .hg/hgrc
598 > [extensions]
600 > [extensions]
599 > mq =
601 > mq =
600 > EOF
602 > EOF
601
603
602 >>> import os
604 >>> import os
603 >>> from hgclient import check, readchannel, runcommand
605 >>> from hgclient import check, readchannel, runcommand
604 >>> @check
606 >>> @check
605 ... def mqoutsidechanges(server):
607 ... def mqoutsidechanges(server):
606 ... readchannel(server)
608 ... readchannel(server)
607 ...
609 ...
608 ... # load repo.mq
610 ... # load repo.mq
609 ... runcommand(server, [b'qapplied'])
611 ... runcommand(server, [b'qapplied'])
610 ... os.system('hg qnew 0.diff')
612 ... os.system('hg qnew 0.diff')
611 ... # repo.mq should be invalidated
613 ... # repo.mq should be invalidated
612 ... runcommand(server, [b'qapplied'])
614 ... runcommand(server, [b'qapplied'])
613 ...
615 ...
614 ... runcommand(server, [b'qpop', b'--all'])
616 ... runcommand(server, [b'qpop', b'--all'])
615 ... os.system('hg qqueue --create foo')
617 ... os.system('hg qqueue --create foo')
616 ... # repo.mq should be recreated to point to new queue
618 ... # repo.mq should be recreated to point to new queue
617 ... runcommand(server, [b'qqueue', b'--active'])
619 ... runcommand(server, [b'qqueue', b'--active'])
618 *** runcommand qapplied
620 *** runcommand qapplied
619 *** runcommand qapplied
621 *** runcommand qapplied
620 0.diff
622 0.diff
621 *** runcommand qpop --all
623 *** runcommand qpop --all
622 popping 0.diff
624 popping 0.diff
623 patch queue now empty
625 patch queue now empty
624 *** runcommand qqueue --active
626 *** runcommand qqueue --active
625 foo
627 foo
626
628
627 $ cat <<'EOF' > ../dbgui.py
629 $ cat <<'EOF' > ../dbgui.py
628 > import os
630 > import os
629 > import sys
631 > import sys
630 > from mercurial import commands, registrar
632 > from mercurial import commands, registrar
631 > cmdtable = {}
633 > cmdtable = {}
632 > command = registrar.command(cmdtable)
634 > command = registrar.command(cmdtable)
633 > @command(b"debuggetpass", norepo=True)
635 > @command(b"debuggetpass", norepo=True)
634 > def debuggetpass(ui):
636 > def debuggetpass(ui):
635 > ui.write(b"%s\n" % ui.getpass())
637 > ui.write(b"%s\n" % ui.getpass())
636 > @command(b"debugprompt", norepo=True)
638 > @command(b"debugprompt", norepo=True)
637 > def debugprompt(ui):
639 > def debugprompt(ui):
638 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
640 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
639 > @command(b"debugpromptchoice", norepo=True)
641 > @command(b"debugpromptchoice", norepo=True)
640 > def debugpromptchoice(ui):
642 > def debugpromptchoice(ui):
641 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
643 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
642 > ui.write(b"%d\n" % ui.promptchoice(msg))
644 > ui.write(b"%d\n" % ui.promptchoice(msg))
643 > @command(b"debugreadstdin", norepo=True)
645 > @command(b"debugreadstdin", norepo=True)
644 > def debugreadstdin(ui):
646 > def debugreadstdin(ui):
645 > ui.write(b"read: %r\n" % sys.stdin.read(1))
647 > ui.write(b"read: %r\n" % sys.stdin.read(1))
646 > @command(b"debugwritestdout", norepo=True)
648 > @command(b"debugwritestdout", norepo=True)
647 > def debugwritestdout(ui):
649 > def debugwritestdout(ui):
648 > os.write(1, b"low-level stdout fd and\n")
650 > os.write(1, b"low-level stdout fd and\n")
649 > sys.stdout.write("stdout should be redirected to stderr\n")
651 > sys.stdout.write("stdout should be redirected to stderr\n")
650 > sys.stdout.flush()
652 > sys.stdout.flush()
651 > EOF
653 > EOF
652 $ cat <<EOF >> .hg/hgrc
654 $ cat <<EOF >> .hg/hgrc
653 > [extensions]
655 > [extensions]
654 > dbgui = ../dbgui.py
656 > dbgui = ../dbgui.py
655 > EOF
657 > EOF
656
658
657 >>> from hgclient import check, readchannel, runcommand, stringio
659 >>> from hgclient import check, readchannel, runcommand, stringio
658 >>> @check
660 >>> @check
659 ... def getpass(server):
661 ... def getpass(server):
660 ... readchannel(server)
662 ... readchannel(server)
661 ... runcommand(server, [b'debuggetpass', b'--config',
663 ... runcommand(server, [b'debuggetpass', b'--config',
662 ... b'ui.interactive=True'],
664 ... b'ui.interactive=True'],
663 ... input=stringio(b'1234\n'))
665 ... input=stringio(b'1234\n'))
664 ... runcommand(server, [b'debuggetpass', b'--config',
666 ... runcommand(server, [b'debuggetpass', b'--config',
665 ... b'ui.interactive=True'],
667 ... b'ui.interactive=True'],
666 ... input=stringio(b'\n'))
668 ... input=stringio(b'\n'))
667 ... runcommand(server, [b'debuggetpass', b'--config',
669 ... runcommand(server, [b'debuggetpass', b'--config',
668 ... b'ui.interactive=True'],
670 ... b'ui.interactive=True'],
669 ... input=stringio(b''))
671 ... input=stringio(b''))
670 ... runcommand(server, [b'debugprompt', b'--config',
672 ... runcommand(server, [b'debugprompt', b'--config',
671 ... b'ui.interactive=True'],
673 ... b'ui.interactive=True'],
672 ... input=stringio(b'5678\n'))
674 ... input=stringio(b'5678\n'))
673 ... runcommand(server, [b'debugprompt', b'--config',
675 ... runcommand(server, [b'debugprompt', b'--config',
674 ... b'ui.interactive=True'],
676 ... b'ui.interactive=True'],
675 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
677 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
676 ... runcommand(server, [b'debugreadstdin'])
678 ... runcommand(server, [b'debugreadstdin'])
677 ... runcommand(server, [b'debugwritestdout'])
679 ... runcommand(server, [b'debugwritestdout'])
678 *** runcommand debuggetpass --config ui.interactive=True
680 *** runcommand debuggetpass --config ui.interactive=True
679 password: 1234
681 password: 1234
680 *** runcommand debuggetpass --config ui.interactive=True
682 *** runcommand debuggetpass --config ui.interactive=True
681 password:
683 password:
682 *** runcommand debuggetpass --config ui.interactive=True
684 *** runcommand debuggetpass --config ui.interactive=True
683 password: abort: response expected
685 password: abort: response expected
684 [255]
686 [255]
685 *** runcommand debugprompt --config ui.interactive=True
687 *** runcommand debugprompt --config ui.interactive=True
686 prompt: 5678
688 prompt: 5678
687 *** runcommand debugprompt --config ui.interactive=True
689 *** runcommand debugprompt --config ui.interactive=True
688 prompt: y
690 prompt: y
689 *** runcommand debugreadstdin
691 *** runcommand debugreadstdin
690 read: ''
692 read: ''
691 *** runcommand debugwritestdout
693 *** runcommand debugwritestdout
692 low-level stdout fd and
694 low-level stdout fd and
693 stdout should be redirected to stderr
695 stdout should be redirected to stderr
694
696
695
697
696 run commandserver in commandserver, which is silly but should work:
698 run commandserver in commandserver, which is silly but should work:
697
699
698 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
700 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
699 >>> @check
701 >>> @check
700 ... def nested(server):
702 ... def nested(server):
701 ... bprint(b'%c, %r' % readchannel(server))
703 ... bprint(b'%c, %r' % readchannel(server))
702 ... class nestedserver(object):
704 ... class nestedserver(object):
703 ... stdin = stringio(b'getencoding\n')
705 ... stdin = stringio(b'getencoding\n')
704 ... stdout = stringio()
706 ... stdout = stringio()
705 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
707 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
706 ... output=nestedserver.stdout, input=nestedserver.stdin)
708 ... output=nestedserver.stdout, input=nestedserver.stdin)
707 ... nestedserver.stdout.seek(0)
709 ... nestedserver.stdout.seek(0)
708 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
710 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
709 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
711 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
710 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
712 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
711 *** runcommand serve --cmdserver pipe
713 *** runcommand serve --cmdserver pipe
712 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
714 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
713 r, '*' (glob)
715 r, '*' (glob)
714
716
715
717
716 start without repository:
718 start without repository:
717
719
718 $ cd ..
720 $ cd ..
719
721
720 >>> from hgclient import bprint, check, readchannel, runcommand
722 >>> from hgclient import bprint, check, readchannel, runcommand
721 >>> @check
723 >>> @check
722 ... def hellomessage(server):
724 ... def hellomessage(server):
723 ... ch, data = readchannel(server)
725 ... ch, data = readchannel(server)
724 ... bprint(b'%c, %r' % (ch, data))
726 ... bprint(b'%c, %r' % (ch, data))
725 ... # run an arbitrary command to make sure the next thing the server
727 ... # run an arbitrary command to make sure the next thing the server
726 ... # sends isn't part of the hello message
728 ... # sends isn't part of the hello message
727 ... runcommand(server, [b'id'])
729 ... runcommand(server, [b'id'])
728 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
730 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
729 *** runcommand id
731 *** runcommand id
730 abort: there is no Mercurial repository here (.hg not found)
732 abort: there is no Mercurial repository here (.hg not found)
731 [10]
733 [10]
732
734
733 >>> from hgclient import check, readchannel, runcommand
735 >>> from hgclient import check, readchannel, runcommand
734 >>> @check
736 >>> @check
735 ... def startwithoutrepo(server):
737 ... def startwithoutrepo(server):
736 ... readchannel(server)
738 ... readchannel(server)
737 ... runcommand(server, [b'init', b'repo2'])
739 ... runcommand(server, [b'init', b'repo2'])
738 ... runcommand(server, [b'id', b'-R', b'repo2'])
740 ... runcommand(server, [b'id', b'-R', b'repo2'])
739 *** runcommand init repo2
741 *** runcommand init repo2
740 *** runcommand id -R repo2
742 *** runcommand id -R repo2
741 000000000000 tip
743 000000000000 tip
742
744
743
745
744 don't fall back to cwd if invalid -R path is specified (issue4805):
746 don't fall back to cwd if invalid -R path is specified (issue4805):
745
747
746 $ cd repo
748 $ cd repo
747 $ hg serve --cmdserver pipe -R ../nonexistent
749 $ hg serve --cmdserver pipe -R ../nonexistent
748 abort: repository ../nonexistent not found
750 abort: repository ../nonexistent not found
749 [255]
751 [255]
750 $ cd ..
752 $ cd ..
751
753
752
754
753 #if no-windows
755 #if no-windows
754
756
755 option to not shutdown on SIGINT:
757 option to not shutdown on SIGINT:
756
758
757 $ cat <<'EOF' > dbgint.py
759 $ cat <<'EOF' > dbgint.py
758 > import os
760 > import os
759 > import signal
761 > import signal
760 > import time
762 > import time
761 > from mercurial import commands, registrar
763 > from mercurial import commands, registrar
762 > cmdtable = {}
764 > cmdtable = {}
763 > command = registrar.command(cmdtable)
765 > command = registrar.command(cmdtable)
764 > @command(b"debugsleep", norepo=True)
766 > @command(b"debugsleep", norepo=True)
765 > def debugsleep(ui):
767 > def debugsleep(ui):
766 > time.sleep(1)
768 > time.sleep(1)
767 > @command(b"debugsuicide", norepo=True)
769 > @command(b"debugsuicide", norepo=True)
768 > def debugsuicide(ui):
770 > def debugsuicide(ui):
769 > os.kill(os.getpid(), signal.SIGINT)
771 > os.kill(os.getpid(), signal.SIGINT)
770 > time.sleep(1)
772 > time.sleep(1)
771 > EOF
773 > EOF
772
774
773 >>> import signal
775 >>> import signal
774 >>> import time
776 >>> import time
775 >>> from hgclient import checkwith, readchannel, runcommand
777 >>> from hgclient import checkwith, readchannel, runcommand
776 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
778 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
777 ... b'--config', b'extensions.dbgint=dbgint.py'])
779 ... b'--config', b'extensions.dbgint=dbgint.py'])
778 ... def nointr(server):
780 ... def nointr(server):
779 ... readchannel(server)
781 ... readchannel(server)
780 ... server.send_signal(signal.SIGINT) # server won't be terminated
782 ... server.send_signal(signal.SIGINT) # server won't be terminated
781 ... time.sleep(1)
783 ... time.sleep(1)
782 ... runcommand(server, [b'debugsleep'])
784 ... runcommand(server, [b'debugsleep'])
783 ... server.send_signal(signal.SIGINT) # server won't be terminated
785 ... server.send_signal(signal.SIGINT) # server won't be terminated
784 ... runcommand(server, [b'debugsleep'])
786 ... runcommand(server, [b'debugsleep'])
785 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
787 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
786 ... server.send_signal(signal.SIGTERM) # server will be terminated
788 ... server.send_signal(signal.SIGTERM) # server will be terminated
787 ... time.sleep(1)
789 ... time.sleep(1)
788 *** runcommand debugsleep
790 *** runcommand debugsleep
789 *** runcommand debugsleep
791 *** runcommand debugsleep
790 *** runcommand debugsuicide
792 *** runcommand debugsuicide
791 interrupted!
793 interrupted!
792 killed!
794 killed!
793 [255]
795 [255]
794
796
795 #endif
797 #endif
796
798
797
799
798 structured message channel:
800 structured message channel:
799
801
800 $ cat <<'EOF' >> repo2/.hg/hgrc
802 $ cat <<'EOF' >> repo2/.hg/hgrc
801 > [ui]
803 > [ui]
802 > # server --config should precede repository option
804 > # server --config should precede repository option
803 > message-output = stdio
805 > message-output = stdio
804 > EOF
806 > EOF
805
807
806 >>> from hgclient import bprint, checkwith, readchannel, runcommand
808 >>> from hgclient import bprint, checkwith, readchannel, runcommand
807 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
809 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
808 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
810 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
809 ... def verify(server):
811 ... def verify(server):
810 ... _ch, data = readchannel(server)
812 ... _ch, data = readchannel(server)
811 ... bprint(data)
813 ... bprint(data)
812 ... runcommand(server, [b'-R', b'repo2', b'verify'])
814 ... runcommand(server, [b'-R', b'repo2', b'verify'])
813 capabilities: getencoding runcommand
815 capabilities: getencoding runcommand
814 encoding: ascii
816 encoding: ascii
815 message-encoding: cbor
817 message-encoding: cbor
816 pid: * (glob)
818 pid: * (glob)
817 pgid: * (glob) (no-windows !)
819 pgid: * (glob) (no-windows !)
818 *** runcommand -R repo2 verify
820 *** runcommand -R repo2 verify
819 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
821 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
820 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
822 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
821 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
823 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
822 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
824 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
823 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
825 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
824 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
826 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
825 message: '\xa2DdataOchecking files\nDtypeFstatus'
827 message: '\xa2DdataOchecking files\nDtypeFstatus'
826 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
828 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
827 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
829 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
828
830
829 >>> from hgclient import checkwith, readchannel, runcommand, stringio
831 >>> from hgclient import checkwith, readchannel, runcommand, stringio
830 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
832 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
831 ... b'--config', b'cmdserver.message-encodings=cbor',
833 ... b'--config', b'cmdserver.message-encodings=cbor',
832 ... b'--config', b'extensions.dbgui=dbgui.py'])
834 ... b'--config', b'extensions.dbgui=dbgui.py'])
833 ... def prompt(server):
835 ... def prompt(server):
834 ... readchannel(server)
836 ... readchannel(server)
835 ... interactive = [b'--config', b'ui.interactive=True']
837 ... interactive = [b'--config', b'ui.interactive=True']
836 ... runcommand(server, [b'debuggetpass'] + interactive,
838 ... runcommand(server, [b'debuggetpass'] + interactive,
837 ... input=stringio(b'1234\n'))
839 ... input=stringio(b'1234\n'))
838 ... runcommand(server, [b'debugprompt'] + interactive,
840 ... runcommand(server, [b'debugprompt'] + interactive,
839 ... input=stringio(b'5678\n'))
841 ... input=stringio(b'5678\n'))
840 ... runcommand(server, [b'debugpromptchoice'] + interactive,
842 ... runcommand(server, [b'debugpromptchoice'] + interactive,
841 ... input=stringio(b'n\n'))
843 ... input=stringio(b'n\n'))
842 *** runcommand debuggetpass --config ui.interactive=True
844 *** runcommand debuggetpass --config ui.interactive=True
843 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
845 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
844 1234
846 1234
845 *** runcommand debugprompt --config ui.interactive=True
847 *** runcommand debugprompt --config ui.interactive=True
846 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
848 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
847 5678
849 5678
848 *** runcommand debugpromptchoice --config ui.interactive=True
850 *** runcommand debugpromptchoice --config ui.interactive=True
849 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
851 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
850 1
852 1
851
853
852 bad message encoding:
854 bad message encoding:
853
855
854 $ hg serve --cmdserver pipe --config ui.message-output=channel
856 $ hg serve --cmdserver pipe --config ui.message-output=channel
855 abort: no supported message encodings:
857 abort: no supported message encodings:
856 [255]
858 [255]
857 $ hg serve --cmdserver pipe --config ui.message-output=channel \
859 $ hg serve --cmdserver pipe --config ui.message-output=channel \
858 > --config cmdserver.message-encodings='foo bar'
860 > --config cmdserver.message-encodings='foo bar'
859 abort: no supported message encodings: foo bar
861 abort: no supported message encodings: foo bar
860 [255]
862 [255]
861
863
862 unix domain socket:
864 unix domain socket:
863
865
864 $ cd repo
866 $ cd repo
865 $ hg update -q
867 $ hg update -q
866
868
867 #if unix-socket unix-permissions
869 #if unix-socket unix-permissions
868
870
869 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
871 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
870 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
872 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
871 >>> def hellomessage(conn):
873 >>> def hellomessage(conn):
872 ... ch, data = readchannel(conn)
874 ... ch, data = readchannel(conn)
873 ... bprint(b'%c, %r' % (ch, data))
875 ... bprint(b'%c, %r' % (ch, data))
874 ... runcommand(conn, [b'id'])
876 ... runcommand(conn, [b'id'])
875 >>> check(hellomessage, server.connect)
877 >>> check(hellomessage, server.connect)
876 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
878 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
877 *** runcommand id
879 *** runcommand id
878 eff892de26ec tip bm1/bm2/bm3
880 eff892de26ec tip bm1/bm2/bm3
879 >>> def unknowncommand(conn):
881 >>> def unknowncommand(conn):
880 ... readchannel(conn)
882 ... readchannel(conn)
881 ... conn.stdin.write(b'unknowncommand\n')
883 ... conn.stdin.write(b'unknowncommand\n')
882 >>> check(unknowncommand, server.connect) # error sent to server.log
884 >>> check(unknowncommand, server.connect) # error sent to server.log
883 >>> def serverinput(conn):
885 >>> def serverinput(conn):
884 ... readchannel(conn)
886 ... readchannel(conn)
885 ... patch = b"""
887 ... patch = b"""
886 ... # HG changeset patch
888 ... # HG changeset patch
887 ... # User test
889 ... # User test
888 ... # Date 0 0
890 ... # Date 0 0
889 ... 2
891 ... 2
890 ...
892 ...
891 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
893 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
892 ... --- a/a
894 ... --- a/a
893 ... +++ b/a
895 ... +++ b/a
894 ... @@ -1,1 +1,2 @@
896 ... @@ -1,1 +1,2 @@
895 ... 1
897 ... 1
896 ... +2
898 ... +2
897 ... """
899 ... """
898 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
900 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
899 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
901 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
900 >>> check(serverinput, server.connect)
902 >>> check(serverinput, server.connect)
901 *** runcommand import -
903 *** runcommand import -
902 applying patch from stdin
904 applying patch from stdin
903 *** runcommand log -rtip -q
905 *** runcommand log -rtip -q
904 2:1ed24be7e7a0
906 2:1ed24be7e7a0
905 >>> server.shutdown()
907 >>> server.shutdown()
906
908
907 $ cat .hg/server.log
909 $ cat .hg/server.log
908 listening at .hg/server.sock
910 listening at .hg/server.sock
909 abort: unknown command unknowncommand
911 abort: unknown command unknowncommand
910 killed!
912 killed!
911 $ rm .hg/server.log
913 $ rm .hg/server.log
912
914
913 if server crashed before hello, traceback will be sent to 'e' channel as
915 if server crashed before hello, traceback will be sent to 'e' channel as
914 last ditch:
916 last ditch:
915
917
916 $ cat <<'EOF' > ../earlycrasher.py
918 $ cat <<'EOF' > ../earlycrasher.py
917 > from mercurial import commandserver, extensions
919 > from mercurial import commandserver, extensions
918 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
920 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
919 > def createcmdserver(*args, **kwargs):
921 > def createcmdserver(*args, **kwargs):
920 > raise Exception('crash')
922 > raise Exception('crash')
921 > return orig(ui, repo, conn, createcmdserver, prereposetups)
923 > return orig(ui, repo, conn, createcmdserver, prereposetups)
922 > def extsetup(ui):
924 > def extsetup(ui):
923 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
925 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
924 > EOF
926 > EOF
925 $ cat <<EOF >> .hg/hgrc
927 $ cat <<EOF >> .hg/hgrc
926 > [extensions]
928 > [extensions]
927 > earlycrasher = ../earlycrasher.py
929 > earlycrasher = ../earlycrasher.py
928 > EOF
930 > EOF
929 >>> from hgclient import bprint, check, readchannel, unixserver
931 >>> from hgclient import bprint, check, readchannel, unixserver
930 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
932 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
931 >>> def earlycrash(conn):
933 >>> def earlycrash(conn):
932 ... while True:
934 ... while True:
933 ... try:
935 ... try:
934 ... ch, data = readchannel(conn)
936 ... ch, data = readchannel(conn)
935 ... for l in data.splitlines(True):
937 ... for l in data.splitlines(True):
936 ... if not l.startswith(b' '):
938 ... if not l.startswith(b' '):
937 ... bprint(b'%c, %r' % (ch, l))
939 ... bprint(b'%c, %r' % (ch, l))
938 ... except EOFError:
940 ... except EOFError:
939 ... break
941 ... break
940 >>> check(earlycrash, server.connect)
942 >>> check(earlycrash, server.connect)
941 e, 'Traceback (most recent call last):\n'
943 e, 'Traceback (most recent call last):\n'
942 e, 'Exception: crash\n'
944 e, 'Exception: crash\n'
943 >>> server.shutdown()
945 >>> server.shutdown()
944
946
945 $ cat .hg/server.log | grep -v '^ '
947 $ cat .hg/server.log | grep -v '^ '
946 listening at .hg/server.sock
948 listening at .hg/server.sock
947 Traceback (most recent call last):
949 Traceback (most recent call last):
948 Exception: crash
950 Exception: crash
949 killed!
951 killed!
950 #endif
952 #endif
951 #if no-unix-socket
953 #if no-unix-socket
952
954
953 $ hg serve --cmdserver unix -a .hg/server.sock
955 $ hg serve --cmdserver unix -a .hg/server.sock
954 abort: unsupported platform
956 abort: unsupported platform
955 [255]
957 [255]
956
958
957 #endif
959 #endif
958
960
959 $ cd ..
961 $ cd ..
960
962
961 Test that accessing to invalid changelog cache is avoided at
963 Test that accessing to invalid changelog cache is avoided at
962 subsequent operations even if repo object is reused even after failure
964 subsequent operations even if repo object is reused even after failure
963 of transaction (see 0a7610758c42 also)
965 of transaction (see 0a7610758c42 also)
964
966
965 "hg log" after failure of transaction is needed to detect invalid
967 "hg log" after failure of transaction is needed to detect invalid
966 cache in repoview: this can't detect by "hg verify" only.
968 cache in repoview: this can't detect by "hg verify" only.
967
969
968 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
970 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
969 4) are tested, because '00changelog.i' are differently changed in each
971 4) are tested, because '00changelog.i' are differently changed in each
970 cases.
972 cases.
971
973
972 $ cat > $TESTTMP/failafterfinalize.py <<EOF
974 $ cat > $TESTTMP/failafterfinalize.py <<EOF
973 > # extension to abort transaction after finalization forcibly
975 > # extension to abort transaction after finalization forcibly
974 > from mercurial import commands, error, extensions, lock as lockmod
976 > from mercurial import commands, error, extensions, lock as lockmod
975 > from mercurial import registrar
977 > from mercurial import registrar
976 > cmdtable = {}
978 > cmdtable = {}
977 > command = registrar.command(cmdtable)
979 > command = registrar.command(cmdtable)
978 > configtable = {}
980 > configtable = {}
979 > configitem = registrar.configitem(configtable)
981 > configitem = registrar.configitem(configtable)
980 > configitem(b'failafterfinalize', b'fail',
982 > configitem(b'failafterfinalize', b'fail',
981 > default=None,
983 > default=None,
982 > )
984 > )
983 > def fail(tr):
985 > def fail(tr):
984 > raise error.Abort(b'fail after finalization')
986 > raise error.Abort(b'fail after finalization')
985 > def reposetup(ui, repo):
987 > def reposetup(ui, repo):
986 > class failrepo(repo.__class__):
988 > class failrepo(repo.__class__):
987 > def commitctx(self, ctx, error=False, origctx=None):
989 > def commitctx(self, ctx, error=False, origctx=None):
988 > if self.ui.configbool(b'failafterfinalize', b'fail'):
990 > if self.ui.configbool(b'failafterfinalize', b'fail'):
989 > # 'sorted()' by ASCII code on category names causes
991 > # 'sorted()' by ASCII code on category names causes
990 > # invoking 'fail' after finalization of changelog
992 > # invoking 'fail' after finalization of changelog
991 > # using "'cl-%i' % id(self)" as category name
993 > # using "'cl-%i' % id(self)" as category name
992 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
994 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
993 > return super(failrepo, self).commitctx(ctx, error, origctx)
995 > return super(failrepo, self).commitctx(ctx, error, origctx)
994 > repo.__class__ = failrepo
996 > repo.__class__ = failrepo
995 > EOF
997 > EOF
996
998
997 $ hg init repo3
999 $ hg init repo3
998 $ cd repo3
1000 $ cd repo3
999
1001
1000 $ cat <<EOF >> $HGRCPATH
1002 $ cat <<EOF >> $HGRCPATH
1001 > [command-templates]
1003 > [command-templates]
1002 > log = {rev} {desc|firstline} ({files})\n
1004 > log = {rev} {desc|firstline} ({files})\n
1003 >
1005 >
1004 > [extensions]
1006 > [extensions]
1005 > failafterfinalize = $TESTTMP/failafterfinalize.py
1007 > failafterfinalize = $TESTTMP/failafterfinalize.py
1006 > EOF
1008 > EOF
1007
1009
1008 - test failure with "empty changelog"
1010 - test failure with "empty changelog"
1009
1011
1010 $ echo foo > foo
1012 $ echo foo > foo
1011 $ hg add foo
1013 $ hg add foo
1012
1014
1013 (failure before finalization)
1015 (failure before finalization)
1014
1016
1015 >>> from hgclient import check, readchannel, runcommand
1017 >>> from hgclient import check, readchannel, runcommand
1016 >>> @check
1018 >>> @check
1017 ... def abort(server):
1019 ... def abort(server):
1018 ... readchannel(server)
1020 ... readchannel(server)
1019 ... runcommand(server, [b'commit',
1021 ... runcommand(server, [b'commit',
1020 ... b'--config', b'hooks.pretxncommit=false',
1022 ... b'--config', b'hooks.pretxncommit=false',
1021 ... b'-mfoo'])
1023 ... b'-mfoo'])
1022 ... runcommand(server, [b'log'])
1024 ... runcommand(server, [b'log'])
1023 ... runcommand(server, [b'verify', b'-q'])
1025 ... runcommand(server, [b'verify', b'-q'])
1024 *** runcommand commit --config hooks.pretxncommit=false -mfoo
1026 *** runcommand commit --config hooks.pretxncommit=false -mfoo
1025 transaction abort!
1027 transaction abort!
1026 rollback completed
1028 rollback completed
1027 abort: pretxncommit hook exited with status 1
1029 abort: pretxncommit hook exited with status 1
1028 [40]
1030 [40]
1029 *** runcommand log
1031 *** runcommand log
1030 *** runcommand verify -q
1032 *** runcommand verify -q
1031
1033
1032 (failure after finalization)
1034 (failure after finalization)
1033
1035
1034 >>> from hgclient import check, readchannel, runcommand
1036 >>> from hgclient import check, readchannel, runcommand
1035 >>> @check
1037 >>> @check
1036 ... def abort(server):
1038 ... def abort(server):
1037 ... readchannel(server)
1039 ... readchannel(server)
1038 ... runcommand(server, [b'commit',
1040 ... runcommand(server, [b'commit',
1039 ... b'--config', b'failafterfinalize.fail=true',
1041 ... b'--config', b'failafterfinalize.fail=true',
1040 ... b'-mfoo'])
1042 ... b'-mfoo'])
1041 ... runcommand(server, [b'log'])
1043 ... runcommand(server, [b'log'])
1042 ... runcommand(server, [b'verify', b'-q'])
1044 ... runcommand(server, [b'verify', b'-q'])
1043 *** runcommand commit --config failafterfinalize.fail=true -mfoo
1045 *** runcommand commit --config failafterfinalize.fail=true -mfoo
1044 transaction abort!
1046 transaction abort!
1045 rollback completed
1047 rollback completed
1046 abort: fail after finalization
1048 abort: fail after finalization
1047 [255]
1049 [255]
1048 *** runcommand log
1050 *** runcommand log
1049 *** runcommand verify -q
1051 *** runcommand verify -q
1050
1052
1051 - test failure with "not-empty changelog"
1053 - test failure with "not-empty changelog"
1052
1054
1053 $ echo bar > bar
1055 $ echo bar > bar
1054 $ hg add bar
1056 $ hg add bar
1055 $ hg commit -mbar bar
1057 $ hg commit -mbar bar
1056
1058
1057 (failure before finalization)
1059 (failure before finalization)
1058
1060
1059 >>> from hgclient import check, readchannel, runcommand
1061 >>> from hgclient import check, readchannel, runcommand
1060 >>> @check
1062 >>> @check
1061 ... def abort(server):
1063 ... def abort(server):
1062 ... readchannel(server)
1064 ... readchannel(server)
1063 ... runcommand(server, [b'commit',
1065 ... runcommand(server, [b'commit',
1064 ... b'--config', b'hooks.pretxncommit=false',
1066 ... b'--config', b'hooks.pretxncommit=false',
1065 ... b'-mfoo', b'foo'])
1067 ... b'-mfoo', b'foo'])
1066 ... runcommand(server, [b'log'])
1068 ... runcommand(server, [b'log'])
1067 ... runcommand(server, [b'verify', b'-q'])
1069 ... runcommand(server, [b'verify', b'-q'])
1068 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1070 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1069 transaction abort!
1071 transaction abort!
1070 rollback completed
1072 rollback completed
1071 abort: pretxncommit hook exited with status 1
1073 abort: pretxncommit hook exited with status 1
1072 [40]
1074 [40]
1073 *** runcommand log
1075 *** runcommand log
1074 0 bar (bar)
1076 0 bar (bar)
1075 *** runcommand verify -q
1077 *** runcommand verify -q
1076
1078
1077 (failure after finalization)
1079 (failure after finalization)
1078
1080
1079 >>> from hgclient import check, readchannel, runcommand
1081 >>> from hgclient import check, readchannel, runcommand
1080 >>> @check
1082 >>> @check
1081 ... def abort(server):
1083 ... def abort(server):
1082 ... readchannel(server)
1084 ... readchannel(server)
1083 ... runcommand(server, [b'commit',
1085 ... runcommand(server, [b'commit',
1084 ... b'--config', b'failafterfinalize.fail=true',
1086 ... b'--config', b'failafterfinalize.fail=true',
1085 ... b'-mfoo', b'foo'])
1087 ... b'-mfoo', b'foo'])
1086 ... runcommand(server, [b'log'])
1088 ... runcommand(server, [b'log'])
1087 ... runcommand(server, [b'verify', b'-q'])
1089 ... runcommand(server, [b'verify', b'-q'])
1088 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1090 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1089 transaction abort!
1091 transaction abort!
1090 rollback completed
1092 rollback completed
1091 abort: fail after finalization
1093 abort: fail after finalization
1092 [255]
1094 [255]
1093 *** runcommand log
1095 *** runcommand log
1094 0 bar (bar)
1096 0 bar (bar)
1095 *** runcommand verify -q
1097 *** runcommand verify -q
1096
1098
1097 $ cd ..
1099 $ cd ..
1098
1100
1099 Test symlink traversal over cached audited paths:
1101 Test symlink traversal over cached audited paths:
1100 -------------------------------------------------
1102 -------------------------------------------------
1101
1103
1102 #if symlink
1104 #if symlink
1103
1105
1104 set up symlink hell
1106 set up symlink hell
1105
1107
1106 $ mkdir merge-symlink-out
1108 $ mkdir merge-symlink-out
1107 $ hg init merge-symlink
1109 $ hg init merge-symlink
1108 $ cd merge-symlink
1110 $ cd merge-symlink
1109 $ touch base
1111 $ touch base
1110 $ hg commit -qAm base
1112 $ hg commit -qAm base
1111 $ ln -s ../merge-symlink-out a
1113 $ ln -s ../merge-symlink-out a
1112 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1114 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1113 $ hg up -q 0
1115 $ hg up -q 0
1114 $ mkdir a
1116 $ mkdir a
1115 $ touch a/poisoned
1117 $ touch a/poisoned
1116 $ hg commit -qAm 'file a/poisoned'
1118 $ hg commit -qAm 'file a/poisoned'
1117 $ hg log -G -T '{rev}: {desc}\n'
1119 $ hg log -G -T '{rev}: {desc}\n'
1118 @ 2: file a/poisoned
1120 @ 2: file a/poisoned
1119 |
1121 |
1120 | o 1: symlink a -> ../merge-symlink-out
1122 | o 1: symlink a -> ../merge-symlink-out
1121 |/
1123 |/
1122 o 0: base
1124 o 0: base
1123
1125
1124
1126
1125 try trivial merge after update: cache of audited paths should be discarded,
1127 try trivial merge after update: cache of audited paths should be discarded,
1126 and the merge should fail (issue5628)
1128 and the merge should fail (issue5628)
1127
1129
1128 $ hg up -q null
1130 $ hg up -q null
1129 >>> from hgclient import check, readchannel, runcommand
1131 >>> from hgclient import check, readchannel, runcommand
1130 >>> @check
1132 >>> @check
1131 ... def merge(server):
1133 ... def merge(server):
1132 ... readchannel(server)
1134 ... readchannel(server)
1133 ... # audit a/poisoned as a good path
1135 ... # audit a/poisoned as a good path
1134 ... runcommand(server, [b'up', b'-qC', b'2'])
1136 ... runcommand(server, [b'up', b'-qC', b'2'])
1135 ... runcommand(server, [b'up', b'-qC', b'1'])
1137 ... runcommand(server, [b'up', b'-qC', b'1'])
1136 ... # here a is a symlink, so a/poisoned is bad
1138 ... # here a is a symlink, so a/poisoned is bad
1137 ... runcommand(server, [b'merge', b'2'])
1139 ... runcommand(server, [b'merge', b'2'])
1138 *** runcommand up -qC 2
1140 *** runcommand up -qC 2
1139 *** runcommand up -qC 1
1141 *** runcommand up -qC 1
1140 *** runcommand merge 2
1142 *** runcommand merge 2
1141 abort: path 'a/poisoned' traverses symbolic link 'a'
1143 abort: path 'a/poisoned' traverses symbolic link 'a'
1142 [255]
1144 [255]
1143 $ ls ../merge-symlink-out
1145 $ ls ../merge-symlink-out
1144
1146
1145 cache of repo.auditor should be discarded, so matcher would never traverse
1147 cache of repo.auditor should be discarded, so matcher would never traverse
1146 symlinks:
1148 symlinks:
1147
1149
1148 $ hg up -qC 0
1150 $ hg up -qC 0
1149 $ touch ../merge-symlink-out/poisoned
1151 $ touch ../merge-symlink-out/poisoned
1150 >>> from hgclient import check, readchannel, runcommand
1152 >>> from hgclient import check, readchannel, runcommand
1151 >>> @check
1153 >>> @check
1152 ... def files(server):
1154 ... def files(server):
1153 ... readchannel(server)
1155 ... readchannel(server)
1154 ... runcommand(server, [b'up', b'-qC', b'2'])
1156 ... runcommand(server, [b'up', b'-qC', b'2'])
1155 ... # audit a/poisoned as a good path
1157 ... # audit a/poisoned as a good path
1156 ... runcommand(server, [b'files', b'a/poisoned'])
1158 ... runcommand(server, [b'files', b'a/poisoned'])
1157 ... runcommand(server, [b'up', b'-qC', b'0'])
1159 ... runcommand(server, [b'up', b'-qC', b'0'])
1158 ... runcommand(server, [b'up', b'-qC', b'1'])
1160 ... runcommand(server, [b'up', b'-qC', b'1'])
1159 ... # here 'a' is a symlink, so a/poisoned should be warned
1161 ... # here 'a' is a symlink, so a/poisoned should be warned
1160 ... runcommand(server, [b'files', b'a/poisoned'])
1162 ... runcommand(server, [b'files', b'a/poisoned'])
1161 *** runcommand up -qC 2
1163 *** runcommand up -qC 2
1162 *** runcommand files a/poisoned
1164 *** runcommand files a/poisoned
1163 a/poisoned
1165 a/poisoned
1164 *** runcommand up -qC 0
1166 *** runcommand up -qC 0
1165 *** runcommand up -qC 1
1167 *** runcommand up -qC 1
1166 *** runcommand files a/poisoned
1168 *** runcommand files a/poisoned
1167 abort: path 'a/poisoned' traverses symbolic link 'a'
1169 abort: path 'a/poisoned' traverses symbolic link 'a'
1168 [255]
1170 [255]
1169
1171
1170 $ cd ..
1172 $ cd ..
1171
1173
1172 #endif
1174 #endif
@@ -1,546 +1,546 b''
1 Windows needs ';' as a file separator in an environment variable, and MSYS
1 Windows needs ';' as a file separator in an environment variable, and MSYS
2 doesn't automatically convert it in every case.
2 doesn't automatically convert it in every case.
3
3
4 #if windows
4 #if windows
5 $ path_list_var() {
5 $ path_list_var() {
6 > echo $1 | sed 's/:/;/'
6 > echo $1 | sed 's/:/;/'
7 > }
7 > }
8 #else
8 #else
9 $ path_list_var() {
9 $ path_list_var() {
10 > echo $1
10 > echo $1
11 > }
11 > }
12 #endif
12 #endif
13
13
14
14
15 hide outer repo
15 hide outer repo
16 $ hg init
16 $ hg init
17
17
18 Invalid syntax: no value
18 Invalid syntax: no value
19
19
20 $ cat > .hg/hgrc << EOF
20 $ cat > .hg/hgrc << EOF
21 > novaluekey
21 > novaluekey
22 > EOF
22 > EOF
23 $ hg showconfig
23 $ hg showconfig
24 config error at $TESTTMP/.hg/hgrc:1: novaluekey
24 config error at $TESTTMP/.hg/hgrc:1: novaluekey
25 [30]
25 [30]
26
26
27 Invalid syntax: no key
27 Invalid syntax: no key
28
28
29 $ cat > .hg/hgrc << EOF
29 $ cat > .hg/hgrc << EOF
30 > =nokeyvalue
30 > =nokeyvalue
31 > EOF
31 > EOF
32 $ hg showconfig
32 $ hg showconfig
33 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
33 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
34 [30]
34 [30]
35
35
36 Test hint about invalid syntax from leading white space
36 Test hint about invalid syntax from leading white space
37
37
38 $ cat > .hg/hgrc << EOF
38 $ cat > .hg/hgrc << EOF
39 > key=value
39 > key=value
40 > EOF
40 > EOF
41 $ hg showconfig
41 $ hg showconfig
42 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
42 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
43 [30]
43 [30]
44
44
45 $ cat > .hg/hgrc << EOF
45 $ cat > .hg/hgrc << EOF
46 > [section]
46 > [section]
47 > key=value
47 > key=value
48 > EOF
48 > EOF
49 $ hg showconfig
49 $ hg showconfig
50 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
50 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
51 [30]
51 [30]
52
52
53 Reset hgrc
53 Reset hgrc
54
54
55 $ echo > .hg/hgrc
55 $ echo > .hg/hgrc
56
56
57 Test case sensitive configuration
57 Test case sensitive configuration
58
58
59 $ cat <<EOF >> $HGRCPATH
59 $ cat <<EOF >> $HGRCPATH
60 > [Section]
60 > [Section]
61 > KeY = Case Sensitive
61 > KeY = Case Sensitive
62 > key = lower case
62 > key = lower case
63 > EOF
63 > EOF
64
64
65 $ hg showconfig Section
65 $ hg showconfig Section
66 Section.KeY=Case Sensitive
66 Section.KeY=Case Sensitive
67 Section.key=lower case
67 Section.key=lower case
68
68
69 $ hg showconfig Section -Tjson
69 $ hg showconfig Section -Tjson
70 [
70 [
71 {
71 {
72 "defaultvalue": null,
72 "defaultvalue": null,
73 "name": "Section.KeY",
73 "name": "Section.KeY",
74 "source": "*.hgrc:*", (glob)
74 "source": "*.hgrc:*", (glob)
75 "value": "Case Sensitive"
75 "value": "Case Sensitive"
76 },
76 },
77 {
77 {
78 "defaultvalue": null,
78 "defaultvalue": null,
79 "name": "Section.key",
79 "name": "Section.key",
80 "source": "*.hgrc:*", (glob)
80 "source": "*.hgrc:*", (glob)
81 "value": "lower case"
81 "value": "lower case"
82 }
82 }
83 ]
83 ]
84 $ hg showconfig Section.KeY -Tjson
84 $ hg showconfig Section.KeY -Tjson
85 [
85 [
86 {
86 {
87 "defaultvalue": null,
87 "defaultvalue": null,
88 "name": "Section.KeY",
88 "name": "Section.KeY",
89 "source": "*.hgrc:*", (glob)
89 "source": "*.hgrc:*", (glob)
90 "value": "Case Sensitive"
90 "value": "Case Sensitive"
91 }
91 }
92 ]
92 ]
93 $ hg showconfig -Tjson | tail -7
93 $ hg showconfig -Tjson | tail -7
94 {
94 {
95 "defaultvalue": null,
95 "defaultvalue": null,
96 "name": "*", (glob)
96 "name": "*", (glob)
97 "source": "*", (glob)
97 "source": "*", (glob)
98 "value": "*" (glob)
98 "value": "*" (glob)
99 }
99 }
100 ]
100 ]
101
101
102 Test config default of various types:
102 Test config default of various types:
103
103
104 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
104 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
105 how the templater works. Unknown keywords are evaluated to "".
105 how the templater works. Unknown keywords are evaluated to "".
106
106
107 dynamicdefault
107 dynamicdefault
108
108
109 $ hg config --config alias.foo= alias -Tjson
109 $ hg config --config alias.foo= alias -Tjson
110 [
110 [
111 {
111 {
112 "name": "alias.foo",
112 "name": "alias.foo",
113 "source": "--config",
113 "source": "--config",
114 "value": ""
114 "value": ""
115 }
115 }
116 ]
116 ]
117 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
117 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
118 [
118 [
119 {"defaultvalue": ""}
119 {"defaultvalue": ""}
120 ]
120 ]
121 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
121 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
122
122
123
123
124 null
124 null
125
125
126 $ hg config --config auth.cookiefile= auth -Tjson
126 $ hg config --config auth.cookiefile= auth -Tjson
127 [
127 [
128 {
128 {
129 "defaultvalue": null,
129 "defaultvalue": null,
130 "name": "auth.cookiefile",
130 "name": "auth.cookiefile",
131 "source": "--config",
131 "source": "--config",
132 "value": ""
132 "value": ""
133 }
133 }
134 ]
134 ]
135 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
135 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
136 [
136 [
137 {"defaultvalue": null}
137 {"defaultvalue": null}
138 ]
138 ]
139 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
139 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
140
140
141
141
142 false
142 false
143
143
144 $ hg config --config commands.commit.post-status= commands -Tjson
144 $ hg config --config commands.commit.post-status= commands -Tjson
145 [
145 [
146 {
146 {
147 "defaultvalue": false,
147 "defaultvalue": false,
148 "name": "commands.commit.post-status",
148 "name": "commands.commit.post-status",
149 "source": "--config",
149 "source": "--config",
150 "value": ""
150 "value": ""
151 }
151 }
152 ]
152 ]
153 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
153 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
154 [
154 [
155 {"defaultvalue": false}
155 {"defaultvalue": false}
156 ]
156 ]
157 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
157 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
158 False
158 False
159
159
160 true
160 true
161
161
162 $ hg config --config format.dotencode= format.dotencode -Tjson
162 $ hg config --config format.dotencode= format.dotencode -Tjson
163 [
163 [
164 {
164 {
165 "defaultvalue": true,
165 "defaultvalue": true,
166 "name": "format.dotencode",
166 "name": "format.dotencode",
167 "source": "--config",
167 "source": "--config",
168 "value": ""
168 "value": ""
169 }
169 }
170 ]
170 ]
171 $ hg config --config format.dotencode= format.dotencode -T'json(defaultvalue)'
171 $ hg config --config format.dotencode= format.dotencode -T'json(defaultvalue)'
172 [
172 [
173 {"defaultvalue": true}
173 {"defaultvalue": true}
174 ]
174 ]
175 $ hg config --config format.dotencode= format.dotencode -T'{defaultvalue}\n'
175 $ hg config --config format.dotencode= format.dotencode -T'{defaultvalue}\n'
176 True
176 True
177
177
178 bytes
178 bytes
179
179
180 $ hg config --config commands.resolve.mark-check= commands -Tjson
180 $ hg config --config commands.resolve.mark-check= commands -Tjson
181 [
181 [
182 {
182 {
183 "defaultvalue": "none",
183 "defaultvalue": "none",
184 "name": "commands.resolve.mark-check",
184 "name": "commands.resolve.mark-check",
185 "source": "--config",
185 "source": "--config",
186 "value": ""
186 "value": ""
187 }
187 }
188 ]
188 ]
189 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
189 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
190 [
190 [
191 {"defaultvalue": "none"}
191 {"defaultvalue": "none"}
192 ]
192 ]
193 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
193 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
194 none
194 none
195
195
196 empty list
196 empty list
197
197
198 $ hg config --config commands.show.aliasprefix= commands -Tjson
198 $ hg config --config commands.show.aliasprefix= commands -Tjson
199 [
199 [
200 {
200 {
201 "defaultvalue": [],
201 "defaultvalue": [],
202 "name": "commands.show.aliasprefix",
202 "name": "commands.show.aliasprefix",
203 "source": "--config",
203 "source": "--config",
204 "value": ""
204 "value": ""
205 }
205 }
206 ]
206 ]
207 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
207 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
208 [
208 [
209 {"defaultvalue": []}
209 {"defaultvalue": []}
210 ]
210 ]
211 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
211 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
212
212
213
213
214 nonempty list
214 nonempty list
215
215
216 $ hg config --config progress.format= progress -Tjson
216 $ hg config --config progress.format= progress -Tjson
217 [
217 [
218 {
218 {
219 "defaultvalue": ["topic", "bar", "number", "estimate"],
219 "defaultvalue": ["topic", "bar", "number", "estimate"],
220 "name": "progress.format",
220 "name": "progress.format",
221 "source": "--config",
221 "source": "--config",
222 "value": ""
222 "value": ""
223 }
223 }
224 ]
224 ]
225 $ hg config --config progress.format= progress -T'json(defaultvalue)'
225 $ hg config --config progress.format= progress -T'json(defaultvalue)'
226 [
226 [
227 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
227 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
228 ]
228 ]
229 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
229 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
230 topic bar number estimate
230 topic bar number estimate
231
231
232 int
232 int
233
233
234 $ hg config --config profiling.freq= profiling -Tjson
234 $ hg config --config profiling.freq= profiling -Tjson
235 [
235 [
236 {
236 {
237 "defaultvalue": 1000,
237 "defaultvalue": 1000,
238 "name": "profiling.freq",
238 "name": "profiling.freq",
239 "source": "--config",
239 "source": "--config",
240 "value": ""
240 "value": ""
241 }
241 }
242 ]
242 ]
243 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
243 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
244 [
244 [
245 {"defaultvalue": 1000}
245 {"defaultvalue": 1000}
246 ]
246 ]
247 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
247 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
248 1000
248 1000
249
249
250 float
250 float
251
251
252 $ hg config --config profiling.showmax= profiling -Tjson
252 $ hg config --config profiling.showmax= profiling -Tjson
253 [
253 [
254 {
254 {
255 "defaultvalue": 0.999,
255 "defaultvalue": 0.999,
256 "name": "profiling.showmax",
256 "name": "profiling.showmax",
257 "source": "--config",
257 "source": "--config",
258 "value": ""
258 "value": ""
259 }
259 }
260 ]
260 ]
261 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
261 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
262 [
262 [
263 {"defaultvalue": 0.999}
263 {"defaultvalue": 0.999}
264 ]
264 ]
265 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
265 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
266 0.999
266 0.999
267
267
268 Test empty config source:
268 Test empty config source:
269
269
270 $ cat <<EOF > emptysource.py
270 $ cat <<EOF > emptysource.py
271 > def reposetup(ui, repo):
271 > def reposetup(ui, repo):
272 > ui.setconfig(b'empty', b'source', b'value')
272 > ui.setconfig(b'empty', b'source', b'value')
273 > EOF
273 > EOF
274 $ cp .hg/hgrc .hg/hgrc.orig
274 $ cp .hg/hgrc .hg/hgrc.orig
275 $ cat <<EOF >> .hg/hgrc
275 $ cat <<EOF >> .hg/hgrc
276 > [extensions]
276 > [extensions]
277 > emptysource = `pwd`/emptysource.py
277 > emptysource = `pwd`/emptysource.py
278 > EOF
278 > EOF
279
279
280 $ hg config --source empty.source
280 $ hg config --source empty.source
281 none: value
281 none: value
282 $ hg config empty.source -Tjson
282 $ hg config empty.source -Tjson
283 [
283 [
284 {
284 {
285 "defaultvalue": null,
285 "defaultvalue": null,
286 "name": "empty.source",
286 "name": "empty.source",
287 "source": "",
287 "source": "",
288 "value": "value"
288 "value": "value"
289 }
289 }
290 ]
290 ]
291
291
292 $ cp .hg/hgrc.orig .hg/hgrc
292 $ cp .hg/hgrc.orig .hg/hgrc
293
293
294 Test "%unset"
294 Test "%unset"
295
295
296 $ cat >> $HGRCPATH <<EOF
296 $ cat >> $HGRCPATH <<EOF
297 > [unsettest]
297 > [unsettest]
298 > local-hgrcpath = should be unset (HGRCPATH)
298 > local-hgrcpath = should be unset (HGRCPATH)
299 > %unset local-hgrcpath
299 > %unset local-hgrcpath
300 >
300 >
301 > global = should be unset (HGRCPATH)
301 > global = should be unset (HGRCPATH)
302 >
302 >
303 > both = should be unset (HGRCPATH)
303 > both = should be unset (HGRCPATH)
304 >
304 >
305 > set-after-unset = should be unset (HGRCPATH)
305 > set-after-unset = should be unset (HGRCPATH)
306 > EOF
306 > EOF
307
307
308 $ cat >> .hg/hgrc <<EOF
308 $ cat >> .hg/hgrc <<EOF
309 > [unsettest]
309 > [unsettest]
310 > local-hgrc = should be unset (.hg/hgrc)
310 > local-hgrc = should be unset (.hg/hgrc)
311 > %unset local-hgrc
311 > %unset local-hgrc
312 >
312 >
313 > %unset global
313 > %unset global
314 >
314 >
315 > both = should be unset (.hg/hgrc)
315 > both = should be unset (.hg/hgrc)
316 > %unset both
316 > %unset both
317 >
317 >
318 > set-after-unset = should be unset (.hg/hgrc)
318 > set-after-unset = should be unset (.hg/hgrc)
319 > %unset set-after-unset
319 > %unset set-after-unset
320 > set-after-unset = should be set (.hg/hgrc)
320 > set-after-unset = should be set (.hg/hgrc)
321 > EOF
321 > EOF
322
322
323 $ hg showconfig unsettest
323 $ hg showconfig unsettest
324 unsettest.set-after-unset=should be set (.hg/hgrc)
324 unsettest.set-after-unset=should be set (.hg/hgrc)
325
325
326 Test exit code when no config matches
326 Test exit code when no config matches
327
327
328 $ hg config Section.idontexist
328 $ hg config Section.idontexist
329 [1]
329 [1]
330
330
331 sub-options in [paths] aren't expanded
331 sub-options in [paths] aren't expanded
332
332
333 $ cat > .hg/hgrc << EOF
333 $ cat > .hg/hgrc << EOF
334 > [paths]
334 > [paths]
335 > foo = ~/foo
335 > foo = ~/foo
336 > foo:suboption = ~/foo
336 > foo:suboption = ~/foo
337 > EOF
337 > EOF
338
338
339 $ hg showconfig paths
339 $ hg showconfig paths
340 paths.foo=~/foo
340 paths.foo=~/foo
341 paths.foo:suboption=~/foo
341 paths.foo:suboption=~/foo
342
342
343 note: The path expansion no longer happens at the config level, but the path is
343 note: The path expansion no longer happens at the config level, but the path is
344 still expanded:
344 still expanded:
345
345
346 $ hg path | grep foo
346 $ hg path | grep foo
347 foo = $TESTTMP/foo
347 foo = $TESTTMP/foo
348
348
349 edit failure
349 edit failure
350
350
351 $ HGEDITOR=false hg config --edit
351 $ HGEDITOR=false hg config --edit
352 abort: edit failed: false exited with status 1
352 abort: edit failed: false exited with status 1
353 [10]
353 [10]
354
354
355 config affected by environment variables
355 config affected by environment variables
356
356
357 $ EDITOR=e1 VISUAL=e2 hg config --source | grep 'ui\.editor'
357 $ EDITOR=e1 VISUAL=e2 hg config --source | grep 'ui\.editor'
358 $VISUAL: ui.editor=e2
358 $VISUAL: ui.editor=e2
359
359
360 $ VISUAL=e2 hg config --source --config ui.editor=e3 | grep 'ui\.editor'
360 $ VISUAL=e2 hg config --source --config ui.editor=e3 | grep 'ui\.editor'
361 --config: ui.editor=e3
361 --config: ui.editor=e3
362
362
363 $ PAGER=p1 hg config --source | grep 'pager\.pager'
363 $ PAGER=p1 hg config --source | grep 'pager\.pager'
364 $PAGER: pager.pager=p1
364 $PAGER: pager.pager=p1
365
365
366 $ PAGER=p1 hg config --source --config pager.pager=p2 | grep 'pager\.pager'
366 $ PAGER=p1 hg config --source --config pager.pager=p2 | grep 'pager\.pager'
367 --config: pager.pager=p2
367 --config: pager.pager=p2
368
368
369 verify that aliases are evaluated as well
369 verify that aliases are evaluated as well
370
370
371 $ hg init aliastest
371 $ hg init aliastest
372 $ cd aliastest
372 $ cd aliastest
373 $ cat > .hg/hgrc << EOF
373 $ cat > .hg/hgrc << EOF
374 > [ui]
374 > [ui]
375 > user = repo user
375 > user = repo user
376 > EOF
376 > EOF
377 $ touch index
377 $ touch index
378 $ unset HGUSER
378 $ unset HGUSER
379 $ hg ci -Am test
379 $ hg ci -Am test
380 adding index
380 adding index
381 $ hg log --template '{author}\n'
381 $ hg log --template '{author}\n'
382 repo user
382 repo user
383 $ cd ..
383 $ cd ..
384
384
385 alias has lower priority
385 alias has lower priority
386
386
387 $ hg init aliaspriority
387 $ hg init aliaspriority
388 $ cd aliaspriority
388 $ cd aliaspriority
389 $ cat > .hg/hgrc << EOF
389 $ cat > .hg/hgrc << EOF
390 > [ui]
390 > [ui]
391 > user = alias user
391 > user = alias user
392 > username = repo user
392 > username = repo user
393 > EOF
393 > EOF
394 $ touch index
394 $ touch index
395 $ unset HGUSER
395 $ unset HGUSER
396 $ hg ci -Am test
396 $ hg ci -Am test
397 adding index
397 adding index
398 $ hg log --template '{author}\n'
398 $ hg log --template '{author}\n'
399 repo user
399 repo user
400 $ cd ..
400 $ cd ..
401
401
402 configs should be read in lexicographical order
402 configs should be read in lexicographical order
403
403
404 $ mkdir configs
404 $ mkdir configs
405 $ for i in `$TESTDIR/seq.py 10 99`; do
405 $ for i in `$TESTDIR/seq.py 10 99`; do
406 > printf "[section]\nkey=$i" > configs/$i.rc
406 > printf "[section]\nkey=$i" > configs/$i.rc
407 > done
407 > done
408 $ HGRCPATH=configs hg config section.key
408 $ HGRCPATH=configs hg config section.key
409 99
409 99
410
410
411 Listing all config options
411 Listing all config options
412 ==========================
412 ==========================
413
413
414 The feature is experimental and behavior may varies. This test exists to make sure the code is run. We grep it to avoid too much variability in its current experimental state.
414 The feature is experimental and behavior may varies. This test exists to make sure the code is run. We grep it to avoid too much variability in its current experimental state.
415
415
416 $ hg config --exp-all-known | grep commit
416 $ hg config --exp-all-known | grep commit | grep -v ssh
417 commands.commit.interactive.git=False
417 commands.commit.interactive.git=False
418 commands.commit.interactive.ignoreblanklines=False
418 commands.commit.interactive.ignoreblanklines=False
419 commands.commit.interactive.ignorews=False
419 commands.commit.interactive.ignorews=False
420 commands.commit.interactive.ignorewsamount=False
420 commands.commit.interactive.ignorewsamount=False
421 commands.commit.interactive.ignorewseol=False
421 commands.commit.interactive.ignorewseol=False
422 commands.commit.interactive.nobinary=False
422 commands.commit.interactive.nobinary=False
423 commands.commit.interactive.nodates=False
423 commands.commit.interactive.nodates=False
424 commands.commit.interactive.noprefix=False
424 commands.commit.interactive.noprefix=False
425 commands.commit.interactive.showfunc=False
425 commands.commit.interactive.showfunc=False
426 commands.commit.interactive.unified=None
426 commands.commit.interactive.unified=None
427 commands.commit.interactive.word-diff=False
427 commands.commit.interactive.word-diff=False
428 commands.commit.post-status=False
428 commands.commit.post-status=False
429 convert.git.committeractions=[*'messagedifferent'] (glob)
429 convert.git.committeractions=[*'messagedifferent'] (glob)
430 convert.svn.dangerous-set-commit-dates=False
430 convert.svn.dangerous-set-commit-dates=False
431 experimental.copytrace.sourcecommitlimit=100
431 experimental.copytrace.sourcecommitlimit=100
432 phases.new-commit=draft
432 phases.new-commit=draft
433 ui.allowemptycommit=False
433 ui.allowemptycommit=False
434 ui.commitsubrepos=False
434 ui.commitsubrepos=False
435
435
436
436
437 Configuration priority
437 Configuration priority
438 ======================
438 ======================
439
439
440 setup necessary file
440 setup necessary file
441
441
442 $ cat > file-A.rc << EOF
442 $ cat > file-A.rc << EOF
443 > [config-test]
443 > [config-test]
444 > basic = value-A
444 > basic = value-A
445 > pre-include= value-A
445 > pre-include= value-A
446 > %include ./included.rc
446 > %include ./included.rc
447 > post-include= value-A
447 > post-include= value-A
448 > [command-templates]
448 > [command-templates]
449 > log = "value-A\n"
449 > log = "value-A\n"
450 > EOF
450 > EOF
451
451
452 $ cat > file-B.rc << EOF
452 $ cat > file-B.rc << EOF
453 > [config-test]
453 > [config-test]
454 > basic = value-B
454 > basic = value-B
455 > [ui]
455 > [ui]
456 > logtemplate = "value-B\n"
456 > logtemplate = "value-B\n"
457 > EOF
457 > EOF
458
458
459
459
460 $ cat > included.rc << EOF
460 $ cat > included.rc << EOF
461 > [config-test]
461 > [config-test]
462 > pre-include= value-included
462 > pre-include= value-included
463 > post-include= value-included
463 > post-include= value-included
464 > EOF
464 > EOF
465
465
466 $ cat > file-C.rc << EOF
466 $ cat > file-C.rc << EOF
467 > %include ./included-alias-C.rc
467 > %include ./included-alias-C.rc
468 > [ui]
468 > [ui]
469 > logtemplate = "value-C\n"
469 > logtemplate = "value-C\n"
470 > EOF
470 > EOF
471
471
472 $ cat > included-alias-C.rc << EOF
472 $ cat > included-alias-C.rc << EOF
473 > [command-templates]
473 > [command-templates]
474 > log = "value-included\n"
474 > log = "value-included\n"
475 > EOF
475 > EOF
476
476
477
477
478 $ cat > file-D.rc << EOF
478 $ cat > file-D.rc << EOF
479 > [command-templates]
479 > [command-templates]
480 > log = "value-D\n"
480 > log = "value-D\n"
481 > %include ./included-alias-D.rc
481 > %include ./included-alias-D.rc
482 > EOF
482 > EOF
483
483
484 $ cat > included-alias-D.rc << EOF
484 $ cat > included-alias-D.rc << EOF
485 > [ui]
485 > [ui]
486 > logtemplate = "value-included\n"
486 > logtemplate = "value-included\n"
487 > EOF
487 > EOF
488
488
489 Simple order checking
489 Simple order checking
490 ---------------------
490 ---------------------
491
491
492 If file B is read after file A, value from B overwrite value from A.
492 If file B is read after file A, value from B overwrite value from A.
493
493
494 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic
494 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic
495 value-B
495 value-B
496
496
497 Ordering from include
497 Ordering from include
498 ---------------------
498 ---------------------
499
499
500 value from an include overwrite value defined before the include, but not the one defined after the include
500 value from an include overwrite value defined before the include, but not the one defined after the include
501
501
502 $ HGRCPATH="file-A.rc" hg config config-test.pre-include
502 $ HGRCPATH="file-A.rc" hg config config-test.pre-include
503 value-included
503 value-included
504 $ HGRCPATH="file-A.rc" hg config config-test.post-include
504 $ HGRCPATH="file-A.rc" hg config config-test.post-include
505 value-A
505 value-A
506
506
507 command line override
507 command line override
508 ---------------------
508 ---------------------
509
509
510 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic --config config-test.basic=value-CLI
510 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg config config-test.basic --config config-test.basic=value-CLI
511 value-CLI
511 value-CLI
512
512
513 Alias ordering
513 Alias ordering
514 --------------
514 --------------
515
515
516 The official config is now `command-templates.log`, the historical
516 The official config is now `command-templates.log`, the historical
517 `ui.logtemplate` is a valid alternative for it.
517 `ui.logtemplate` is a valid alternative for it.
518
518
519 When both are defined, The config value read the last "win", this should keep
519 When both are defined, The config value read the last "win", this should keep
520 being true if the config have other alias. In other word, the config value read
520 being true if the config have other alias. In other word, the config value read
521 earlier will be considered "lower level" and the config read later would be
521 earlier will be considered "lower level" and the config read later would be
522 considered "higher level". And higher level values wins.
522 considered "higher level". And higher level values wins.
523
523
524 $ HGRCPATH="file-A.rc" hg log -r .
524 $ HGRCPATH="file-A.rc" hg log -r .
525 value-A
525 value-A
526 $ HGRCPATH="file-B.rc" hg log -r .
526 $ HGRCPATH="file-B.rc" hg log -r .
527 value-B
527 value-B
528 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r .
528 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r .
529 value-B
529 value-B
530
530
531 Alias and include
531 Alias and include
532 -----------------
532 -----------------
533
533
534 The pre/post include priority should also apply when tie-breaking alternatives.
534 The pre/post include priority should also apply when tie-breaking alternatives.
535 See the case above for details about the two config options used.
535 See the case above for details about the two config options used.
536
536
537 $ HGRCPATH="file-C.rc" hg log -r .
537 $ HGRCPATH="file-C.rc" hg log -r .
538 value-C
538 value-C
539 $ HGRCPATH="file-D.rc" hg log -r .
539 $ HGRCPATH="file-D.rc" hg log -r .
540 value-included
540 value-included
541
541
542 command line override
542 command line override
543 ---------------------
543 ---------------------
544
544
545 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r . --config ui.logtemplate="value-CLI\n"
545 $ HGRCPATH=`path_list_var "file-A.rc:file-B.rc"` hg log -r . --config ui.logtemplate="value-CLI\n"
546 value-CLI
546 value-CLI
@@ -1,618 +1,618 b''
1 This test is a duplicate of 'test-http.t' feel free to factor out
1 This test is a duplicate of 'test-http.t' feel free to factor out
2 parts that are not bundle1/bundle2 specific.
2 parts that are not bundle1/bundle2 specific.
3
3
4 #testcases sshv1 sshv2
4 #testcases sshv1 sshv2
5
5
6 #if sshv2
6 #if sshv2
7 $ cat >> $HGRCPATH << EOF
7 $ cat >> $HGRCPATH << EOF
8 > [experimental]
8 > [experimental]
9 > sshpeer.advertise-v2 = true
9 > sshpeer.advertise-v2 = true
10 > sshserver.support-v2 = true
10 > sshserver.support-v2 = true
11 > EOF
11 > EOF
12 #endif
12 #endif
13
13
14 $ cat << EOF >> $HGRCPATH
14 $ cat << EOF >> $HGRCPATH
15 > [devel]
15 > [devel]
16 > # This test is dedicated to interaction through old bundle
16 > # This test is dedicated to interaction through old bundle
17 > legacy.exchange = bundle1
17 > legacy.exchange = bundle1
18 > EOF
18 > EOF
19
19
20
20
21 This test tries to exercise the ssh functionality with a dummy script
21 This test tries to exercise the ssh functionality with a dummy script
22
22
23 creating 'remote' repo
23 creating 'remote' repo
24
24
25 $ hg init remote
25 $ hg init remote
26 $ cd remote
26 $ cd remote
27 $ echo this > foo
27 $ echo this > foo
28 $ echo this > fooO
28 $ echo this > fooO
29 $ hg ci -A -m "init" foo fooO
29 $ hg ci -A -m "init" foo fooO
30
30
31 insert a closed branch (issue4428)
31 insert a closed branch (issue4428)
32
32
33 $ hg up null
33 $ hg up null
34 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
34 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
35 $ hg branch closed
35 $ hg branch closed
36 marked working directory as branch closed
36 marked working directory as branch closed
37 (branches are permanent and global, did you want a bookmark?)
37 (branches are permanent and global, did you want a bookmark?)
38 $ hg ci -mc0
38 $ hg ci -mc0
39 $ hg ci --close-branch -mc1
39 $ hg ci --close-branch -mc1
40 $ hg up -q default
40 $ hg up -q default
41
41
42 configure for serving
42 configure for serving
43
43
44 $ cat <<EOF > .hg/hgrc
44 $ cat <<EOF > .hg/hgrc
45 > [server]
45 > [server]
46 > uncompressed = True
46 > uncompressed = True
47 >
47 >
48 > [hooks]
48 > [hooks]
49 > changegroup = sh -c "printenv.py --line changegroup-in-remote 0 ../dummylog"
49 > changegroup = sh -c "printenv.py --line changegroup-in-remote 0 ../dummylog"
50 > EOF
50 > EOF
51 $ cd $TESTTMP
51 $ cd $TESTTMP
52
52
53 repo not found error
53 repo not found error
54
54
55 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
55 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
56 remote: abort: repository nonexistent not found
56 remote: abort: repository nonexistent not found
57 abort: no suitable response from remote hg
57 abort: no suitable response from remote hg
58 [255]
58 [255]
59
59
60 non-existent absolute path
60 non-existent absolute path
61
61
62 #if no-msys
62 #if no-msys
63 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
63 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
64 remote: abort: repository /$TESTTMP/nonexistent not found
64 remote: abort: repository /$TESTTMP/nonexistent not found
65 abort: no suitable response from remote hg
65 abort: no suitable response from remote hg
66 [255]
66 [255]
67 #endif
67 #endif
68
68
69 clone remote via stream
69 clone remote via stream
70
70
71 #if no-reposimplestore
71 #if no-reposimplestore
72
72
73 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
73 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
74 streaming all changes
74 streaming all changes
75 4 files to transfer, 602 bytes of data (no-zstd !)
75 4 files to transfer, 602 bytes of data (no-zstd !)
76 transferred 602 bytes in * seconds (*) (glob) (no-zstd !)
76 transferred 602 bytes in * seconds (*) (glob) (no-zstd !)
77 4 files to transfer, 621 bytes of data (zstd !)
77 4 files to transfer, 621 bytes of data (zstd !)
78 transferred 621 bytes in * seconds (* */sec) (glob) (zstd !)
78 transferred 621 bytes in * seconds (* */sec) (glob) (zstd !)
79 searching for changes
79 searching for changes
80 no changes found
80 no changes found
81 updating to branch default
81 updating to branch default
82 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 $ cd local-stream
83 $ cd local-stream
84 $ hg verify
84 $ hg verify
85 checking changesets
85 checking changesets
86 checking manifests
86 checking manifests
87 crosschecking files in changesets and manifests
87 crosschecking files in changesets and manifests
88 checking files
88 checking files
89 checked 3 changesets with 2 changes to 2 files
89 checked 3 changesets with 2 changes to 2 files
90 $ hg branches
90 $ hg branches
91 default 0:1160648e36ce
91 default 0:1160648e36ce
92 $ cd $TESTTMP
92 $ cd $TESTTMP
93
93
94 clone bookmarks via stream
94 clone bookmarks via stream
95
95
96 $ hg -R local-stream book mybook
96 $ hg -R local-stream book mybook
97 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
97 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
98 streaming all changes
98 streaming all changes
99 4 files to transfer, 602 bytes of data (no-zstd !)
99 4 files to transfer, 602 bytes of data (no-zstd !)
100 transferred 602 bytes in * seconds (*) (glob) (no-zstd !)
100 transferred 602 bytes in * seconds (*) (glob) (no-zstd !)
101 4 files to transfer, 621 bytes of data (zstd !)
101 4 files to transfer, 621 bytes of data (zstd !)
102 transferred 621 bytes in * seconds (* */sec) (glob) (zstd !)
102 transferred 621 bytes in * seconds (* */sec) (glob) (zstd !)
103 searching for changes
103 searching for changes
104 no changes found
104 no changes found
105 updating to branch default
105 updating to branch default
106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 $ cd stream2
107 $ cd stream2
108 $ hg book
108 $ hg book
109 mybook 0:1160648e36ce
109 mybook 0:1160648e36ce
110 $ cd $TESTTMP
110 $ cd $TESTTMP
111 $ rm -rf local-stream stream2
111 $ rm -rf local-stream stream2
112
112
113 #endif
113 #endif
114
114
115 clone remote via pull
115 clone remote via pull
116
116
117 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
117 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
118 requesting all changes
118 requesting all changes
119 adding changesets
119 adding changesets
120 adding manifests
120 adding manifests
121 adding file changes
121 adding file changes
122 added 3 changesets with 2 changes to 2 files
122 added 3 changesets with 2 changes to 2 files
123 new changesets 1160648e36ce:ad076bfb429d
123 new changesets 1160648e36ce:ad076bfb429d
124 updating to branch default
124 updating to branch default
125 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
125 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
126
126
127 verify
127 verify
128
128
129 $ cd local
129 $ cd local
130 $ hg verify
130 $ hg verify
131 checking changesets
131 checking changesets
132 checking manifests
132 checking manifests
133 crosschecking files in changesets and manifests
133 crosschecking files in changesets and manifests
134 checking files
134 checking files
135 checked 3 changesets with 2 changes to 2 files
135 checked 3 changesets with 2 changes to 2 files
136 $ cat >> .hg/hgrc <<EOF
136 $ cat >> .hg/hgrc <<EOF
137 > [hooks]
137 > [hooks]
138 > changegroup = sh -c "printenv.py --line changegroup-in-local 0 ../dummylog"
138 > changegroup = sh -c "printenv.py --line changegroup-in-local 0 ../dummylog"
139 > EOF
139 > EOF
140
140
141 empty default pull
141 empty default pull
142
142
143 $ hg paths
143 $ hg paths
144 default = ssh://user@dummy/remote
144 default = ssh://user@dummy/remote
145 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
145 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
146 pulling from ssh://user@dummy/remote
146 pulling from ssh://user@dummy/remote
147 searching for changes
147 searching for changes
148 no changes found
148 no changes found
149
149
150 pull from wrong ssh URL
150 pull from wrong ssh URL
151
151
152 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
152 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
153 pulling from ssh://user@dummy/doesnotexist
153 pulling from ssh://user@dummy/doesnotexist
154 remote: abort: repository doesnotexist not found
154 remote: abort: repository doesnotexist not found
155 abort: no suitable response from remote hg
155 abort: no suitable response from remote hg
156 [255]
156 [255]
157
157
158 local change
158 local change
159
159
160 $ echo bleah > foo
160 $ echo bleah > foo
161 $ hg ci -m "add"
161 $ hg ci -m "add"
162
162
163 updating rc
163 updating rc
164
164
165 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
165 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
166 $ echo "[ui]" >> .hg/hgrc
166 $ echo "[ui]" >> .hg/hgrc
167 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
167 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
168
168
169 find outgoing
169 find outgoing
170
170
171 $ hg out ssh://user@dummy/remote
171 $ hg out ssh://user@dummy/remote
172 comparing with ssh://user@dummy/remote
172 comparing with ssh://user@dummy/remote
173 searching for changes
173 searching for changes
174 changeset: 3:a28a9d1a809c
174 changeset: 3:a28a9d1a809c
175 tag: tip
175 tag: tip
176 parent: 0:1160648e36ce
176 parent: 0:1160648e36ce
177 user: test
177 user: test
178 date: Thu Jan 01 00:00:00 1970 +0000
178 date: Thu Jan 01 00:00:00 1970 +0000
179 summary: add
179 summary: add
180
180
181
181
182 find incoming on the remote side
182 find incoming on the remote side
183
183
184 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
184 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
185 comparing with ssh://user@dummy/local
185 comparing with ssh://user@dummy/local
186 searching for changes
186 searching for changes
187 changeset: 3:a28a9d1a809c
187 changeset: 3:a28a9d1a809c
188 tag: tip
188 tag: tip
189 parent: 0:1160648e36ce
189 parent: 0:1160648e36ce
190 user: test
190 user: test
191 date: Thu Jan 01 00:00:00 1970 +0000
191 date: Thu Jan 01 00:00:00 1970 +0000
192 summary: add
192 summary: add
193
193
194
194
195 find incoming on the remote side (using absolute path)
195 find incoming on the remote side (using absolute path)
196
196
197 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
197 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
198 comparing with ssh://user@dummy/$TESTTMP/local
198 comparing with ssh://user@dummy/$TESTTMP/local
199 searching for changes
199 searching for changes
200 changeset: 3:a28a9d1a809c
200 changeset: 3:a28a9d1a809c
201 tag: tip
201 tag: tip
202 parent: 0:1160648e36ce
202 parent: 0:1160648e36ce
203 user: test
203 user: test
204 date: Thu Jan 01 00:00:00 1970 +0000
204 date: Thu Jan 01 00:00:00 1970 +0000
205 summary: add
205 summary: add
206
206
207
207
208 push
208 push
209
209
210 $ hg push
210 $ hg push
211 pushing to ssh://user@dummy/remote
211 pushing to ssh://user@dummy/remote
212 searching for changes
212 searching for changes
213 remote: adding changesets
213 remote: adding changesets
214 remote: adding manifests
214 remote: adding manifests
215 remote: adding file changes
215 remote: adding file changes
216 remote: added 1 changesets with 1 changes to 1 files
216 remote: added 1 changesets with 1 changes to 1 files
217 $ cd $TESTTMP/remote
217 $ cd $TESTTMP/remote
218
218
219 check remote tip
219 check remote tip
220
220
221 $ hg tip
221 $ hg tip
222 changeset: 3:a28a9d1a809c
222 changeset: 3:a28a9d1a809c
223 tag: tip
223 tag: tip
224 parent: 0:1160648e36ce
224 parent: 0:1160648e36ce
225 user: test
225 user: test
226 date: Thu Jan 01 00:00:00 1970 +0000
226 date: Thu Jan 01 00:00:00 1970 +0000
227 summary: add
227 summary: add
228
228
229 $ hg verify
229 $ hg verify
230 checking changesets
230 checking changesets
231 checking manifests
231 checking manifests
232 crosschecking files in changesets and manifests
232 crosschecking files in changesets and manifests
233 checking files
233 checking files
234 checked 4 changesets with 3 changes to 2 files
234 checked 4 changesets with 3 changes to 2 files
235 $ hg cat -r tip foo
235 $ hg cat -r tip foo
236 bleah
236 bleah
237 $ echo z > z
237 $ echo z > z
238 $ hg ci -A -m z z
238 $ hg ci -A -m z z
239 created new head
239 created new head
240
240
241 test pushkeys and bookmarks
241 test pushkeys and bookmarks
242
242
243 $ cd $TESTTMP/local
243 $ cd $TESTTMP/local
244 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
244 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
245 bookmarks
245 bookmarks
246 namespaces
246 namespaces
247 phases
247 phases
248 $ hg book foo -r 0
248 $ hg book foo -r 0
249 $ hg out -B
249 $ hg out -B
250 comparing with ssh://user@dummy/remote
250 comparing with ssh://user@dummy/remote
251 searching for changed bookmarks
251 searching for changed bookmarks
252 foo 1160648e36ce
252 foo 1160648e36ce
253 $ hg push -B foo
253 $ hg push -B foo
254 pushing to ssh://user@dummy/remote
254 pushing to ssh://user@dummy/remote
255 searching for changes
255 searching for changes
256 no changes found
256 no changes found
257 exporting bookmark foo
257 exporting bookmark foo
258 [1]
258 [1]
259 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
259 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
260 foo 1160648e36cec0054048a7edc4110c6f84fde594
260 foo 1160648e36cec0054048a7edc4110c6f84fde594
261 $ hg book -f foo
261 $ hg book -f foo
262 $ hg push --traceback
262 $ hg push --traceback
263 pushing to ssh://user@dummy/remote
263 pushing to ssh://user@dummy/remote
264 searching for changes
264 searching for changes
265 no changes found
265 no changes found
266 updating bookmark foo
266 updating bookmark foo
267 [1]
267 [1]
268 $ hg book -d foo
268 $ hg book -d foo
269 $ hg in -B
269 $ hg in -B
270 comparing with ssh://user@dummy/remote
270 comparing with ssh://user@dummy/remote
271 searching for changed bookmarks
271 searching for changed bookmarks
272 foo a28a9d1a809c
272 foo a28a9d1a809c
273 $ hg book -f -r 0 foo
273 $ hg book -f -r 0 foo
274 $ hg pull -B foo
274 $ hg pull -B foo
275 pulling from ssh://user@dummy/remote
275 pulling from ssh://user@dummy/remote
276 no changes found
276 no changes found
277 updating bookmark foo
277 updating bookmark foo
278 $ hg book -d foo
278 $ hg book -d foo
279 $ hg push -B foo
279 $ hg push -B foo
280 pushing to ssh://user@dummy/remote
280 pushing to ssh://user@dummy/remote
281 searching for changes
281 searching for changes
282 no changes found
282 no changes found
283 deleting remote bookmark foo
283 deleting remote bookmark foo
284 [1]
284 [1]
285
285
286 a bad, evil hook that prints to stdout
286 a bad, evil hook that prints to stdout
287
287
288 $ cat <<EOF > $TESTTMP/badhook
288 $ cat <<EOF > $TESTTMP/badhook
289 > import sys
289 > import sys
290 > sys.stdout.write("KABOOM\n")
290 > sys.stdout.write("KABOOM\n")
291 > EOF
291 > EOF
292
292
293 $ echo '[hooks]' >> ../remote/.hg/hgrc
293 $ echo '[hooks]' >> ../remote/.hg/hgrc
294 $ echo "changegroup.stdout = \"$PYTHON\" $TESTTMP/badhook" >> ../remote/.hg/hgrc
294 $ echo "changegroup.stdout = \"$PYTHON\" $TESTTMP/badhook" >> ../remote/.hg/hgrc
295 $ echo r > r
295 $ echo r > r
296 $ hg ci -A -m z r
296 $ hg ci -A -m z r
297
297
298 push should succeed even though it has an unexpected response
298 push should succeed even though it has an unexpected response
299
299
300 $ hg push
300 $ hg push
301 pushing to ssh://user@dummy/remote
301 pushing to ssh://user@dummy/remote
302 searching for changes
302 searching for changes
303 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
303 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
304 remote: adding changesets
304 remote: adding changesets
305 remote: adding manifests
305 remote: adding manifests
306 remote: adding file changes
306 remote: adding file changes
307 remote: added 1 changesets with 1 changes to 1 files (py3 !)
307 remote: added 1 changesets with 1 changes to 1 files (py3 !)
308 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
308 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
309 remote: KABOOM
309 remote: KABOOM
310 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
310 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
311 $ hg -R ../remote heads
311 $ hg -R ../remote heads
312 changeset: 5:1383141674ec
312 changeset: 5:1383141674ec
313 tag: tip
313 tag: tip
314 parent: 3:a28a9d1a809c
314 parent: 3:a28a9d1a809c
315 user: test
315 user: test
316 date: Thu Jan 01 00:00:00 1970 +0000
316 date: Thu Jan 01 00:00:00 1970 +0000
317 summary: z
317 summary: z
318
318
319 changeset: 4:6c0482d977a3
319 changeset: 4:6c0482d977a3
320 parent: 0:1160648e36ce
320 parent: 0:1160648e36ce
321 user: test
321 user: test
322 date: Thu Jan 01 00:00:00 1970 +0000
322 date: Thu Jan 01 00:00:00 1970 +0000
323 summary: z
323 summary: z
324
324
325
325
326 clone bookmarks
326 clone bookmarks
327
327
328 $ hg -R ../remote bookmark test
328 $ hg -R ../remote bookmark test
329 $ hg -R ../remote bookmarks
329 $ hg -R ../remote bookmarks
330 * test 4:6c0482d977a3
330 * test 4:6c0482d977a3
331 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
331 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
332 requesting all changes
332 requesting all changes
333 adding changesets
333 adding changesets
334 adding manifests
334 adding manifests
335 adding file changes
335 adding file changes
336 added 6 changesets with 5 changes to 4 files (+1 heads)
336 added 6 changesets with 5 changes to 4 files (+1 heads)
337 new changesets 1160648e36ce:1383141674ec
337 new changesets 1160648e36ce:1383141674ec
338 updating to branch default
338 updating to branch default
339 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
339 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 $ hg -R local-bookmarks bookmarks
340 $ hg -R local-bookmarks bookmarks
341 test 4:6c0482d977a3
341 test 4:6c0482d977a3
342
342
343 passwords in ssh urls are not supported
343 passwords in ssh urls are not supported
344 (we use a glob here because different Python versions give different
344 (we use a glob here because different Python versions give different
345 results here)
345 results here)
346
346
347 $ hg push ssh://user:erroneouspwd@dummy/remote
347 $ hg push ssh://user:erroneouspwd@dummy/remote
348 pushing to ssh://user:*@dummy/remote (glob)
348 pushing to ssh://user:*@dummy/remote (glob)
349 abort: password in URL not supported
349 abort: password in URL not supported
350 [255]
350 [255]
351
351
352 $ cd $TESTTMP
352 $ cd $TESTTMP
353
353
354 hide outer repo
354 hide outer repo
355 $ hg init
355 $ hg init
356
356
357 Test remote paths with spaces (issue2983):
357 Test remote paths with spaces (issue2983):
358
358
359 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
359 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
360 $ touch "$TESTTMP/a repo/test"
360 $ touch "$TESTTMP/a repo/test"
361 $ hg -R 'a repo' commit -A -m "test"
361 $ hg -R 'a repo' commit -A -m "test"
362 adding test
362 adding test
363 $ hg -R 'a repo' tag tag
363 $ hg -R 'a repo' tag tag
364 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
364 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
365 73649e48688a
365 73649e48688a
366
366
367 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
367 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
368 abort: unknown revision 'noNoNO'
368 abort: unknown revision 'noNoNO'
369 [255]
369 [255]
370
370
371 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
371 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
372
372
373 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
373 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
374 destination directory: a repo
374 destination directory: a repo
375 abort: destination 'a repo' is not empty
375 abort: destination 'a repo' is not empty
376 [10]
376 [10]
377
377
378 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
378 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
379 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
379 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
380 parameters:
380 parameters:
381
381
382 $ cat > ssh.sh << EOF
382 $ cat > ssh.sh << EOF
383 > userhost="\$1"
383 > userhost="\$1"
384 > SSH_ORIGINAL_COMMAND="\$2"
384 > SSH_ORIGINAL_COMMAND="\$2"
385 > export SSH_ORIGINAL_COMMAND
385 > export SSH_ORIGINAL_COMMAND
386 > PYTHONPATH="$PYTHONPATH"
386 > PYTHONPATH="$PYTHONPATH"
387 > export PYTHONPATH
387 > export PYTHONPATH
388 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
388 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
389 > EOF
389 > EOF
390
390
391 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
391 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
392 73649e48688a
392 73649e48688a
393
393
394 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
394 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
395 remote: Illegal repository "$TESTTMP/a'repo"
395 remote: Illegal repository "$TESTTMP/a'repo"
396 abort: no suitable response from remote hg
396 abort: no suitable response from remote hg
397 [255]
397 [255]
398
398
399 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
399 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
400 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
400 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
401 abort: no suitable response from remote hg
401 abort: no suitable response from remote hg
402 [255]
402 [255]
403
403
404 $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
404 $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
405 Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
405 Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
406 [255]
406 [255]
407
407
408 Test hg-ssh in read-only mode:
408 Test hg-ssh in read-only mode:
409
409
410 $ cat > ssh.sh << EOF
410 $ cat > ssh.sh << EOF
411 > userhost="\$1"
411 > userhost="\$1"
412 > SSH_ORIGINAL_COMMAND="\$2"
412 > SSH_ORIGINAL_COMMAND="\$2"
413 > export SSH_ORIGINAL_COMMAND
413 > export SSH_ORIGINAL_COMMAND
414 > PYTHONPATH="$PYTHONPATH"
414 > PYTHONPATH="$PYTHONPATH"
415 > export PYTHONPATH
415 > export PYTHONPATH
416 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
416 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
417 > EOF
417 > EOF
418
418
419 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
419 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
420 requesting all changes
420 requesting all changes
421 adding changesets
421 adding changesets
422 adding manifests
422 adding manifests
423 adding file changes
423 adding file changes
424 added 6 changesets with 5 changes to 4 files (+1 heads)
424 added 6 changesets with 5 changes to 4 files (+1 heads)
425 new changesets 1160648e36ce:1383141674ec
425 new changesets 1160648e36ce:1383141674ec
426 updating to branch default
426 updating to branch default
427 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
427 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
428
428
429 $ cd read-only-local
429 $ cd read-only-local
430 $ echo "baz" > bar
430 $ echo "baz" > bar
431 $ hg ci -A -m "unpushable commit" bar
431 $ hg ci -A -m "unpushable commit" bar
432 $ hg push --ssh "sh ../ssh.sh"
432 $ hg push --ssh "sh ../ssh.sh"
433 pushing to ssh://user@dummy/*/remote (glob)
433 pushing to ssh://user@dummy/*/remote (glob)
434 searching for changes
434 searching for changes
435 remote: Permission denied
435 remote: Permission denied
436 remote: abort: pretxnopen.hg-ssh hook failed
436 remote: abort: pretxnopen.hg-ssh hook failed
437 remote: Permission denied
437 remote: Permission denied
438 remote: pushkey-abort: prepushkey.hg-ssh hook failed
438 remote: pushkey-abort: prepushkey.hg-ssh hook failed
439 updating 6c0482d977a3 to public failed!
439 updating 6c0482d977a3 to public failed!
440 [1]
440 [1]
441
441
442 $ cd $TESTTMP
442 $ cd $TESTTMP
443
443
444 stderr from remote commands should be printed before stdout from local code (issue4336)
444 stderr from remote commands should be printed before stdout from local code (issue4336)
445
445
446 $ hg clone remote stderr-ordering
446 $ hg clone remote stderr-ordering
447 updating to branch default
447 updating to branch default
448 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 $ cd stderr-ordering
449 $ cd stderr-ordering
450 $ cat >> localwrite.py << EOF
450 $ cat >> localwrite.py << EOF
451 > from mercurial import exchange, extensions
451 > from mercurial import exchange, extensions
452 >
452 >
453 > def wrappedpush(orig, repo, *args, **kwargs):
453 > def wrappedpush(orig, repo, *args, **kwargs):
454 > res = orig(repo, *args, **kwargs)
454 > res = orig(repo, *args, **kwargs)
455 > repo.ui.write(b'local stdout\n')
455 > repo.ui.write(b'local stdout\n')
456 > return res
456 > return res
457 >
457 >
458 > def extsetup(ui):
458 > def extsetup(ui):
459 > extensions.wrapfunction(exchange, b'push', wrappedpush)
459 > extensions.wrapfunction(exchange, b'push', wrappedpush)
460 > EOF
460 > EOF
461
461
462 $ cat >> .hg/hgrc << EOF
462 $ cat >> .hg/hgrc << EOF
463 > [paths]
463 > [paths]
464 > default-push = ssh://user@dummy/remote
464 > default-push = ssh://user@dummy/remote
465 > [ui]
465 > [ui]
466 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
466 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
467 > [extensions]
467 > [extensions]
468 > localwrite = localwrite.py
468 > localwrite = localwrite.py
469 > EOF
469 > EOF
470
470
471 $ echo localwrite > foo
471 $ echo localwrite > foo
472 $ hg commit -m 'testing localwrite'
472 $ hg commit -m 'testing localwrite'
473 $ hg push
473 $ hg push
474 pushing to ssh://user@dummy/remote
474 pushing to ssh://user@dummy/remote
475 searching for changes
475 searching for changes
476 remote: adding changesets
476 remote: adding changesets
477 remote: adding manifests
477 remote: adding manifests
478 remote: adding file changes
478 remote: adding file changes
479 remote: added 1 changesets with 1 changes to 1 files (py3 !)
479 remote: added 1 changesets with 1 changes to 1 files (py3 !)
480 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
480 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
481 remote: KABOOM
481 remote: KABOOM
482 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
482 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
483 local stdout
483 local stdout
484
484
485 debug output
485 debug output
486
486
487 $ hg pull --debug ssh://user@dummy/remote
487 $ hg pull --debug ssh://user@dummy/remote
488 pulling from ssh://user@dummy/remote
488 pulling from ssh://user@dummy/remote
489 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
489 running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R remote serve --stdio['"] (re)
490 sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !)
490 sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !)
491 sending hello command
491 sending hello command
492 sending between command
492 sending between command
493 remote: 444 (sshv1 no-rust !)
493 remote: 444 (sshv1 no-rust !)
494 remote: 463 (sshv1 rust !)
494 remote: 463 (sshv1 rust !)
495 protocol upgraded to exp-ssh-v2-0003 (sshv2 !)
495 protocol upgraded to exp-ssh-v2-0003 (sshv2 !)
496 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !)
496 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !)
497 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !)
497 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !)
498 remote: 1 (sshv1 !)
498 remote: 1 (sshv1 !)
499 sending protocaps command
499 sending protocaps command
500 preparing listkeys for "bookmarks"
500 preparing listkeys for "bookmarks"
501 sending listkeys command
501 sending listkeys command
502 received listkey for "bookmarks": 45 bytes
502 received listkey for "bookmarks": 45 bytes
503 query 1; heads
503 query 1; heads
504 sending batch command
504 sending batch command
505 searching for changes
505 searching for changes
506 all remote heads known locally
506 all remote heads known locally
507 no changes found
507 no changes found
508 preparing listkeys for "phases"
508 preparing listkeys for "phases"
509 sending listkeys command
509 sending listkeys command
510 received listkey for "phases": 15 bytes
510 received listkey for "phases": 15 bytes
511 checking for updated bookmarks
511 checking for updated bookmarks
512
512
513 $ cd $TESTTMP
513 $ cd $TESTTMP
514
514
515 $ cat dummylog
515 $ cat dummylog
516 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
516 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
517 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio (no-msys !)
517 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio (no-msys !)
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
518 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
519 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
519 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
520 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
521 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
521 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
522 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
522 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
523 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
524 Got arguments 1:user@dummy 2:hg -R local serve --stdio
524 Got arguments 1:user@dummy 2:hg -R local serve --stdio
525 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
525 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
526 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
526 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
527 changegroup-in-remote hook: HG_HOOKNAME=changegroup
527 changegroup-in-remote hook: HG_HOOKNAME=changegroup
528 HG_HOOKTYPE=changegroup
528 HG_HOOKTYPE=changegroup
529 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
529 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
530 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
530 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
531 HG_SOURCE=serve
531 HG_SOURCE=serve
532 HG_TXNID=TXN:$ID$
532 HG_TXNID=TXN:$ID$
533 HG_TXNNAME=serve
533 HG_TXNNAME=serve
534 remote:ssh:$LOCALIP
534 remote:ssh:$LOCALIP
535 HG_URL=remote:ssh:$LOCALIP
535 HG_URL=remote:ssh:$LOCALIP
536
536
537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
537 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
538 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
538 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
539 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
540 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
541 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
541 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
542 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
542 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
543 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
543 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
544 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
544 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
545 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
545 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
546 changegroup-in-remote hook: HG_HOOKNAME=changegroup
546 changegroup-in-remote hook: HG_HOOKNAME=changegroup
547 HG_HOOKTYPE=changegroup
547 HG_HOOKTYPE=changegroup
548 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6
548 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6
549 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6
549 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6
550 HG_SOURCE=serve
550 HG_SOURCE=serve
551 HG_TXNID=TXN:$ID$
551 HG_TXNID=TXN:$ID$
552 HG_TXNNAME=serve
552 HG_TXNNAME=serve
553 remote:ssh:$LOCALIP
553 remote:ssh:$LOCALIP
554 HG_URL=remote:ssh:$LOCALIP
554 HG_URL=remote:ssh:$LOCALIP
555
555
556 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
556 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
557 Got arguments 1:user@dummy 2:hg init 'a repo'
557 Got arguments 1:user@dummy 2:hg init 'a repo'
558 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
558 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
559 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
559 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
560 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
560 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
561 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
561 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
562 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
562 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
563 changegroup-in-remote hook: HG_HOOKNAME=changegroup
563 changegroup-in-remote hook: HG_HOOKNAME=changegroup
564 HG_HOOKTYPE=changegroup
564 HG_HOOKTYPE=changegroup
565 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8
565 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8
566 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8
566 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8
567 HG_SOURCE=serve
567 HG_SOURCE=serve
568 HG_TXNID=TXN:$ID$
568 HG_TXNID=TXN:$ID$
569 HG_TXNNAME=serve
569 HG_TXNNAME=serve
570 remote:ssh:$LOCALIP
570 remote:ssh:$LOCALIP
571 HG_URL=remote:ssh:$LOCALIP
571 HG_URL=remote:ssh:$LOCALIP
572
572
573 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
573 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
574
574
575 remote hook failure is attributed to remote
575 remote hook failure is attributed to remote
576
576
577 $ cat > $TESTTMP/failhook << EOF
577 $ cat > $TESTTMP/failhook << EOF
578 > def hook(ui, repo, **kwargs):
578 > def hook(ui, repo, **kwargs):
579 > ui.write(b'hook failure!\n')
579 > ui.write(b'hook failure!\n')
580 > ui.flush()
580 > ui.flush()
581 > return 1
581 > return 1
582 > EOF
582 > EOF
583
583
584 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
584 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
585
585
586 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
586 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
587 $ cd hookout
587 $ cd hookout
588 $ touch hookfailure
588 $ touch hookfailure
589 $ hg -q commit -A -m 'remote hook failure'
589 $ hg -q commit -A -m 'remote hook failure'
590 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
590 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
591 pushing to ssh://user@dummy/remote
591 pushing to ssh://user@dummy/remote
592 searching for changes
592 searching for changes
593 remote: adding changesets
593 remote: adding changesets
594 remote: adding manifests
594 remote: adding manifests
595 remote: adding file changes
595 remote: adding file changes
596 remote: hook failure!
596 remote: hook failure!
597 remote: transaction abort!
597 remote: transaction abort!
598 remote: rollback completed
598 remote: rollback completed
599 remote: abort: pretxnchangegroup.fail hook failed
599 remote: abort: pretxnchangegroup.fail hook failed
600 [1]
600 [1]
601
601
602 abort during pull is properly reported as such
602 abort during pull is properly reported as such
603
603
604 $ echo morefoo >> ../remote/foo
604 $ echo morefoo >> ../remote/foo
605 $ hg -R ../remote commit --message "more foo to be pulled"
605 $ hg -R ../remote commit --message "more foo to be pulled"
606 $ cat >> ../remote/.hg/hgrc << EOF
606 $ cat >> ../remote/.hg/hgrc << EOF
607 > [extensions]
607 > [extensions]
608 > crash = ${TESTDIR}/crashgetbundler.py
608 > crash = ${TESTDIR}/crashgetbundler.py
609 > EOF
609 > EOF
610 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
610 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
611 pulling from ssh://user@dummy/remote
611 pulling from ssh://user@dummy/remote
612 searching for changes
612 searching for changes
613 adding changesets
613 adding changesets
614 remote: abort: this is an exercise
614 remote: abort: this is an exercise
615 transaction abort!
615 transaction abort!
616 rollback completed
616 rollback completed
617 abort: stream ended unexpectedly (got 0 bytes, expected 4)
617 abort: stream ended unexpectedly (got 0 bytes, expected 4)
618 [255]
618 [255]
@@ -1,220 +1,220 b''
1 This test tries to exercise the ssh functionality with a dummy script
1 This test tries to exercise the ssh functionality with a dummy script
2
2
3 #testcases sshv1 sshv2
3 #testcases sshv1 sshv2
4
4
5 #if sshv2
5 #if sshv2
6 $ cat >> $HGRCPATH << EOF
6 $ cat >> $HGRCPATH << EOF
7 > [experimental]
7 > [experimental]
8 > sshpeer.advertise-v2 = true
8 > sshpeer.advertise-v2 = true
9 > sshserver.support-v2 = true
9 > sshserver.support-v2 = true
10 > EOF
10 > EOF
11 #endif
11 #endif
12
12
13 creating 'remote' repo
13 creating 'remote' repo
14
14
15 $ hg init remote
15 $ hg init remote
16 $ cd remote
16 $ cd remote
17 $ hg unbundle "$TESTDIR/bundles/remote.hg"
17 $ hg unbundle "$TESTDIR/bundles/remote.hg"
18 adding changesets
18 adding changesets
19 adding manifests
19 adding manifests
20 adding file changes
20 adding file changes
21 added 9 changesets with 7 changes to 4 files (+1 heads)
21 added 9 changesets with 7 changes to 4 files (+1 heads)
22 new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
22 new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
23 (run 'hg heads' to see heads, 'hg merge' to merge)
23 (run 'hg heads' to see heads, 'hg merge' to merge)
24 $ hg up tip
24 $ hg up tip
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 $ cd ..
26 $ cd ..
27
27
28 clone remote via stream
28 clone remote via stream
29
29
30 $ for i in 0 1 2 3 4 5 6 7 8; do
30 $ for i in 0 1 2 3 4 5 6 7 8; do
31 > hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream -r "$i" ssh://user@dummy/remote test-"$i"
31 > hg clone --stream -r "$i" ssh://user@dummy/remote test-"$i"
32 > if cd test-"$i"; then
32 > if cd test-"$i"; then
33 > hg verify
33 > hg verify
34 > cd ..
34 > cd ..
35 > fi
35 > fi
36 > done
36 > done
37 adding changesets
37 adding changesets
38 adding manifests
38 adding manifests
39 adding file changes
39 adding file changes
40 added 1 changesets with 1 changes to 1 files
40 added 1 changesets with 1 changes to 1 files
41 new changesets bfaf4b5cbf01
41 new changesets bfaf4b5cbf01
42 updating to branch default
42 updating to branch default
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 checking changesets
44 checking changesets
45 checking manifests
45 checking manifests
46 crosschecking files in changesets and manifests
46 crosschecking files in changesets and manifests
47 checking files
47 checking files
48 checked 1 changesets with 1 changes to 1 files
48 checked 1 changesets with 1 changes to 1 files
49 adding changesets
49 adding changesets
50 adding manifests
50 adding manifests
51 adding file changes
51 adding file changes
52 added 2 changesets with 2 changes to 1 files
52 added 2 changesets with 2 changes to 1 files
53 new changesets bfaf4b5cbf01:21f32785131f
53 new changesets bfaf4b5cbf01:21f32785131f
54 updating to branch default
54 updating to branch default
55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 checking changesets
56 checking changesets
57 checking manifests
57 checking manifests
58 crosschecking files in changesets and manifests
58 crosschecking files in changesets and manifests
59 checking files
59 checking files
60 checked 2 changesets with 2 changes to 1 files
60 checked 2 changesets with 2 changes to 1 files
61 adding changesets
61 adding changesets
62 adding manifests
62 adding manifests
63 adding file changes
63 adding file changes
64 added 3 changesets with 3 changes to 1 files
64 added 3 changesets with 3 changes to 1 files
65 new changesets bfaf4b5cbf01:4ce51a113780
65 new changesets bfaf4b5cbf01:4ce51a113780
66 updating to branch default
66 updating to branch default
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 checking changesets
68 checking changesets
69 checking manifests
69 checking manifests
70 crosschecking files in changesets and manifests
70 crosschecking files in changesets and manifests
71 checking files
71 checking files
72 checked 3 changesets with 3 changes to 1 files
72 checked 3 changesets with 3 changes to 1 files
73 adding changesets
73 adding changesets
74 adding manifests
74 adding manifests
75 adding file changes
75 adding file changes
76 added 4 changesets with 4 changes to 1 files
76 added 4 changesets with 4 changes to 1 files
77 new changesets bfaf4b5cbf01:93ee6ab32777
77 new changesets bfaf4b5cbf01:93ee6ab32777
78 updating to branch default
78 updating to branch default
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
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 checked 4 changesets with 4 changes to 1 files
84 checked 4 changesets with 4 changes to 1 files
85 adding changesets
85 adding changesets
86 adding manifests
86 adding manifests
87 adding file changes
87 adding file changes
88 added 2 changesets with 2 changes to 1 files
88 added 2 changesets with 2 changes to 1 files
89 new changesets bfaf4b5cbf01:c70afb1ee985
89 new changesets bfaf4b5cbf01:c70afb1ee985
90 updating to branch default
90 updating to branch default
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 checking changesets
92 checking changesets
93 checking manifests
93 checking manifests
94 crosschecking files in changesets and manifests
94 crosschecking files in changesets and manifests
95 checking files
95 checking files
96 checked 2 changesets with 2 changes to 1 files
96 checked 2 changesets with 2 changes to 1 files
97 adding changesets
97 adding changesets
98 adding manifests
98 adding manifests
99 adding file changes
99 adding file changes
100 added 3 changesets with 3 changes to 1 files
100 added 3 changesets with 3 changes to 1 files
101 new changesets bfaf4b5cbf01:f03ae5a9b979
101 new changesets bfaf4b5cbf01:f03ae5a9b979
102 updating to branch default
102 updating to branch default
103 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
103 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
104 checking changesets
104 checking changesets
105 checking manifests
105 checking manifests
106 crosschecking files in changesets and manifests
106 crosschecking files in changesets and manifests
107 checking files
107 checking files
108 checked 3 changesets with 3 changes to 1 files
108 checked 3 changesets with 3 changes to 1 files
109 adding changesets
109 adding changesets
110 adding manifests
110 adding manifests
111 adding file changes
111 adding file changes
112 added 4 changesets with 5 changes to 2 files
112 added 4 changesets with 5 changes to 2 files
113 new changesets bfaf4b5cbf01:095cb14b1b4d
113 new changesets bfaf4b5cbf01:095cb14b1b4d
114 updating to branch default
114 updating to branch default
115 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 checking changesets
116 checking changesets
117 checking manifests
117 checking manifests
118 crosschecking files in changesets and manifests
118 crosschecking files in changesets and manifests
119 checking files
119 checking files
120 checked 4 changesets with 5 changes to 2 files
120 checked 4 changesets with 5 changes to 2 files
121 adding changesets
121 adding changesets
122 adding manifests
122 adding manifests
123 adding file changes
123 adding file changes
124 added 5 changesets with 6 changes to 3 files
124 added 5 changesets with 6 changes to 3 files
125 new changesets bfaf4b5cbf01:faa2e4234c7a
125 new changesets bfaf4b5cbf01:faa2e4234c7a
126 updating to branch default
126 updating to branch default
127 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 checking changesets
128 checking changesets
129 checking manifests
129 checking manifests
130 crosschecking files in changesets and manifests
130 crosschecking files in changesets and manifests
131 checking files
131 checking files
132 checked 5 changesets with 6 changes to 3 files
132 checked 5 changesets with 6 changes to 3 files
133 adding changesets
133 adding changesets
134 adding manifests
134 adding manifests
135 adding file changes
135 adding file changes
136 added 5 changesets with 5 changes to 2 files
136 added 5 changesets with 5 changes to 2 files
137 new changesets bfaf4b5cbf01:916f1afdef90
137 new changesets bfaf4b5cbf01:916f1afdef90
138 updating to branch default
138 updating to branch default
139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 checking changesets
140 checking changesets
141 checking manifests
141 checking manifests
142 crosschecking files in changesets and manifests
142 crosschecking files in changesets and manifests
143 checking files
143 checking files
144 checked 5 changesets with 5 changes to 2 files
144 checked 5 changesets with 5 changes to 2 files
145 $ cd test-8
145 $ cd test-8
146 $ hg pull ../test-7
146 $ hg pull ../test-7
147 pulling from ../test-7
147 pulling from ../test-7
148 searching for changes
148 searching for changes
149 adding changesets
149 adding changesets
150 adding manifests
150 adding manifests
151 adding file changes
151 adding file changes
152 added 4 changesets with 2 changes to 3 files (+1 heads)
152 added 4 changesets with 2 changes to 3 files (+1 heads)
153 new changesets c70afb1ee985:faa2e4234c7a
153 new changesets c70afb1ee985:faa2e4234c7a
154 (run 'hg heads' to see heads, 'hg merge' to merge)
154 (run 'hg heads' to see heads, 'hg merge' to merge)
155 $ hg verify
155 $ hg verify
156 checking changesets
156 checking changesets
157 checking manifests
157 checking manifests
158 crosschecking files in changesets and manifests
158 crosschecking files in changesets and manifests
159 checking files
159 checking files
160 checked 9 changesets with 7 changes to 4 files
160 checked 9 changesets with 7 changes to 4 files
161 $ cd ..
161 $ cd ..
162 $ cd test-1
162 $ cd test-1
163 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -r 4 ssh://user@dummy/remote
163 $ hg pull -r 4 ssh://user@dummy/remote
164 pulling from ssh://user@dummy/remote
164 pulling from ssh://user@dummy/remote
165 searching for changes
165 searching for 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 0 changes to 0 files (+1 heads)
169 added 1 changesets with 0 changes to 0 files (+1 heads)
170 new changesets c70afb1ee985
170 new changesets c70afb1ee985
171 (run 'hg heads' to see heads, 'hg merge' to merge)
171 (run 'hg heads' to see heads, 'hg merge' to merge)
172 $ hg verify
172 $ hg verify
173 checking changesets
173 checking changesets
174 checking manifests
174 checking manifests
175 crosschecking files in changesets and manifests
175 crosschecking files in changesets and manifests
176 checking files
176 checking files
177 checked 3 changesets with 2 changes to 1 files
177 checked 3 changesets with 2 changes to 1 files
178 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote
178 $ hg pull ssh://user@dummy/remote
179 pulling from ssh://user@dummy/remote
179 pulling from ssh://user@dummy/remote
180 searching for changes
180 searching for changes
181 adding changesets
181 adding changesets
182 adding manifests
182 adding manifests
183 adding file changes
183 adding file changes
184 added 6 changesets with 5 changes to 4 files
184 added 6 changesets with 5 changes to 4 files
185 new changesets 4ce51a113780:916f1afdef90
185 new changesets 4ce51a113780:916f1afdef90
186 (run 'hg update' to get a working copy)
186 (run 'hg update' to get a working copy)
187 $ cd ..
187 $ cd ..
188 $ cd test-2
188 $ cd test-2
189 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -r 5 ssh://user@dummy/remote
189 $ hg pull -r 5 ssh://user@dummy/remote
190 pulling from ssh://user@dummy/remote
190 pulling from ssh://user@dummy/remote
191 searching for changes
191 searching for changes
192 adding changesets
192 adding changesets
193 adding manifests
193 adding manifests
194 adding file changes
194 adding file changes
195 added 2 changesets with 0 changes to 0 files (+1 heads)
195 added 2 changesets with 0 changes to 0 files (+1 heads)
196 new changesets c70afb1ee985:f03ae5a9b979
196 new changesets c70afb1ee985:f03ae5a9b979
197 (run 'hg heads' to see heads, 'hg merge' to merge)
197 (run 'hg heads' to see heads, 'hg merge' to merge)
198 $ hg verify
198 $ hg verify
199 checking changesets
199 checking changesets
200 checking manifests
200 checking manifests
201 crosschecking files in changesets and manifests
201 crosschecking files in changesets and manifests
202 checking files
202 checking files
203 checked 5 changesets with 3 changes to 1 files
203 checked 5 changesets with 3 changes to 1 files
204 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote
204 $ hg pull ssh://user@dummy/remote
205 pulling from ssh://user@dummy/remote
205 pulling from ssh://user@dummy/remote
206 searching for changes
206 searching for changes
207 adding changesets
207 adding changesets
208 adding manifests
208 adding manifests
209 adding file changes
209 adding file changes
210 added 4 changesets with 4 changes to 4 files
210 added 4 changesets with 4 changes to 4 files
211 new changesets 93ee6ab32777:916f1afdef90
211 new changesets 93ee6ab32777:916f1afdef90
212 (run 'hg update' to get a working copy)
212 (run 'hg update' to get a working copy)
213 $ hg verify
213 $ hg verify
214 checking changesets
214 checking changesets
215 checking manifests
215 checking manifests
216 crosschecking files in changesets and manifests
216 crosschecking files in changesets and manifests
217 checking files
217 checking files
218 checked 9 changesets with 7 changes to 4 files
218 checked 9 changesets with 7 changes to 4 files
219
219
220 $ cd ..
220 $ cd ..
@@ -1,737 +1,737 b''
1 #testcases sshv1 sshv2
1 #testcases sshv1 sshv2
2
2
3 #if sshv2
3 #if sshv2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [experimental]
5 > [experimental]
6 > sshpeer.advertise-v2 = true
6 > sshpeer.advertise-v2 = true
7 > sshserver.support-v2 = true
7 > sshserver.support-v2 = true
8 > EOF
8 > EOF
9 #endif
9 #endif
10
10
11 This test tries to exercise the ssh functionality with a dummy script
11 This test tries to exercise the ssh functionality with a dummy script
12
12
13 creating 'remote' repo
13 creating 'remote' repo
14
14
15 $ hg init remote
15 $ hg init remote
16 $ cd remote
16 $ cd remote
17 $ echo this > foo
17 $ echo this > foo
18 $ echo this > fooO
18 $ echo this > fooO
19 $ hg ci -A -m "init" foo fooO
19 $ hg ci -A -m "init" foo fooO
20
20
21 insert a closed branch (issue4428)
21 insert a closed branch (issue4428)
22
22
23 $ hg up null
23 $ hg up null
24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
24 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
25 $ hg branch closed
25 $ hg branch closed
26 marked working directory as branch closed
26 marked working directory as branch closed
27 (branches are permanent and global, did you want a bookmark?)
27 (branches are permanent and global, did you want a bookmark?)
28 $ hg ci -mc0
28 $ hg ci -mc0
29 $ hg ci --close-branch -mc1
29 $ hg ci --close-branch -mc1
30 $ hg up -q default
30 $ hg up -q default
31
31
32 configure for serving
32 configure for serving
33
33
34 $ cat <<EOF > .hg/hgrc
34 $ cat <<EOF > .hg/hgrc
35 > [server]
35 > [server]
36 > uncompressed = True
36 > uncompressed = True
37 >
37 >
38 > [hooks]
38 > [hooks]
39 > changegroup = sh -c "printenv.py --line changegroup-in-remote 0 ../dummylog"
39 > changegroup = sh -c "printenv.py --line changegroup-in-remote 0 ../dummylog"
40 > EOF
40 > EOF
41 $ cd $TESTTMP
41 $ cd $TESTTMP
42
42
43 repo not found error
43 repo not found error
44
44
45 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
45 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
46 remote: abort: repository nonexistent not found
46 remote: abort: repository nonexistent not found
47 abort: no suitable response from remote hg
47 abort: no suitable response from remote hg
48 [255]
48 [255]
49 $ hg clone -q -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
49 $ hg clone -q -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
50 remote: abort: repository nonexistent not found
50 remote: abort: repository nonexistent not found
51 abort: no suitable response from remote hg
51 abort: no suitable response from remote hg
52 [255]
52 [255]
53
53
54 non-existent absolute path
54 non-existent absolute path
55
55
56 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
56 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/nonexistent local
57 remote: abort: repository $TESTTMP/nonexistent not found
57 remote: abort: repository $TESTTMP/nonexistent not found
58 abort: no suitable response from remote hg
58 abort: no suitable response from remote hg
59 [255]
59 [255]
60
60
61 clone remote via stream
61 clone remote via stream
62
62
63 #if no-reposimplestore
63 #if no-reposimplestore
64
64
65 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
65 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
66 streaming all changes
66 streaming all changes
67 8 files to transfer, 827 bytes of data (no-zstd !)
67 8 files to transfer, 827 bytes of data (no-zstd !)
68 transferred 827 bytes in * seconds (*) (glob) (no-zstd !)
68 transferred 827 bytes in * seconds (*) (glob) (no-zstd !)
69 8 files to transfer, 846 bytes of data (zstd !)
69 8 files to transfer, 846 bytes of data (zstd !)
70 transferred * bytes in * seconds (* */sec) (glob) (zstd !)
70 transferred * bytes in * seconds (* */sec) (glob) (zstd !)
71 updating to branch default
71 updating to branch default
72 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
73 $ cd local-stream
73 $ cd local-stream
74 $ hg verify
74 $ hg verify
75 checking changesets
75 checking changesets
76 checking manifests
76 checking manifests
77 crosschecking files in changesets and manifests
77 crosschecking files in changesets and manifests
78 checking files
78 checking files
79 checked 3 changesets with 2 changes to 2 files
79 checked 3 changesets with 2 changes to 2 files
80 $ hg branches
80 $ hg branches
81 default 0:1160648e36ce
81 default 0:1160648e36ce
82 $ cd $TESTTMP
82 $ cd $TESTTMP
83
83
84 clone bookmarks via stream
84 clone bookmarks via stream
85
85
86 $ hg -R local-stream book mybook
86 $ hg -R local-stream book mybook
87 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
87 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
88 streaming all changes
88 streaming all changes
89 15 files to transfer, * of data (glob)
89 15 files to transfer, * of data (glob)
90 transferred * in * seconds (*) (glob)
90 transferred * in * seconds (*) (glob)
91 updating to branch default
91 updating to branch default
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ cd stream2
93 $ cd stream2
94 $ hg book
94 $ hg book
95 mybook 0:1160648e36ce
95 mybook 0:1160648e36ce
96 $ cd $TESTTMP
96 $ cd $TESTTMP
97 $ rm -rf local-stream stream2
97 $ rm -rf local-stream stream2
98
98
99 #endif
99 #endif
100
100
101 clone remote via pull
101 clone remote via pull
102
102
103 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
103 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
104 requesting all changes
104 requesting all changes
105 adding changesets
105 adding changesets
106 adding manifests
106 adding manifests
107 adding file changes
107 adding file changes
108 added 3 changesets with 2 changes to 2 files
108 added 3 changesets with 2 changes to 2 files
109 new changesets 1160648e36ce:ad076bfb429d
109 new changesets 1160648e36ce:ad076bfb429d
110 updating to branch default
110 updating to branch default
111 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
112
112
113 verify
113 verify
114
114
115 $ cd local
115 $ cd local
116 $ hg verify
116 $ hg verify
117 checking changesets
117 checking changesets
118 checking manifests
118 checking manifests
119 crosschecking files in changesets and manifests
119 crosschecking files in changesets and manifests
120 checking files
120 checking files
121 checked 3 changesets with 2 changes to 2 files
121 checked 3 changesets with 2 changes to 2 files
122 $ cat >> .hg/hgrc <<EOF
122 $ cat >> .hg/hgrc <<EOF
123 > [hooks]
123 > [hooks]
124 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
124 > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
125 > EOF
125 > EOF
126
126
127 empty default pull
127 empty default pull
128
128
129 $ hg paths
129 $ hg paths
130 default = ssh://user@dummy/remote
130 default = ssh://user@dummy/remote
131 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
131 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
132 pulling from ssh://user@dummy/remote
132 pulling from ssh://user@dummy/remote
133 searching for changes
133 searching for changes
134 no changes found
134 no changes found
135
135
136 pull from wrong ssh URL
136 pull from wrong ssh URL
137
137
138 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
138 $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
139 pulling from ssh://user@dummy/doesnotexist
139 pulling from ssh://user@dummy/doesnotexist
140 remote: abort: repository doesnotexist not found
140 remote: abort: repository doesnotexist not found
141 abort: no suitable response from remote hg
141 abort: no suitable response from remote hg
142 [255]
142 [255]
143
143
144 local change
144 local change
145
145
146 $ echo bleah > foo
146 $ echo bleah > foo
147 $ hg ci -m "add"
147 $ hg ci -m "add"
148
148
149 updating rc
149 updating rc
150
150
151 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
151 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
152 $ echo "[ui]" >> .hg/hgrc
152 $ echo "[ui]" >> .hg/hgrc
153 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
153 $ echo "ssh = \"$PYTHON\" \"$TESTDIR/dummyssh\"" >> .hg/hgrc
154
154
155 find outgoing
155 find outgoing
156
156
157 $ hg out ssh://user@dummy/remote
157 $ hg out ssh://user@dummy/remote
158 comparing with ssh://user@dummy/remote
158 comparing with ssh://user@dummy/remote
159 searching for changes
159 searching for changes
160 changeset: 3:a28a9d1a809c
160 changeset: 3:a28a9d1a809c
161 tag: tip
161 tag: tip
162 parent: 0:1160648e36ce
162 parent: 0:1160648e36ce
163 user: test
163 user: test
164 date: Thu Jan 01 00:00:00 1970 +0000
164 date: Thu Jan 01 00:00:00 1970 +0000
165 summary: add
165 summary: add
166
166
167
167
168 find incoming on the remote side
168 find incoming on the remote side
169
169
170 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
170 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
171 comparing with ssh://user@dummy/local
171 comparing with ssh://user@dummy/local
172 searching for changes
172 searching for changes
173 changeset: 3:a28a9d1a809c
173 changeset: 3:a28a9d1a809c
174 tag: tip
174 tag: tip
175 parent: 0:1160648e36ce
175 parent: 0:1160648e36ce
176 user: test
176 user: test
177 date: Thu Jan 01 00:00:00 1970 +0000
177 date: Thu Jan 01 00:00:00 1970 +0000
178 summary: add
178 summary: add
179
179
180
180
181 find incoming on the remote side (using absolute path)
181 find incoming on the remote side (using absolute path)
182
182
183 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
183 $ hg incoming -R ../remote -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
184 comparing with ssh://user@dummy/$TESTTMP/local
184 comparing with ssh://user@dummy/$TESTTMP/local
185 searching for changes
185 searching for changes
186 changeset: 3:a28a9d1a809c
186 changeset: 3:a28a9d1a809c
187 tag: tip
187 tag: tip
188 parent: 0:1160648e36ce
188 parent: 0:1160648e36ce
189 user: test
189 user: test
190 date: Thu Jan 01 00:00:00 1970 +0000
190 date: Thu Jan 01 00:00:00 1970 +0000
191 summary: add
191 summary: add
192
192
193
193
194 push
194 push
195
195
196 $ hg push
196 $ hg push
197 pushing to ssh://user@dummy/remote
197 pushing to ssh://user@dummy/remote
198 searching for changes
198 searching for changes
199 remote: adding changesets
199 remote: adding changesets
200 remote: adding manifests
200 remote: adding manifests
201 remote: adding file changes
201 remote: adding file changes
202 remote: added 1 changesets with 1 changes to 1 files
202 remote: added 1 changesets with 1 changes to 1 files
203 $ cd $TESTTMP/remote
203 $ cd $TESTTMP/remote
204
204
205 check remote tip
205 check remote tip
206
206
207 $ hg tip
207 $ hg tip
208 changeset: 3:a28a9d1a809c
208 changeset: 3:a28a9d1a809c
209 tag: tip
209 tag: tip
210 parent: 0:1160648e36ce
210 parent: 0:1160648e36ce
211 user: test
211 user: test
212 date: Thu Jan 01 00:00:00 1970 +0000
212 date: Thu Jan 01 00:00:00 1970 +0000
213 summary: add
213 summary: add
214
214
215 $ hg verify
215 $ hg verify
216 checking changesets
216 checking changesets
217 checking manifests
217 checking manifests
218 crosschecking files in changesets and manifests
218 crosschecking files in changesets and manifests
219 checking files
219 checking files
220 checked 4 changesets with 3 changes to 2 files
220 checked 4 changesets with 3 changes to 2 files
221 $ hg cat -r tip foo
221 $ hg cat -r tip foo
222 bleah
222 bleah
223 $ echo z > z
223 $ echo z > z
224 $ hg ci -A -m z z
224 $ hg ci -A -m z z
225 created new head
225 created new head
226
226
227 test pushkeys and bookmarks
227 test pushkeys and bookmarks
228
228
229 $ cd $TESTTMP/local
229 $ cd $TESTTMP/local
230 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
230 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
231 bookmarks
231 bookmarks
232 namespaces
232 namespaces
233 phases
233 phases
234 $ hg book foo -r 0
234 $ hg book foo -r 0
235 $ hg out -B --config paths.default=bogus://invalid --config paths.default:pushurl=`hg paths default`
235 $ hg out -B --config paths.default=bogus://invalid --config paths.default:pushurl=`hg paths default`
236 comparing with ssh://user@dummy/remote
236 comparing with ssh://user@dummy/remote
237 searching for changed bookmarks
237 searching for changed bookmarks
238 foo 1160648e36ce
238 foo 1160648e36ce
239 $ hg push -B foo
239 $ hg push -B foo
240 pushing to ssh://user@dummy/remote
240 pushing to ssh://user@dummy/remote
241 searching for changes
241 searching for changes
242 no changes found
242 no changes found
243 exporting bookmark foo
243 exporting bookmark foo
244 [1]
244 [1]
245 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
245 $ hg debugpushkey --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
246 foo 1160648e36cec0054048a7edc4110c6f84fde594
246 foo 1160648e36cec0054048a7edc4110c6f84fde594
247 $ hg book -f foo
247 $ hg book -f foo
248 $ hg push --traceback
248 $ hg push --traceback
249 pushing to ssh://user@dummy/remote
249 pushing to ssh://user@dummy/remote
250 searching for changes
250 searching for changes
251 no changes found
251 no changes found
252 updating bookmark foo
252 updating bookmark foo
253 [1]
253 [1]
254 $ hg book -d foo
254 $ hg book -d foo
255 $ hg in -B
255 $ hg in -B
256 comparing with ssh://user@dummy/remote
256 comparing with ssh://user@dummy/remote
257 searching for changed bookmarks
257 searching for changed bookmarks
258 foo a28a9d1a809c
258 foo a28a9d1a809c
259 $ hg book -f -r 0 foo
259 $ hg book -f -r 0 foo
260 $ hg pull -B foo
260 $ hg pull -B foo
261 pulling from ssh://user@dummy/remote
261 pulling from ssh://user@dummy/remote
262 no changes found
262 no changes found
263 updating bookmark foo
263 updating bookmark foo
264 $ hg book -d foo
264 $ hg book -d foo
265 $ hg push -B foo
265 $ hg push -B foo
266 pushing to ssh://user@dummy/remote
266 pushing to ssh://user@dummy/remote
267 searching for changes
267 searching for changes
268 no changes found
268 no changes found
269 deleting remote bookmark foo
269 deleting remote bookmark foo
270 [1]
270 [1]
271
271
272 a bad, evil hook that prints to stdout
272 a bad, evil hook that prints to stdout
273
273
274 $ cat <<EOF > $TESTTMP/badhook
274 $ cat <<EOF > $TESTTMP/badhook
275 > import sys
275 > import sys
276 > sys.stdout.write("KABOOM\n")
276 > sys.stdout.write("KABOOM\n")
277 > sys.stdout.flush()
277 > sys.stdout.flush()
278 > EOF
278 > EOF
279
279
280 $ cat <<EOF > $TESTTMP/badpyhook.py
280 $ cat <<EOF > $TESTTMP/badpyhook.py
281 > import sys
281 > import sys
282 > def hook(ui, repo, hooktype, **kwargs):
282 > def hook(ui, repo, hooktype, **kwargs):
283 > sys.stdout.write("KABOOM IN PROCESS\n")
283 > sys.stdout.write("KABOOM IN PROCESS\n")
284 > sys.stdout.flush()
284 > sys.stdout.flush()
285 > EOF
285 > EOF
286
286
287 $ cat <<EOF >> ../remote/.hg/hgrc
287 $ cat <<EOF >> ../remote/.hg/hgrc
288 > [hooks]
288 > [hooks]
289 > changegroup.stdout = "$PYTHON" $TESTTMP/badhook
289 > changegroup.stdout = "$PYTHON" $TESTTMP/badhook
290 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
290 > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
291 > EOF
291 > EOF
292 $ echo r > r
292 $ echo r > r
293 $ hg ci -A -m z r
293 $ hg ci -A -m z r
294
294
295 push should succeed even though it has an unexpected response
295 push should succeed even though it has an unexpected response
296
296
297 $ hg push
297 $ hg push
298 pushing to ssh://user@dummy/remote
298 pushing to ssh://user@dummy/remote
299 searching for changes
299 searching for changes
300 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
300 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
301 remote: adding changesets
301 remote: adding changesets
302 remote: adding manifests
302 remote: adding manifests
303 remote: adding file changes
303 remote: adding file changes
304 remote: added 1 changesets with 1 changes to 1 files (py3 !)
304 remote: added 1 changesets with 1 changes to 1 files (py3 !)
305 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
305 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
306 remote: KABOOM
306 remote: KABOOM
307 remote: KABOOM IN PROCESS
307 remote: KABOOM IN PROCESS
308 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
308 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
309 $ hg -R ../remote heads
309 $ hg -R ../remote heads
310 changeset: 5:1383141674ec
310 changeset: 5:1383141674ec
311 tag: tip
311 tag: tip
312 parent: 3:a28a9d1a809c
312 parent: 3:a28a9d1a809c
313 user: test
313 user: test
314 date: Thu Jan 01 00:00:00 1970 +0000
314 date: Thu Jan 01 00:00:00 1970 +0000
315 summary: z
315 summary: z
316
316
317 changeset: 4:6c0482d977a3
317 changeset: 4:6c0482d977a3
318 parent: 0:1160648e36ce
318 parent: 0:1160648e36ce
319 user: test
319 user: test
320 date: Thu Jan 01 00:00:00 1970 +0000
320 date: Thu Jan 01 00:00:00 1970 +0000
321 summary: z
321 summary: z
322
322
323
323
324 #if chg
324 #if chg
325
325
326 try again with remote chg, which should succeed as well
326 try again with remote chg, which should succeed as well
327
327
328 $ hg rollback -R ../remote
328 $ hg rollback -R ../remote
329 repository tip rolled back to revision 4 (undo serve)
329 repository tip rolled back to revision 4 (undo serve)
330
330
331 $ hg push --config ui.remotecmd=chg
331 $ hg push --config ui.remotecmd=chg
332 pushing to ssh://user@dummy/remote
332 pushing to ssh://user@dummy/remote
333 searching for changes
333 searching for changes
334 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
334 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
335 remote: adding changesets
335 remote: adding changesets
336 remote: adding manifests
336 remote: adding manifests
337 remote: adding file changes
337 remote: adding file changes
338 remote: added 1 changesets with 1 changes to 1 files (py3 !)
338 remote: added 1 changesets with 1 changes to 1 files (py3 !)
339 remote: KABOOM
339 remote: KABOOM
340 remote: KABOOM IN PROCESS
340 remote: KABOOM IN PROCESS
341 remote: added 1 changesets with 1 changes to 1 files (no-py3 !)
341 remote: added 1 changesets with 1 changes to 1 files (no-py3 !)
342
342
343 #endif
343 #endif
344
344
345 clone bookmarks
345 clone bookmarks
346
346
347 $ hg -R ../remote bookmark test
347 $ hg -R ../remote bookmark test
348 $ hg -R ../remote bookmarks
348 $ hg -R ../remote bookmarks
349 * test 4:6c0482d977a3
349 * test 4:6c0482d977a3
350 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
350 $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
351 requesting all changes
351 requesting all changes
352 adding changesets
352 adding changesets
353 adding manifests
353 adding manifests
354 adding file changes
354 adding file changes
355 added 6 changesets with 5 changes to 4 files (+1 heads)
355 added 6 changesets with 5 changes to 4 files (+1 heads)
356 new changesets 1160648e36ce:1383141674ec
356 new changesets 1160648e36ce:1383141674ec
357 updating to branch default
357 updating to branch default
358 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 $ hg -R local-bookmarks bookmarks
359 $ hg -R local-bookmarks bookmarks
360 test 4:6c0482d977a3
360 test 4:6c0482d977a3
361
361
362 passwords in ssh urls are not supported
362 passwords in ssh urls are not supported
363 (we use a glob here because different Python versions give different
363 (we use a glob here because different Python versions give different
364 results here)
364 results here)
365
365
366 $ hg push ssh://user:erroneouspwd@dummy/remote
366 $ hg push ssh://user:erroneouspwd@dummy/remote
367 pushing to ssh://user:*@dummy/remote (glob)
367 pushing to ssh://user:*@dummy/remote (glob)
368 abort: password in URL not supported
368 abort: password in URL not supported
369 [255]
369 [255]
370
370
371 $ cd $TESTTMP
371 $ cd $TESTTMP
372
372
373 hide outer repo
373 hide outer repo
374 $ hg init
374 $ hg init
375
375
376 Test remote paths with spaces (issue2983):
376 Test remote paths with spaces (issue2983):
377
377
378 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
378 $ hg init --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
379 $ touch "$TESTTMP/a repo/test"
379 $ touch "$TESTTMP/a repo/test"
380 $ hg -R 'a repo' commit -A -m "test"
380 $ hg -R 'a repo' commit -A -m "test"
381 adding test
381 adding test
382 $ hg -R 'a repo' tag tag
382 $ hg -R 'a repo' tag tag
383 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
383 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
384 73649e48688a
384 73649e48688a
385
385
386 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
386 $ hg id --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
387 abort: unknown revision 'noNoNO'
387 abort: unknown revision 'noNoNO'
388 [255]
388 [255]
389
389
390 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
390 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
391
391
392 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
392 $ hg clone --ssh "\"$PYTHON\" \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
393 destination directory: a repo
393 destination directory: a repo
394 abort: destination 'a repo' is not empty
394 abort: destination 'a repo' is not empty
395 [10]
395 [10]
396
396
397 #if no-rhg
397 #if no-rhg
398 Make sure hg is really paranoid in serve --stdio mode. It used to be
398 Make sure hg is really paranoid in serve --stdio mode. It used to be
399 possible to get a debugger REPL by specifying a repo named --debugger.
399 possible to get a debugger REPL by specifying a repo named --debugger.
400 $ hg -R --debugger serve --stdio
400 $ hg -R --debugger serve --stdio
401 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
401 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
402 [255]
402 [255]
403 $ hg -R --config=ui.debugger=yes serve --stdio
403 $ hg -R --config=ui.debugger=yes serve --stdio
404 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
404 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
405 [255]
405 [255]
406 Abbreviations of 'serve' also don't work, to avoid shenanigans.
406 Abbreviations of 'serve' also don't work, to avoid shenanigans.
407 $ hg -R narf serv --stdio
407 $ hg -R narf serv --stdio
408 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
408 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
409 [255]
409 [255]
410 #else
410 #else
411 rhg aborts early on -R without a repository at that path
411 rhg aborts early on -R without a repository at that path
412 $ hg -R --debugger serve --stdio
412 $ hg -R --debugger serve --stdio
413 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio'] (missing-correct-output !)
413 abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio'] (missing-correct-output !)
414 abort: repository --debugger not found (known-bad-output !)
414 abort: repository --debugger not found (known-bad-output !)
415 [255]
415 [255]
416 $ hg -R --config=ui.debugger=yes serve --stdio
416 $ hg -R --config=ui.debugger=yes serve --stdio
417 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio'] (missing-correct-output !)
417 abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio'] (missing-correct-output !)
418 abort: repository --config=ui.debugger=yes not found (known-bad-output !)
418 abort: repository --config=ui.debugger=yes not found (known-bad-output !)
419 [255]
419 [255]
420 $ hg -R narf serv --stdio
420 $ hg -R narf serv --stdio
421 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] (missing-correct-output !)
421 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio'] (missing-correct-output !)
422 abort: repository narf not found (known-bad-output !)
422 abort: repository narf not found (known-bad-output !)
423 [255]
423 [255]
424 If the repo does exist, rhg finds an unsupported command and falls back to Python
424 If the repo does exist, rhg finds an unsupported command and falls back to Python
425 which still does the right thing
425 which still does the right thing
426 $ hg init narf
426 $ hg init narf
427 $ hg -R narf serv --stdio
427 $ hg -R narf serv --stdio
428 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
428 abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
429 [255]
429 [255]
430 #endif
430 #endif
431
431
432 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
432 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
433 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
433 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
434 parameters:
434 parameters:
435
435
436 $ cat > ssh.sh << EOF
436 $ cat > ssh.sh << EOF
437 > userhost="\$1"
437 > userhost="\$1"
438 > SSH_ORIGINAL_COMMAND="\$2"
438 > SSH_ORIGINAL_COMMAND="\$2"
439 > export SSH_ORIGINAL_COMMAND
439 > export SSH_ORIGINAL_COMMAND
440 > PYTHONPATH="$PYTHONPATH"
440 > PYTHONPATH="$PYTHONPATH"
441 > export PYTHONPATH
441 > export PYTHONPATH
442 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
442 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
443 > EOF
443 > EOF
444
444
445 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
445 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
446 73649e48688a
446 73649e48688a
447
447
448 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
448 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
449 remote: Illegal repository "$TESTTMP/a'repo"
449 remote: Illegal repository "$TESTTMP/a'repo"
450 abort: no suitable response from remote hg
450 abort: no suitable response from remote hg
451 [255]
451 [255]
452
452
453 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
453 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
454 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
454 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
455 abort: no suitable response from remote hg
455 abort: no suitable response from remote hg
456 [255]
456 [255]
457
457
458 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
458 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
459 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
459 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
460 [255]
460 [255]
461
461
462 Test hg-ssh in read-only mode:
462 Test hg-ssh in read-only mode:
463
463
464 $ cat > ssh.sh << EOF
464 $ cat > ssh.sh << EOF
465 > userhost="\$1"
465 > userhost="\$1"
466 > SSH_ORIGINAL_COMMAND="\$2"
466 > SSH_ORIGINAL_COMMAND="\$2"
467 > export SSH_ORIGINAL_COMMAND
467 > export SSH_ORIGINAL_COMMAND
468 > PYTHONPATH="$PYTHONPATH"
468 > PYTHONPATH="$PYTHONPATH"
469 > export PYTHONPATH
469 > export PYTHONPATH
470 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
470 > "$PYTHON" "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
471 > EOF
471 > EOF
472
472
473 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
473 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
474 requesting all changes
474 requesting all changes
475 adding changesets
475 adding changesets
476 adding manifests
476 adding manifests
477 adding file changes
477 adding file changes
478 added 6 changesets with 5 changes to 4 files (+1 heads)
478 added 6 changesets with 5 changes to 4 files (+1 heads)
479 new changesets 1160648e36ce:1383141674ec
479 new changesets 1160648e36ce:1383141674ec
480 updating to branch default
480 updating to branch default
481 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
481 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
482
482
483 $ cd read-only-local
483 $ cd read-only-local
484 $ echo "baz" > bar
484 $ echo "baz" > bar
485 $ hg ci -A -m "unpushable commit" bar
485 $ hg ci -A -m "unpushable commit" bar
486 $ hg push --ssh "sh ../ssh.sh"
486 $ hg push --ssh "sh ../ssh.sh"
487 pushing to ssh://user@dummy/*/remote (glob)
487 pushing to ssh://user@dummy/*/remote (glob)
488 searching for changes
488 searching for changes
489 remote: Permission denied
489 remote: Permission denied
490 remote: pretxnopen.hg-ssh hook failed
490 remote: pretxnopen.hg-ssh hook failed
491 abort: push failed on remote
491 abort: push failed on remote
492 [100]
492 [100]
493
493
494 $ cd $TESTTMP
494 $ cd $TESTTMP
495
495
496 stderr from remote commands should be printed before stdout from local code (issue4336)
496 stderr from remote commands should be printed before stdout from local code (issue4336)
497
497
498 $ hg clone remote stderr-ordering
498 $ hg clone remote stderr-ordering
499 updating to branch default
499 updating to branch default
500 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 $ cd stderr-ordering
501 $ cd stderr-ordering
502 $ cat >> localwrite.py << EOF
502 $ cat >> localwrite.py << EOF
503 > from mercurial import exchange, extensions
503 > from mercurial import exchange, extensions
504 >
504 >
505 > def wrappedpush(orig, repo, *args, **kwargs):
505 > def wrappedpush(orig, repo, *args, **kwargs):
506 > res = orig(repo, *args, **kwargs)
506 > res = orig(repo, *args, **kwargs)
507 > repo.ui.write(b'local stdout\n')
507 > repo.ui.write(b'local stdout\n')
508 > repo.ui.flush()
508 > repo.ui.flush()
509 > return res
509 > return res
510 >
510 >
511 > def extsetup(ui):
511 > def extsetup(ui):
512 > extensions.wrapfunction(exchange, b'push', wrappedpush)
512 > extensions.wrapfunction(exchange, b'push', wrappedpush)
513 > EOF
513 > EOF
514
514
515 $ cat >> .hg/hgrc << EOF
515 $ cat >> .hg/hgrc << EOF
516 > [paths]
516 > [paths]
517 > default-push = ssh://user@dummy/remote
517 > default-push = ssh://user@dummy/remote
518 > [ui]
518 > [ui]
519 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
519 > ssh = "$PYTHON" "$TESTDIR/dummyssh"
520 > [extensions]
520 > [extensions]
521 > localwrite = localwrite.py
521 > localwrite = localwrite.py
522 > EOF
522 > EOF
523
523
524 $ echo localwrite > foo
524 $ echo localwrite > foo
525 $ hg commit -m 'testing localwrite'
525 $ hg commit -m 'testing localwrite'
526 $ hg push
526 $ hg push
527 pushing to ssh://user@dummy/remote
527 pushing to ssh://user@dummy/remote
528 searching for changes
528 searching for changes
529 remote: adding changesets
529 remote: adding changesets
530 remote: adding manifests
530 remote: adding manifests
531 remote: adding file changes
531 remote: adding file changes
532 remote: added 1 changesets with 1 changes to 1 files (py3 !)
532 remote: added 1 changesets with 1 changes to 1 files (py3 !)
533 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
533 remote: added 1 changesets with 1 changes to 1 files (no-py3 no-chg !)
534 remote: KABOOM
534 remote: KABOOM
535 remote: KABOOM IN PROCESS
535 remote: KABOOM IN PROCESS
536 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
536 remote: added 1 changesets with 1 changes to 1 files (no-py3 chg !)
537 local stdout
537 local stdout
538
538
539 debug output
539 debug output
540
540
541 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
541 $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
542 pulling from ssh://user@dummy/remote
542 pulling from ssh://user@dummy/remote
543 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
543 running .* ".*[/\\]dummyssh" ['"]user@dummy['"] ['"]hg -R remote serve --stdio['"] (re)
544 sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !)
544 sending upgrade request: * proto=exp-ssh-v2-0003 (glob) (sshv2 !)
545 devel-peer-request: hello+between
545 devel-peer-request: hello+between
546 devel-peer-request: pairs: 81 bytes
546 devel-peer-request: pairs: 81 bytes
547 sending hello command
547 sending hello command
548 sending between command
548 sending between command
549 remote: 444 (sshv1 no-rust !)
549 remote: 444 (sshv1 no-rust !)
550 remote: 463 (sshv1 rust !)
550 remote: 463 (sshv1 rust !)
551 protocol upgraded to exp-ssh-v2-0003 (sshv2 !)
551 protocol upgraded to exp-ssh-v2-0003 (sshv2 !)
552 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !)
552 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-rust !)
553 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !)
553 remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,persistent-nodemap,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (rust !)
554 remote: 1 (sshv1 !)
554 remote: 1 (sshv1 !)
555 devel-peer-request: protocaps
555 devel-peer-request: protocaps
556 devel-peer-request: caps: * bytes (glob)
556 devel-peer-request: caps: * bytes (glob)
557 sending protocaps command
557 sending protocaps command
558 query 1; heads
558 query 1; heads
559 devel-peer-request: batched-content
559 devel-peer-request: batched-content
560 devel-peer-request: - heads (0 arguments)
560 devel-peer-request: - heads (0 arguments)
561 devel-peer-request: - known (1 arguments)
561 devel-peer-request: - known (1 arguments)
562 devel-peer-request: batch
562 devel-peer-request: batch
563 devel-peer-request: cmds: 141 bytes
563 devel-peer-request: cmds: 141 bytes
564 sending batch command
564 sending batch command
565 searching for changes
565 searching for changes
566 all remote heads known locally
566 all remote heads known locally
567 no changes found
567 no changes found
568 devel-peer-request: getbundle
568 devel-peer-request: getbundle
569 devel-peer-request: bookmarks: 1 bytes
569 devel-peer-request: bookmarks: 1 bytes
570 devel-peer-request: bundlecaps: 270 bytes
570 devel-peer-request: bundlecaps: 270 bytes
571 devel-peer-request: cg: 1 bytes
571 devel-peer-request: cg: 1 bytes
572 devel-peer-request: common: 122 bytes
572 devel-peer-request: common: 122 bytes
573 devel-peer-request: heads: 122 bytes
573 devel-peer-request: heads: 122 bytes
574 devel-peer-request: listkeys: 9 bytes
574 devel-peer-request: listkeys: 9 bytes
575 devel-peer-request: phases: 1 bytes
575 devel-peer-request: phases: 1 bytes
576 sending getbundle command
576 sending getbundle command
577 bundle2-input-bundle: with-transaction
577 bundle2-input-bundle: with-transaction
578 bundle2-input-part: "bookmarks" supported
578 bundle2-input-part: "bookmarks" supported
579 bundle2-input-part: total payload size 26
579 bundle2-input-part: total payload size 26
580 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
580 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
581 bundle2-input-part: total payload size 45
581 bundle2-input-part: total payload size 45
582 bundle2-input-part: "phase-heads" supported
582 bundle2-input-part: "phase-heads" supported
583 bundle2-input-part: total payload size 72
583 bundle2-input-part: total payload size 72
584 bundle2-input-bundle: 3 parts total
584 bundle2-input-bundle: 3 parts total
585 checking for updated bookmarks
585 checking for updated bookmarks
586
586
587 $ cd $TESTTMP
587 $ cd $TESTTMP
588
588
589 $ cat dummylog
589 $ cat dummylog
590 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
590 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
591 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
591 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
592 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
592 Got arguments 1:user@dummy 2:hg -R $TESTTMP/nonexistent serve --stdio
593 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
593 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
594 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
594 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
595 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
595 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
596 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
596 Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
597 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
597 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
598 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
598 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
599 Got arguments 1:user@dummy 2:hg -R local serve --stdio
599 Got arguments 1:user@dummy 2:hg -R local serve --stdio
600 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
600 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
601 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
601 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
602 changegroup-in-remote hook: HG_BUNDLE2=1
602 changegroup-in-remote hook: HG_BUNDLE2=1
603 HG_HOOKNAME=changegroup
603 HG_HOOKNAME=changegroup
604 HG_HOOKTYPE=changegroup
604 HG_HOOKTYPE=changegroup
605 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
605 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
606 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
606 HG_NODE_LAST=a28a9d1a809cab7d4e2fde4bee738a9ede948b60
607 HG_SOURCE=serve
607 HG_SOURCE=serve
608 HG_TXNID=TXN:$ID$
608 HG_TXNID=TXN:$ID$
609 HG_TXNNAME=serve
609 HG_TXNNAME=serve
610 HG_URL=remote:ssh:$LOCALIP
610 HG_URL=remote:ssh:$LOCALIP
611
611
612 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
612 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
613 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
613 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
614 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
614 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
615 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
615 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
616 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
616 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
617 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
617 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
618 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
618 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
619 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
619 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
620 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
620 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
621 changegroup-in-remote hook: HG_BUNDLE2=1
621 changegroup-in-remote hook: HG_BUNDLE2=1
622 HG_HOOKNAME=changegroup
622 HG_HOOKNAME=changegroup
623 HG_HOOKTYPE=changegroup
623 HG_HOOKTYPE=changegroup
624 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6
624 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6
625 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6
625 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6
626 HG_SOURCE=serve
626 HG_SOURCE=serve
627 HG_TXNID=TXN:$ID$
627 HG_TXNID=TXN:$ID$
628 HG_TXNNAME=serve
628 HG_TXNNAME=serve
629 HG_URL=remote:ssh:$LOCALIP
629 HG_URL=remote:ssh:$LOCALIP
630
630
631 Got arguments 1:user@dummy 2:chg -R remote serve --stdio (chg !)
631 Got arguments 1:user@dummy 2:chg -R remote serve --stdio (chg !)
632 changegroup-in-remote hook: HG_BUNDLE2=1 (chg !)
632 changegroup-in-remote hook: HG_BUNDLE2=1 (chg !)
633 HG_HOOKNAME=changegroup (chg !)
633 HG_HOOKNAME=changegroup (chg !)
634 HG_HOOKTYPE=changegroup (chg !)
634 HG_HOOKTYPE=changegroup (chg !)
635 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 (chg !)
635 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 (chg !)
636 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 (chg !)
636 HG_NODE_LAST=1383141674ec756a6056f6a9097618482fe0f4a6 (chg !)
637 HG_SOURCE=serve (chg !)
637 HG_SOURCE=serve (chg !)
638 HG_TXNID=TXN:$ID$ (chg !)
638 HG_TXNID=TXN:$ID$ (chg !)
639 HG_TXNNAME=serve (chg !)
639 HG_TXNNAME=serve (chg !)
640 HG_URL=remote:ssh:$LOCALIP (chg !)
640 HG_URL=remote:ssh:$LOCALIP (chg !)
641 (chg !)
641 (chg !)
642 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
642 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
643 Got arguments 1:user@dummy 2:hg init 'a repo'
643 Got arguments 1:user@dummy 2:hg init 'a repo'
644 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
644 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
645 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
645 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
646 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
646 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
647 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
647 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
648 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
648 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
649 changegroup-in-remote hook: HG_BUNDLE2=1
649 changegroup-in-remote hook: HG_BUNDLE2=1
650 HG_HOOKNAME=changegroup
650 HG_HOOKNAME=changegroup
651 HG_HOOKTYPE=changegroup
651 HG_HOOKTYPE=changegroup
652 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8
652 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8
653 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8
653 HG_NODE_LAST=65c38f4125f9602c8db4af56530cc221d93b8ef8
654 HG_SOURCE=serve
654 HG_SOURCE=serve
655 HG_TXNID=TXN:$ID$
655 HG_TXNID=TXN:$ID$
656 HG_TXNNAME=serve
656 HG_TXNNAME=serve
657 HG_URL=remote:ssh:$LOCALIP
657 HG_URL=remote:ssh:$LOCALIP
658
658
659 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
659 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
660
660
661
661
662 remote hook failure is attributed to remote
662 remote hook failure is attributed to remote
663
663
664 $ cat > $TESTTMP/failhook << EOF
664 $ cat > $TESTTMP/failhook << EOF
665 > def hook(ui, repo, **kwargs):
665 > def hook(ui, repo, **kwargs):
666 > ui.write(b'hook failure!\n')
666 > ui.write(b'hook failure!\n')
667 > ui.flush()
667 > ui.flush()
668 > return 1
668 > return 1
669 > EOF
669 > EOF
670
670
671 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
671 $ echo "pretxnchangegroup.fail = python:$TESTTMP/failhook:hook" >> remote/.hg/hgrc
672
672
673 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
673 $ hg -q --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" clone ssh://user@dummy/remote hookout
674 $ cd hookout
674 $ cd hookout
675 $ touch hookfailure
675 $ touch hookfailure
676 $ hg -q commit -A -m 'remote hook failure'
676 $ hg -q commit -A -m 'remote hook failure'
677 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
677 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" push
678 pushing to ssh://user@dummy/remote
678 pushing to ssh://user@dummy/remote
679 searching for changes
679 searching for changes
680 remote: adding changesets
680 remote: adding changesets
681 remote: adding manifests
681 remote: adding manifests
682 remote: adding file changes
682 remote: adding file changes
683 remote: hook failure!
683 remote: hook failure!
684 remote: transaction abort!
684 remote: transaction abort!
685 remote: rollback completed
685 remote: rollback completed
686 remote: pretxnchangegroup.fail hook failed
686 remote: pretxnchangegroup.fail hook failed
687 abort: push failed on remote
687 abort: push failed on remote
688 [100]
688 [100]
689
689
690 abort during pull is properly reported as such
690 abort during pull is properly reported as such
691
691
692 $ echo morefoo >> ../remote/foo
692 $ echo morefoo >> ../remote/foo
693 $ hg -R ../remote commit --message "more foo to be pulled"
693 $ hg -R ../remote commit --message "more foo to be pulled"
694 $ cat >> ../remote/.hg/hgrc << EOF
694 $ cat >> ../remote/.hg/hgrc << EOF
695 > [extensions]
695 > [extensions]
696 > crash = ${TESTDIR}/crashgetbundler.py
696 > crash = ${TESTDIR}/crashgetbundler.py
697 > EOF
697 > EOF
698 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
698 $ hg --config ui.ssh="\"$PYTHON\" $TESTDIR/dummyssh" pull
699 pulling from ssh://user@dummy/remote
699 pulling from ssh://user@dummy/remote
700 searching for changes
700 searching for changes
701 remote: abort: this is an exercise
701 remote: abort: this is an exercise
702 abort: pull failed on remote
702 abort: pull failed on remote
703 [100]
703 [100]
704
704
705 abort with no error hint when there is a ssh problem when pulling
705 abort with no error hint when there is a ssh problem when pulling
706
706
707 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
707 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
708 pulling from ssh://brokenrepository/
708 pulling from ssh://brokenrepository/
709 abort: no suitable response from remote hg
709 abort: no suitable response from remote hg
710 [255]
710 [255]
711
711
712 abort with configured error hint when there is a ssh problem when pulling
712 abort with configured error hint when there is a ssh problem when pulling
713
713
714 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
714 $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
715 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
715 > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
716 pulling from ssh://brokenrepository/
716 pulling from ssh://brokenrepository/
717 abort: no suitable response from remote hg
717 abort: no suitable response from remote hg
718 (Please see http://company/internalwiki/ssh.html)
718 (Please see http://company/internalwiki/ssh.html)
719 [255]
719 [255]
720
720
721 test that custom environment is passed down to ssh executable
721 test that custom environment is passed down to ssh executable
722 $ cat >>dumpenv <<EOF
722 $ cat >>dumpenv <<EOF
723 > #! /bin/sh
723 > #! /bin/sh
724 > echo \$VAR >&2
724 > echo \$VAR >&2
725 > EOF
725 > EOF
726 $ chmod +x dumpenv
726 $ chmod +x dumpenv
727 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
727 $ hg pull ssh://something --config ui.ssh="sh dumpenv"
728 pulling from ssh://something/
728 pulling from ssh://something/
729 remote:
729 remote:
730 abort: no suitable response from remote hg
730 abort: no suitable response from remote hg
731 [255]
731 [255]
732 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
732 $ hg pull ssh://something --config ui.ssh="sh dumpenv" --config sshenv.VAR=17
733 pulling from ssh://something/
733 pulling from ssh://something/
734 remote: 17
734 remote: 17
735 abort: no suitable response from remote hg
735 abort: no suitable response from remote hg
736 [255]
736 [255]
737
737
General Comments 0
You need to be logged in to leave comments. Login now