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['"] |
|
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 |
|
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 - |
|
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 |
|
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 - |
|
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 |
|
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['"] |
|
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