##// END OF EJS Templates
run-tests: allow some slack about 'waiting on lock' message...
marmoute -
r46636:f44b9c72 default
parent child Browse files
Show More
@@ -1,3786 +1,3787 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 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import 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 try:
73 try:
74 import Queue as queue
74 import Queue as queue
75 except ImportError:
75 except ImportError:
76 import queue
76 import queue
77
77
78 try:
78 try:
79 import shlex
79 import shlex
80
80
81 shellquote = shlex.quote
81 shellquote = shlex.quote
82 except (ImportError, AttributeError):
82 except (ImportError, AttributeError):
83 import pipes
83 import pipes
84
84
85 shellquote = pipes.quote
85 shellquote = pipes.quote
86
86
87 processlock = threading.Lock()
87 processlock = threading.Lock()
88
88
89 pygmentspresent = False
89 pygmentspresent = False
90 # ANSI color is unsupported prior to Windows 10
90 # ANSI color is unsupported prior to Windows 10
91 if os.name != 'nt':
91 if os.name != 'nt':
92 try: # is pygments installed
92 try: # is pygments installed
93 import pygments
93 import pygments
94 import pygments.lexers as lexers
94 import pygments.lexers as lexers
95 import pygments.lexer as lexer
95 import pygments.lexer as lexer
96 import pygments.formatters as formatters
96 import pygments.formatters as formatters
97 import pygments.token as token
97 import pygments.token as token
98 import pygments.style as style
98 import pygments.style as style
99
99
100 pygmentspresent = True
100 pygmentspresent = True
101 difflexer = lexers.DiffLexer()
101 difflexer = lexers.DiffLexer()
102 terminal256formatter = formatters.Terminal256Formatter()
102 terminal256formatter = formatters.Terminal256Formatter()
103 except ImportError:
103 except ImportError:
104 pass
104 pass
105
105
106 if pygmentspresent:
106 if pygmentspresent:
107
107
108 class TestRunnerStyle(style.Style):
108 class TestRunnerStyle(style.Style):
109 default_style = ""
109 default_style = ""
110 skipped = token.string_to_tokentype("Token.Generic.Skipped")
110 skipped = token.string_to_tokentype("Token.Generic.Skipped")
111 failed = token.string_to_tokentype("Token.Generic.Failed")
111 failed = token.string_to_tokentype("Token.Generic.Failed")
112 skippedname = token.string_to_tokentype("Token.Generic.SName")
112 skippedname = token.string_to_tokentype("Token.Generic.SName")
113 failedname = token.string_to_tokentype("Token.Generic.FName")
113 failedname = token.string_to_tokentype("Token.Generic.FName")
114 styles = {
114 styles = {
115 skipped: '#e5e5e5',
115 skipped: '#e5e5e5',
116 skippedname: '#00ffff',
116 skippedname: '#00ffff',
117 failed: '#7f0000',
117 failed: '#7f0000',
118 failedname: '#ff0000',
118 failedname: '#ff0000',
119 }
119 }
120
120
121 class TestRunnerLexer(lexer.RegexLexer):
121 class TestRunnerLexer(lexer.RegexLexer):
122 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
122 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
123 tokens = {
123 tokens = {
124 'root': [
124 'root': [
125 (r'^Skipped', token.Generic.Skipped, 'skipped'),
125 (r'^Skipped', token.Generic.Skipped, 'skipped'),
126 (r'^Failed ', token.Generic.Failed, 'failed'),
126 (r'^Failed ', token.Generic.Failed, 'failed'),
127 (r'^ERROR: ', token.Generic.Failed, 'failed'),
127 (r'^ERROR: ', token.Generic.Failed, 'failed'),
128 ],
128 ],
129 'skipped': [
129 'skipped': [
130 (testpattern, token.Generic.SName),
130 (testpattern, token.Generic.SName),
131 (r':.*', token.Generic.Skipped),
131 (r':.*', token.Generic.Skipped),
132 ],
132 ],
133 'failed': [
133 'failed': [
134 (testpattern, token.Generic.FName),
134 (testpattern, token.Generic.FName),
135 (r'(:| ).*', token.Generic.Failed),
135 (r'(:| ).*', token.Generic.Failed),
136 ],
136 ],
137 }
137 }
138
138
139 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
139 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
140 runnerlexer = TestRunnerLexer()
140 runnerlexer = TestRunnerLexer()
141
141
142 origenviron = os.environ.copy()
142 origenviron = os.environ.copy()
143
143
144 if sys.version_info > (3, 5, 0):
144 if sys.version_info > (3, 5, 0):
145 PYTHON3 = True
145 PYTHON3 = True
146 xrange = range # we use xrange in one place, and we'd rather not use range
146 xrange = range # we use xrange in one place, and we'd rather not use range
147
147
148 def _sys2bytes(p):
148 def _sys2bytes(p):
149 if p is None:
149 if p is None:
150 return p
150 return p
151 return p.encode('utf-8')
151 return p.encode('utf-8')
152
152
153 def _bytes2sys(p):
153 def _bytes2sys(p):
154 if p is None:
154 if p is None:
155 return p
155 return p
156 return p.decode('utf-8')
156 return p.decode('utf-8')
157
157
158 osenvironb = getattr(os, 'environb', None)
158 osenvironb = getattr(os, 'environb', None)
159 if osenvironb is None:
159 if osenvironb is None:
160 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # Windows lacks os.environb, for instance. A proxy over the real thing
161 # instead of a copy allows the environment to be updated via bytes on
161 # instead of a copy allows the environment to be updated via bytes on
162 # all platforms.
162 # all platforms.
163 class environbytes(object):
163 class environbytes(object):
164 def __init__(self, strenv):
164 def __init__(self, strenv):
165 self.__len__ = strenv.__len__
165 self.__len__ = strenv.__len__
166 self.clear = strenv.clear
166 self.clear = strenv.clear
167 self._strenv = strenv
167 self._strenv = strenv
168
168
169 def __getitem__(self, k):
169 def __getitem__(self, k):
170 v = self._strenv.__getitem__(_bytes2sys(k))
170 v = self._strenv.__getitem__(_bytes2sys(k))
171 return _sys2bytes(v)
171 return _sys2bytes(v)
172
172
173 def __setitem__(self, k, v):
173 def __setitem__(self, k, v):
174 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
174 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
175
175
176 def __delitem__(self, k):
176 def __delitem__(self, k):
177 self._strenv.__delitem__(_bytes2sys(k))
177 self._strenv.__delitem__(_bytes2sys(k))
178
178
179 def __contains__(self, k):
179 def __contains__(self, k):
180 return self._strenv.__contains__(_bytes2sys(k))
180 return self._strenv.__contains__(_bytes2sys(k))
181
181
182 def __iter__(self):
182 def __iter__(self):
183 return iter([_sys2bytes(k) for k in iter(self._strenv)])
183 return iter([_sys2bytes(k) for k in iter(self._strenv)])
184
184
185 def get(self, k, default=None):
185 def get(self, k, default=None):
186 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
186 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
187 return _sys2bytes(v)
187 return _sys2bytes(v)
188
188
189 def pop(self, k, default=None):
189 def pop(self, k, default=None):
190 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
190 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
191 return _sys2bytes(v)
191 return _sys2bytes(v)
192
192
193 osenvironb = environbytes(os.environ)
193 osenvironb = environbytes(os.environ)
194
194
195 getcwdb = getattr(os, 'getcwdb')
195 getcwdb = getattr(os, 'getcwdb')
196 if not getcwdb or os.name == 'nt':
196 if not getcwdb or os.name == 'nt':
197 getcwdb = lambda: _sys2bytes(os.getcwd())
197 getcwdb = lambda: _sys2bytes(os.getcwd())
198
198
199 elif sys.version_info >= (3, 0, 0):
199 elif sys.version_info >= (3, 0, 0):
200 print(
200 print(
201 '%s is only supported on Python 3.5+ and 2.7, not %s'
201 '%s is only supported on Python 3.5+ and 2.7, not %s'
202 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
202 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
203 )
203 )
204 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
204 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
205 else:
205 else:
206 PYTHON3 = False
206 PYTHON3 = False
207
207
208 # In python 2.x, path operations are generally done using
208 # In python 2.x, path operations are generally done using
209 # bytestrings by default, so we don't have to do any extra
209 # bytestrings by default, so we don't have to do any extra
210 # fiddling there. We define the wrapper functions anyway just to
210 # fiddling there. We define the wrapper functions anyway just to
211 # help keep code consistent between platforms.
211 # help keep code consistent between platforms.
212 def _sys2bytes(p):
212 def _sys2bytes(p):
213 return p
213 return p
214
214
215 _bytes2sys = _sys2bytes
215 _bytes2sys = _sys2bytes
216 osenvironb = os.environ
216 osenvironb = os.environ
217 getcwdb = os.getcwd
217 getcwdb = os.getcwd
218
218
219 # For Windows support
219 # For Windows support
220 wifexited = getattr(os, "WIFEXITED", lambda x: False)
220 wifexited = getattr(os, "WIFEXITED", lambda x: False)
221
221
222 # Whether to use IPv6
222 # Whether to use IPv6
223 def checksocketfamily(name, port=20058):
223 def checksocketfamily(name, port=20058):
224 """return true if we can listen on localhost using family=name
224 """return true if we can listen on localhost using family=name
225
225
226 name should be either 'AF_INET', or 'AF_INET6'.
226 name should be either 'AF_INET', or 'AF_INET6'.
227 port being used is okay - EADDRINUSE is considered as successful.
227 port being used is okay - EADDRINUSE is considered as successful.
228 """
228 """
229 family = getattr(socket, name, None)
229 family = getattr(socket, name, None)
230 if family is None:
230 if family is None:
231 return False
231 return False
232 try:
232 try:
233 s = socket.socket(family, socket.SOCK_STREAM)
233 s = socket.socket(family, socket.SOCK_STREAM)
234 s.bind(('localhost', port))
234 s.bind(('localhost', port))
235 s.close()
235 s.close()
236 return True
236 return True
237 except socket.error as exc:
237 except socket.error as exc:
238 if exc.errno == errno.EADDRINUSE:
238 if exc.errno == errno.EADDRINUSE:
239 return True
239 return True
240 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
240 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
241 return False
241 return False
242 else:
242 else:
243 raise
243 raise
244 else:
244 else:
245 return False
245 return False
246
246
247
247
248 # useipv6 will be set by parseargs
248 # useipv6 will be set by parseargs
249 useipv6 = None
249 useipv6 = None
250
250
251
251
252 def checkportisavailable(port):
252 def checkportisavailable(port):
253 """return true if a port seems free to bind on localhost"""
253 """return true if a port seems free to bind on localhost"""
254 if useipv6:
254 if useipv6:
255 family = socket.AF_INET6
255 family = socket.AF_INET6
256 else:
256 else:
257 family = socket.AF_INET
257 family = socket.AF_INET
258 try:
258 try:
259 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
259 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
260 s.bind(('localhost', port))
260 s.bind(('localhost', port))
261 return True
261 return True
262 except socket.error as exc:
262 except socket.error as exc:
263 if exc.errno not in (
263 if exc.errno not in (
264 errno.EADDRINUSE,
264 errno.EADDRINUSE,
265 errno.EADDRNOTAVAIL,
265 errno.EADDRNOTAVAIL,
266 errno.EPROTONOSUPPORT,
266 errno.EPROTONOSUPPORT,
267 ):
267 ):
268 raise
268 raise
269 return False
269 return False
270
270
271
271
272 closefds = os.name == 'posix'
272 closefds = os.name == 'posix'
273
273
274
274
275 def Popen4(cmd, wd, timeout, env=None):
275 def Popen4(cmd, wd, timeout, env=None):
276 processlock.acquire()
276 processlock.acquire()
277 p = subprocess.Popen(
277 p = subprocess.Popen(
278 _bytes2sys(cmd),
278 _bytes2sys(cmd),
279 shell=True,
279 shell=True,
280 bufsize=-1,
280 bufsize=-1,
281 cwd=_bytes2sys(wd),
281 cwd=_bytes2sys(wd),
282 env=env,
282 env=env,
283 close_fds=closefds,
283 close_fds=closefds,
284 stdin=subprocess.PIPE,
284 stdin=subprocess.PIPE,
285 stdout=subprocess.PIPE,
285 stdout=subprocess.PIPE,
286 stderr=subprocess.STDOUT,
286 stderr=subprocess.STDOUT,
287 )
287 )
288 processlock.release()
288 processlock.release()
289
289
290 p.fromchild = p.stdout
290 p.fromchild = p.stdout
291 p.tochild = p.stdin
291 p.tochild = p.stdin
292 p.childerr = p.stderr
292 p.childerr = p.stderr
293
293
294 p.timeout = False
294 p.timeout = False
295 if timeout:
295 if timeout:
296
296
297 def t():
297 def t():
298 start = time.time()
298 start = time.time()
299 while time.time() - start < timeout and p.returncode is None:
299 while time.time() - start < timeout and p.returncode is None:
300 time.sleep(0.1)
300 time.sleep(0.1)
301 p.timeout = True
301 p.timeout = True
302 if p.returncode is None:
302 if p.returncode is None:
303 terminate(p)
303 terminate(p)
304
304
305 threading.Thread(target=t).start()
305 threading.Thread(target=t).start()
306
306
307 return p
307 return p
308
308
309
309
310 if sys.executable:
310 if sys.executable:
311 sysexecutable = sys.executable
311 sysexecutable = sys.executable
312 elif os.environ.get('PYTHONEXECUTABLE'):
312 elif os.environ.get('PYTHONEXECUTABLE'):
313 sysexecutable = os.environ['PYTHONEXECUTABLE']
313 sysexecutable = os.environ['PYTHONEXECUTABLE']
314 elif os.environ.get('PYTHON'):
314 elif os.environ.get('PYTHON'):
315 sysexecutable = os.environ['PYTHON']
315 sysexecutable = os.environ['PYTHON']
316 else:
316 else:
317 raise AssertionError('Could not find Python interpreter')
317 raise AssertionError('Could not find Python interpreter')
318
318
319 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
319 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
320 IMPL_PATH = b'PYTHONPATH'
320 IMPL_PATH = b'PYTHONPATH'
321 if 'java' in sys.platform:
321 if 'java' in sys.platform:
322 IMPL_PATH = b'JYTHONPATH'
322 IMPL_PATH = b'JYTHONPATH'
323
323
324 default_defaults = {
324 default_defaults = {
325 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
325 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
326 'timeout': ('HGTEST_TIMEOUT', 180),
326 'timeout': ('HGTEST_TIMEOUT', 180),
327 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
327 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
328 'port': ('HGTEST_PORT', 20059),
328 'port': ('HGTEST_PORT', 20059),
329 'shell': ('HGTEST_SHELL', 'sh'),
329 'shell': ('HGTEST_SHELL', 'sh'),
330 }
330 }
331
331
332 defaults = default_defaults.copy()
332 defaults = default_defaults.copy()
333
333
334
334
335 def canonpath(path):
335 def canonpath(path):
336 return os.path.realpath(os.path.expanduser(path))
336 return os.path.realpath(os.path.expanduser(path))
337
337
338
338
339 def parselistfiles(files, listtype, warn=True):
339 def parselistfiles(files, listtype, warn=True):
340 entries = dict()
340 entries = dict()
341 for filename in files:
341 for filename in files:
342 try:
342 try:
343 path = os.path.expanduser(os.path.expandvars(filename))
343 path = os.path.expanduser(os.path.expandvars(filename))
344 f = open(path, "rb")
344 f = open(path, "rb")
345 except IOError as err:
345 except IOError as err:
346 if err.errno != errno.ENOENT:
346 if err.errno != errno.ENOENT:
347 raise
347 raise
348 if warn:
348 if warn:
349 print("warning: no such %s file: %s" % (listtype, filename))
349 print("warning: no such %s file: %s" % (listtype, filename))
350 continue
350 continue
351
351
352 for line in f.readlines():
352 for line in f.readlines():
353 line = line.split(b'#', 1)[0].strip()
353 line = line.split(b'#', 1)[0].strip()
354 if line:
354 if line:
355 entries[line] = filename
355 entries[line] = filename
356
356
357 f.close()
357 f.close()
358 return entries
358 return entries
359
359
360
360
361 def parsettestcases(path):
361 def parsettestcases(path):
362 """read a .t test file, return a set of test case names
362 """read a .t test file, return a set of test case names
363
363
364 If path does not exist, return an empty set.
364 If path does not exist, return an empty set.
365 """
365 """
366 cases = []
366 cases = []
367 try:
367 try:
368 with open(path, 'rb') as f:
368 with open(path, 'rb') as f:
369 for l in f:
369 for l in f:
370 if l.startswith(b'#testcases '):
370 if l.startswith(b'#testcases '):
371 cases.append(sorted(l[11:].split()))
371 cases.append(sorted(l[11:].split()))
372 except IOError as ex:
372 except IOError as ex:
373 if ex.errno != errno.ENOENT:
373 if ex.errno != errno.ENOENT:
374 raise
374 raise
375 return cases
375 return cases
376
376
377
377
378 def getparser():
378 def getparser():
379 """Obtain the OptionParser used by the CLI."""
379 """Obtain the OptionParser used by the CLI."""
380 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
380 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
381
381
382 selection = parser.add_argument_group('Test Selection')
382 selection = parser.add_argument_group('Test Selection')
383 selection.add_argument(
383 selection.add_argument(
384 '--allow-slow-tests',
384 '--allow-slow-tests',
385 action='store_true',
385 action='store_true',
386 help='allow extremely slow tests',
386 help='allow extremely slow tests',
387 )
387 )
388 selection.add_argument(
388 selection.add_argument(
389 "--blacklist",
389 "--blacklist",
390 action="append",
390 action="append",
391 help="skip tests listed in the specified blacklist file",
391 help="skip tests listed in the specified blacklist file",
392 )
392 )
393 selection.add_argument(
393 selection.add_argument(
394 "--changed",
394 "--changed",
395 help="run tests that are changed in parent rev or working directory",
395 help="run tests that are changed in parent rev or working directory",
396 )
396 )
397 selection.add_argument(
397 selection.add_argument(
398 "-k", "--keywords", help="run tests matching keywords"
398 "-k", "--keywords", help="run tests matching keywords"
399 )
399 )
400 selection.add_argument(
400 selection.add_argument(
401 "-r", "--retest", action="store_true", help="retest failed tests"
401 "-r", "--retest", action="store_true", help="retest failed tests"
402 )
402 )
403 selection.add_argument(
403 selection.add_argument(
404 "--test-list",
404 "--test-list",
405 action="append",
405 action="append",
406 help="read tests to run from the specified file",
406 help="read tests to run from the specified file",
407 )
407 )
408 selection.add_argument(
408 selection.add_argument(
409 "--whitelist",
409 "--whitelist",
410 action="append",
410 action="append",
411 help="always run tests listed in the specified whitelist file",
411 help="always run tests listed in the specified whitelist file",
412 )
412 )
413 selection.add_argument(
413 selection.add_argument(
414 'tests', metavar='TESTS', nargs='*', help='Tests to run'
414 'tests', metavar='TESTS', nargs='*', help='Tests to run'
415 )
415 )
416
416
417 harness = parser.add_argument_group('Test Harness Behavior')
417 harness = parser.add_argument_group('Test Harness Behavior')
418 harness.add_argument(
418 harness.add_argument(
419 '--bisect-repo',
419 '--bisect-repo',
420 metavar='bisect_repo',
420 metavar='bisect_repo',
421 help=(
421 help=(
422 "Path of a repo to bisect. Use together with " "--known-good-rev"
422 "Path of a repo to bisect. Use together with " "--known-good-rev"
423 ),
423 ),
424 )
424 )
425 harness.add_argument(
425 harness.add_argument(
426 "-d",
426 "-d",
427 "--debug",
427 "--debug",
428 action="store_true",
428 action="store_true",
429 help="debug mode: write output of test scripts to console"
429 help="debug mode: write output of test scripts to console"
430 " rather than capturing and diffing it (disables timeout)",
430 " rather than capturing and diffing it (disables timeout)",
431 )
431 )
432 harness.add_argument(
432 harness.add_argument(
433 "-f",
433 "-f",
434 "--first",
434 "--first",
435 action="store_true",
435 action="store_true",
436 help="exit on the first test failure",
436 help="exit on the first test failure",
437 )
437 )
438 harness.add_argument(
438 harness.add_argument(
439 "-i",
439 "-i",
440 "--interactive",
440 "--interactive",
441 action="store_true",
441 action="store_true",
442 help="prompt to accept changed output",
442 help="prompt to accept changed output",
443 )
443 )
444 harness.add_argument(
444 harness.add_argument(
445 "-j",
445 "-j",
446 "--jobs",
446 "--jobs",
447 type=int,
447 type=int,
448 help="number of jobs to run in parallel"
448 help="number of jobs to run in parallel"
449 " (default: $%s or %d)" % defaults['jobs'],
449 " (default: $%s or %d)" % defaults['jobs'],
450 )
450 )
451 harness.add_argument(
451 harness.add_argument(
452 "--keep-tmpdir",
452 "--keep-tmpdir",
453 action="store_true",
453 action="store_true",
454 help="keep temporary directory after running tests",
454 help="keep temporary directory after running tests",
455 )
455 )
456 harness.add_argument(
456 harness.add_argument(
457 '--known-good-rev',
457 '--known-good-rev',
458 metavar="known_good_rev",
458 metavar="known_good_rev",
459 help=(
459 help=(
460 "Automatically bisect any failures using this "
460 "Automatically bisect any failures using this "
461 "revision as a known-good revision."
461 "revision as a known-good revision."
462 ),
462 ),
463 )
463 )
464 harness.add_argument(
464 harness.add_argument(
465 "--list-tests",
465 "--list-tests",
466 action="store_true",
466 action="store_true",
467 help="list tests instead of running them",
467 help="list tests instead of running them",
468 )
468 )
469 harness.add_argument(
469 harness.add_argument(
470 "--loop", action="store_true", help="loop tests repeatedly"
470 "--loop", action="store_true", help="loop tests repeatedly"
471 )
471 )
472 harness.add_argument(
472 harness.add_argument(
473 '--random', action="store_true", help='run tests in random order'
473 '--random', action="store_true", help='run tests in random order'
474 )
474 )
475 harness.add_argument(
475 harness.add_argument(
476 '--order-by-runtime',
476 '--order-by-runtime',
477 action="store_true",
477 action="store_true",
478 help='run slowest tests first, according to .testtimes',
478 help='run slowest tests first, according to .testtimes',
479 )
479 )
480 harness.add_argument(
480 harness.add_argument(
481 "-p",
481 "-p",
482 "--port",
482 "--port",
483 type=int,
483 type=int,
484 help="port on which servers should listen"
484 help="port on which servers should listen"
485 " (default: $%s or %d)" % defaults['port'],
485 " (default: $%s or %d)" % defaults['port'],
486 )
486 )
487 harness.add_argument(
487 harness.add_argument(
488 '--profile-runner',
488 '--profile-runner',
489 action='store_true',
489 action='store_true',
490 help='run statprof on run-tests',
490 help='run statprof on run-tests',
491 )
491 )
492 harness.add_argument(
492 harness.add_argument(
493 "-R", "--restart", action="store_true", help="restart at last error"
493 "-R", "--restart", action="store_true", help="restart at last error"
494 )
494 )
495 harness.add_argument(
495 harness.add_argument(
496 "--runs-per-test",
496 "--runs-per-test",
497 type=int,
497 type=int,
498 dest="runs_per_test",
498 dest="runs_per_test",
499 help="run each test N times (default=1)",
499 help="run each test N times (default=1)",
500 default=1,
500 default=1,
501 )
501 )
502 harness.add_argument(
502 harness.add_argument(
503 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
503 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
504 )
504 )
505 harness.add_argument(
505 harness.add_argument(
506 '--showchannels', action='store_true', help='show scheduling channels'
506 '--showchannels', action='store_true', help='show scheduling channels'
507 )
507 )
508 harness.add_argument(
508 harness.add_argument(
509 "--slowtimeout",
509 "--slowtimeout",
510 type=int,
510 type=int,
511 help="kill errant slow tests after SLOWTIMEOUT seconds"
511 help="kill errant slow tests after SLOWTIMEOUT seconds"
512 " (default: $%s or %d)" % defaults['slowtimeout'],
512 " (default: $%s or %d)" % defaults['slowtimeout'],
513 )
513 )
514 harness.add_argument(
514 harness.add_argument(
515 "-t",
515 "-t",
516 "--timeout",
516 "--timeout",
517 type=int,
517 type=int,
518 help="kill errant tests after TIMEOUT seconds"
518 help="kill errant tests after TIMEOUT seconds"
519 " (default: $%s or %d)" % defaults['timeout'],
519 " (default: $%s or %d)" % defaults['timeout'],
520 )
520 )
521 harness.add_argument(
521 harness.add_argument(
522 "--tmpdir",
522 "--tmpdir",
523 help="run tests in the given temporary directory"
523 help="run tests in the given temporary directory"
524 " (implies --keep-tmpdir)",
524 " (implies --keep-tmpdir)",
525 )
525 )
526 harness.add_argument(
526 harness.add_argument(
527 "-v", "--verbose", action="store_true", help="output verbose messages"
527 "-v", "--verbose", action="store_true", help="output verbose messages"
528 )
528 )
529
529
530 hgconf = parser.add_argument_group('Mercurial Configuration')
530 hgconf = parser.add_argument_group('Mercurial Configuration')
531 hgconf.add_argument(
531 hgconf.add_argument(
532 "--chg",
532 "--chg",
533 action="store_true",
533 action="store_true",
534 help="install and use chg wrapper in place of hg",
534 help="install and use chg wrapper in place of hg",
535 )
535 )
536 hgconf.add_argument(
536 hgconf.add_argument(
537 "--chg-debug",
537 "--chg-debug",
538 action="store_true",
538 action="store_true",
539 help="show chg debug logs",
539 help="show chg debug logs",
540 )
540 )
541 hgconf.add_argument("--compiler", help="compiler to build with")
541 hgconf.add_argument("--compiler", help="compiler to build with")
542 hgconf.add_argument(
542 hgconf.add_argument(
543 '--extra-config-opt',
543 '--extra-config-opt',
544 action="append",
544 action="append",
545 default=[],
545 default=[],
546 help='set the given config opt in the test hgrc',
546 help='set the given config opt in the test hgrc',
547 )
547 )
548 hgconf.add_argument(
548 hgconf.add_argument(
549 "-l",
549 "-l",
550 "--local",
550 "--local",
551 action="store_true",
551 action="store_true",
552 help="shortcut for --with-hg=<testdir>/../hg, "
552 help="shortcut for --with-hg=<testdir>/../hg, "
553 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
553 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
554 )
554 )
555 hgconf.add_argument(
555 hgconf.add_argument(
556 "--ipv6",
556 "--ipv6",
557 action="store_true",
557 action="store_true",
558 help="prefer IPv6 to IPv4 for network related tests",
558 help="prefer IPv6 to IPv4 for network related tests",
559 )
559 )
560 hgconf.add_argument(
560 hgconf.add_argument(
561 "--pure",
561 "--pure",
562 action="store_true",
562 action="store_true",
563 help="use pure Python code instead of C extensions",
563 help="use pure Python code instead of C extensions",
564 )
564 )
565 hgconf.add_argument(
565 hgconf.add_argument(
566 "--rust",
566 "--rust",
567 action="store_true",
567 action="store_true",
568 help="use Rust code alongside C extensions",
568 help="use Rust code alongside C extensions",
569 )
569 )
570 hgconf.add_argument(
570 hgconf.add_argument(
571 "--no-rust",
571 "--no-rust",
572 action="store_true",
572 action="store_true",
573 help="do not use Rust code even if compiled",
573 help="do not use Rust code even if compiled",
574 )
574 )
575 hgconf.add_argument(
575 hgconf.add_argument(
576 "--with-chg",
576 "--with-chg",
577 metavar="CHG",
577 metavar="CHG",
578 help="use specified chg wrapper in place of hg",
578 help="use specified chg wrapper in place of hg",
579 )
579 )
580 hgconf.add_argument(
580 hgconf.add_argument(
581 "--with-hg",
581 "--with-hg",
582 metavar="HG",
582 metavar="HG",
583 help="test using specified hg script rather than a "
583 help="test using specified hg script rather than a "
584 "temporary installation",
584 "temporary installation",
585 )
585 )
586
586
587 reporting = parser.add_argument_group('Results Reporting')
587 reporting = parser.add_argument_group('Results Reporting')
588 reporting.add_argument(
588 reporting.add_argument(
589 "-C",
589 "-C",
590 "--annotate",
590 "--annotate",
591 action="store_true",
591 action="store_true",
592 help="output files annotated with coverage",
592 help="output files annotated with coverage",
593 )
593 )
594 reporting.add_argument(
594 reporting.add_argument(
595 "--color",
595 "--color",
596 choices=["always", "auto", "never"],
596 choices=["always", "auto", "never"],
597 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
597 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
598 help="colorisation: always|auto|never (default: auto)",
598 help="colorisation: always|auto|never (default: auto)",
599 )
599 )
600 reporting.add_argument(
600 reporting.add_argument(
601 "-c",
601 "-c",
602 "--cover",
602 "--cover",
603 action="store_true",
603 action="store_true",
604 help="print a test coverage report",
604 help="print a test coverage report",
605 )
605 )
606 reporting.add_argument(
606 reporting.add_argument(
607 '--exceptions',
607 '--exceptions',
608 action='store_true',
608 action='store_true',
609 help='log all exceptions and generate an exception report',
609 help='log all exceptions and generate an exception report',
610 )
610 )
611 reporting.add_argument(
611 reporting.add_argument(
612 "-H",
612 "-H",
613 "--htmlcov",
613 "--htmlcov",
614 action="store_true",
614 action="store_true",
615 help="create an HTML report of the coverage of the files",
615 help="create an HTML report of the coverage of the files",
616 )
616 )
617 reporting.add_argument(
617 reporting.add_argument(
618 "--json",
618 "--json",
619 action="store_true",
619 action="store_true",
620 help="store test result data in 'report.json' file",
620 help="store test result data in 'report.json' file",
621 )
621 )
622 reporting.add_argument(
622 reporting.add_argument(
623 "--outputdir",
623 "--outputdir",
624 help="directory to write error logs to (default=test directory)",
624 help="directory to write error logs to (default=test directory)",
625 )
625 )
626 reporting.add_argument(
626 reporting.add_argument(
627 "-n", "--nodiff", action="store_true", help="skip showing test changes"
627 "-n", "--nodiff", action="store_true", help="skip showing test changes"
628 )
628 )
629 reporting.add_argument(
629 reporting.add_argument(
630 "-S",
630 "-S",
631 "--noskips",
631 "--noskips",
632 action="store_true",
632 action="store_true",
633 help="don't report skip tests verbosely",
633 help="don't report skip tests verbosely",
634 )
634 )
635 reporting.add_argument(
635 reporting.add_argument(
636 "--time", action="store_true", help="time how long each test takes"
636 "--time", action="store_true", help="time how long each test takes"
637 )
637 )
638 reporting.add_argument("--view", help="external diff viewer")
638 reporting.add_argument("--view", help="external diff viewer")
639 reporting.add_argument(
639 reporting.add_argument(
640 "--xunit", help="record xunit results at specified path"
640 "--xunit", help="record xunit results at specified path"
641 )
641 )
642
642
643 for option, (envvar, default) in defaults.items():
643 for option, (envvar, default) in defaults.items():
644 defaults[option] = type(default)(os.environ.get(envvar, default))
644 defaults[option] = type(default)(os.environ.get(envvar, default))
645 parser.set_defaults(**defaults)
645 parser.set_defaults(**defaults)
646
646
647 return parser
647 return parser
648
648
649
649
650 def parseargs(args, parser):
650 def parseargs(args, parser):
651 """Parse arguments with our OptionParser and validate results."""
651 """Parse arguments with our OptionParser and validate results."""
652 options = parser.parse_args(args)
652 options = parser.parse_args(args)
653
653
654 # jython is always pure
654 # jython is always pure
655 if 'java' in sys.platform or '__pypy__' in sys.modules:
655 if 'java' in sys.platform or '__pypy__' in sys.modules:
656 options.pure = True
656 options.pure = True
657
657
658 if platform.python_implementation() != 'CPython' and options.rust:
658 if platform.python_implementation() != 'CPython' and options.rust:
659 parser.error('Rust extensions are only available with CPython')
659 parser.error('Rust extensions are only available with CPython')
660
660
661 if options.pure and options.rust:
661 if options.pure and options.rust:
662 parser.error('--rust cannot be used with --pure')
662 parser.error('--rust cannot be used with --pure')
663
663
664 if options.rust and options.no_rust:
664 if options.rust and options.no_rust:
665 parser.error('--rust cannot be used with --no-rust')
665 parser.error('--rust cannot be used with --no-rust')
666
666
667 if options.local:
667 if options.local:
668 if options.with_hg or options.with_chg:
668 if options.with_hg or options.with_chg:
669 parser.error('--local cannot be used with --with-hg or --with-chg')
669 parser.error('--local cannot be used with --with-hg or --with-chg')
670 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
670 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
671 reporootdir = os.path.dirname(testdir)
671 reporootdir = os.path.dirname(testdir)
672 pathandattrs = [(b'hg', 'with_hg')]
672 pathandattrs = [(b'hg', 'with_hg')]
673 if options.chg:
673 if options.chg:
674 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
674 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
675 for relpath, attr in pathandattrs:
675 for relpath, attr in pathandattrs:
676 binpath = os.path.join(reporootdir, relpath)
676 binpath = os.path.join(reporootdir, relpath)
677 if os.name != 'nt' and not os.access(binpath, os.X_OK):
677 if os.name != 'nt' and not os.access(binpath, os.X_OK):
678 parser.error(
678 parser.error(
679 '--local specified, but %r not found or '
679 '--local specified, but %r not found or '
680 'not executable' % binpath
680 'not executable' % binpath
681 )
681 )
682 setattr(options, attr, _bytes2sys(binpath))
682 setattr(options, attr, _bytes2sys(binpath))
683
683
684 if options.with_hg:
684 if options.with_hg:
685 options.with_hg = canonpath(_sys2bytes(options.with_hg))
685 options.with_hg = canonpath(_sys2bytes(options.with_hg))
686 if not (
686 if not (
687 os.path.isfile(options.with_hg)
687 os.path.isfile(options.with_hg)
688 and os.access(options.with_hg, os.X_OK)
688 and os.access(options.with_hg, os.X_OK)
689 ):
689 ):
690 parser.error('--with-hg must specify an executable hg script')
690 parser.error('--with-hg must specify an executable hg script')
691 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
691 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
692 sys.stderr.write('warning: --with-hg should specify an hg script\n')
692 sys.stderr.write('warning: --with-hg should specify an hg script\n')
693 sys.stderr.flush()
693 sys.stderr.flush()
694
694
695 if (options.chg or options.with_chg) and os.name == 'nt':
695 if (options.chg or options.with_chg) and os.name == 'nt':
696 parser.error('chg does not work on %s' % os.name)
696 parser.error('chg does not work on %s' % os.name)
697 if options.with_chg:
697 if options.with_chg:
698 options.chg = False # no installation to temporary location
698 options.chg = False # no installation to temporary location
699 options.with_chg = canonpath(_sys2bytes(options.with_chg))
699 options.with_chg = canonpath(_sys2bytes(options.with_chg))
700 if not (
700 if not (
701 os.path.isfile(options.with_chg)
701 os.path.isfile(options.with_chg)
702 and os.access(options.with_chg, os.X_OK)
702 and os.access(options.with_chg, os.X_OK)
703 ):
703 ):
704 parser.error('--with-chg must specify a chg executable')
704 parser.error('--with-chg must specify a chg executable')
705 if options.chg and options.with_hg:
705 if options.chg and options.with_hg:
706 # chg shares installation location with hg
706 # chg shares installation location with hg
707 parser.error(
707 parser.error(
708 '--chg does not work when --with-hg is specified '
708 '--chg does not work when --with-hg is specified '
709 '(use --with-chg instead)'
709 '(use --with-chg instead)'
710 )
710 )
711
711
712 if options.color == 'always' and not pygmentspresent:
712 if options.color == 'always' and not pygmentspresent:
713 sys.stderr.write(
713 sys.stderr.write(
714 'warning: --color=always ignored because '
714 'warning: --color=always ignored because '
715 'pygments is not installed\n'
715 'pygments is not installed\n'
716 )
716 )
717
717
718 if options.bisect_repo and not options.known_good_rev:
718 if options.bisect_repo and not options.known_good_rev:
719 parser.error("--bisect-repo cannot be used without --known-good-rev")
719 parser.error("--bisect-repo cannot be used without --known-good-rev")
720
720
721 global useipv6
721 global useipv6
722 if options.ipv6:
722 if options.ipv6:
723 useipv6 = checksocketfamily('AF_INET6')
723 useipv6 = checksocketfamily('AF_INET6')
724 else:
724 else:
725 # only use IPv6 if IPv4 is unavailable and IPv6 is available
725 # only use IPv6 if IPv4 is unavailable and IPv6 is available
726 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
726 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
727 'AF_INET6'
727 'AF_INET6'
728 )
728 )
729
729
730 options.anycoverage = options.cover or options.annotate or options.htmlcov
730 options.anycoverage = options.cover or options.annotate or options.htmlcov
731 if options.anycoverage:
731 if options.anycoverage:
732 try:
732 try:
733 import coverage
733 import coverage
734
734
735 covver = version.StrictVersion(coverage.__version__).version
735 covver = version.StrictVersion(coverage.__version__).version
736 if covver < (3, 3):
736 if covver < (3, 3):
737 parser.error('coverage options require coverage 3.3 or later')
737 parser.error('coverage options require coverage 3.3 or later')
738 except ImportError:
738 except ImportError:
739 parser.error('coverage options now require the coverage package')
739 parser.error('coverage options now require the coverage package')
740
740
741 if options.anycoverage and options.local:
741 if options.anycoverage and options.local:
742 # this needs some path mangling somewhere, I guess
742 # this needs some path mangling somewhere, I guess
743 parser.error(
743 parser.error(
744 "sorry, coverage options do not work when --local " "is specified"
744 "sorry, coverage options do not work when --local " "is specified"
745 )
745 )
746
746
747 if options.anycoverage and options.with_hg:
747 if options.anycoverage and options.with_hg:
748 parser.error(
748 parser.error(
749 "sorry, coverage options do not work when --with-hg " "is specified"
749 "sorry, coverage options do not work when --with-hg " "is specified"
750 )
750 )
751
751
752 global verbose
752 global verbose
753 if options.verbose:
753 if options.verbose:
754 verbose = ''
754 verbose = ''
755
755
756 if options.tmpdir:
756 if options.tmpdir:
757 options.tmpdir = canonpath(options.tmpdir)
757 options.tmpdir = canonpath(options.tmpdir)
758
758
759 if options.jobs < 1:
759 if options.jobs < 1:
760 parser.error('--jobs must be positive')
760 parser.error('--jobs must be positive')
761 if options.interactive and options.debug:
761 if options.interactive and options.debug:
762 parser.error("-i/--interactive and -d/--debug are incompatible")
762 parser.error("-i/--interactive and -d/--debug are incompatible")
763 if options.debug:
763 if options.debug:
764 if options.timeout != defaults['timeout']:
764 if options.timeout != defaults['timeout']:
765 sys.stderr.write('warning: --timeout option ignored with --debug\n')
765 sys.stderr.write('warning: --timeout option ignored with --debug\n')
766 if options.slowtimeout != defaults['slowtimeout']:
766 if options.slowtimeout != defaults['slowtimeout']:
767 sys.stderr.write(
767 sys.stderr.write(
768 'warning: --slowtimeout option ignored with --debug\n'
768 'warning: --slowtimeout option ignored with --debug\n'
769 )
769 )
770 options.timeout = 0
770 options.timeout = 0
771 options.slowtimeout = 0
771 options.slowtimeout = 0
772
772
773 if options.blacklist:
773 if options.blacklist:
774 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
774 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
775 if options.whitelist:
775 if options.whitelist:
776 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
776 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
777 else:
777 else:
778 options.whitelisted = {}
778 options.whitelisted = {}
779
779
780 if options.showchannels:
780 if options.showchannels:
781 options.nodiff = True
781 options.nodiff = True
782
782
783 return options
783 return options
784
784
785
785
786 def rename(src, dst):
786 def rename(src, dst):
787 """Like os.rename(), trade atomicity and opened files friendliness
787 """Like os.rename(), trade atomicity and opened files friendliness
788 for existing destination support.
788 for existing destination support.
789 """
789 """
790 shutil.copy(src, dst)
790 shutil.copy(src, dst)
791 os.remove(src)
791 os.remove(src)
792
792
793
793
794 def makecleanable(path):
794 def makecleanable(path):
795 """Try to fix directory permission recursively so that the entire tree
795 """Try to fix directory permission recursively so that the entire tree
796 can be deleted"""
796 can be deleted"""
797 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
797 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
798 for d in dirnames:
798 for d in dirnames:
799 p = os.path.join(dirpath, d)
799 p = os.path.join(dirpath, d)
800 try:
800 try:
801 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
801 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
802 except OSError:
802 except OSError:
803 pass
803 pass
804
804
805
805
806 _unified_diff = difflib.unified_diff
806 _unified_diff = difflib.unified_diff
807 if PYTHON3:
807 if PYTHON3:
808 import functools
808 import functools
809
809
810 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
810 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
811
811
812
812
813 def getdiff(expected, output, ref, err):
813 def getdiff(expected, output, ref, err):
814 servefail = False
814 servefail = False
815 lines = []
815 lines = []
816 for line in _unified_diff(expected, output, ref, err):
816 for line in _unified_diff(expected, output, ref, err):
817 if line.startswith(b'+++') or line.startswith(b'---'):
817 if line.startswith(b'+++') or line.startswith(b'---'):
818 line = line.replace(b'\\', b'/')
818 line = line.replace(b'\\', b'/')
819 if line.endswith(b' \n'):
819 if line.endswith(b' \n'):
820 line = line[:-2] + b'\n'
820 line = line[:-2] + b'\n'
821 lines.append(line)
821 lines.append(line)
822 if not servefail and line.startswith(
822 if not servefail and line.startswith(
823 b'+ abort: child process failed to start'
823 b'+ abort: child process failed to start'
824 ):
824 ):
825 servefail = True
825 servefail = True
826
826
827 return servefail, lines
827 return servefail, lines
828
828
829
829
830 verbose = False
830 verbose = False
831
831
832
832
833 def vlog(*msg):
833 def vlog(*msg):
834 """Log only when in verbose mode."""
834 """Log only when in verbose mode."""
835 if verbose is False:
835 if verbose is False:
836 return
836 return
837
837
838 return log(*msg)
838 return log(*msg)
839
839
840
840
841 # Bytes that break XML even in a CDATA block: control characters 0-31
841 # Bytes that break XML even in a CDATA block: control characters 0-31
842 # sans \t, \n and \r
842 # sans \t, \n and \r
843 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
843 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
844
844
845 # Match feature conditionalized output lines in the form, capturing the feature
845 # Match feature conditionalized output lines in the form, capturing the feature
846 # list in group 2, and the preceeding line output in group 1:
846 # list in group 2, and the preceeding line output in group 1:
847 #
847 #
848 # output..output (feature !)\n
848 # output..output (feature !)\n
849 optline = re.compile(br'(.*) \((.+?) !\)\n$')
849 optline = re.compile(br'(.*) \((.+?) !\)\n$')
850
850
851
851
852 def cdatasafe(data):
852 def cdatasafe(data):
853 """Make a string safe to include in a CDATA block.
853 """Make a string safe to include in a CDATA block.
854
854
855 Certain control characters are illegal in a CDATA block, and
855 Certain control characters are illegal in a CDATA block, and
856 there's no way to include a ]]> in a CDATA either. This function
856 there's no way to include a ]]> in a CDATA either. This function
857 replaces illegal bytes with ? and adds a space between the ]] so
857 replaces illegal bytes with ? and adds a space between the ]] so
858 that it won't break the CDATA block.
858 that it won't break the CDATA block.
859 """
859 """
860 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
860 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
861
861
862
862
863 def log(*msg):
863 def log(*msg):
864 """Log something to stdout.
864 """Log something to stdout.
865
865
866 Arguments are strings to print.
866 Arguments are strings to print.
867 """
867 """
868 with iolock:
868 with iolock:
869 if verbose:
869 if verbose:
870 print(verbose, end=' ')
870 print(verbose, end=' ')
871 for m in msg:
871 for m in msg:
872 print(m, end=' ')
872 print(m, end=' ')
873 print()
873 print()
874 sys.stdout.flush()
874 sys.stdout.flush()
875
875
876
876
877 def highlightdiff(line, color):
877 def highlightdiff(line, color):
878 if not color:
878 if not color:
879 return line
879 return line
880 assert pygmentspresent
880 assert pygmentspresent
881 return pygments.highlight(
881 return pygments.highlight(
882 line.decode('latin1'), difflexer, terminal256formatter
882 line.decode('latin1'), difflexer, terminal256formatter
883 ).encode('latin1')
883 ).encode('latin1')
884
884
885
885
886 def highlightmsg(msg, color):
886 def highlightmsg(msg, color):
887 if not color:
887 if not color:
888 return msg
888 return msg
889 assert pygmentspresent
889 assert pygmentspresent
890 return pygments.highlight(msg, runnerlexer, runnerformatter)
890 return pygments.highlight(msg, runnerlexer, runnerformatter)
891
891
892
892
893 def terminate(proc):
893 def terminate(proc):
894 """Terminate subprocess"""
894 """Terminate subprocess"""
895 vlog('# Terminating process %d' % proc.pid)
895 vlog('# Terminating process %d' % proc.pid)
896 try:
896 try:
897 proc.terminate()
897 proc.terminate()
898 except OSError:
898 except OSError:
899 pass
899 pass
900
900
901
901
902 def killdaemons(pidfile):
902 def killdaemons(pidfile):
903 import killdaemons as killmod
903 import killdaemons as killmod
904
904
905 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
905 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
906
906
907
907
908 class Test(unittest.TestCase):
908 class Test(unittest.TestCase):
909 """Encapsulates a single, runnable test.
909 """Encapsulates a single, runnable test.
910
910
911 While this class conforms to the unittest.TestCase API, it differs in that
911 While this class conforms to the unittest.TestCase API, it differs in that
912 instances need to be instantiated manually. (Typically, unittest.TestCase
912 instances need to be instantiated manually. (Typically, unittest.TestCase
913 classes are instantiated automatically by scanning modules.)
913 classes are instantiated automatically by scanning modules.)
914 """
914 """
915
915
916 # Status code reserved for skipped tests (used by hghave).
916 # Status code reserved for skipped tests (used by hghave).
917 SKIPPED_STATUS = 80
917 SKIPPED_STATUS = 80
918
918
919 def __init__(
919 def __init__(
920 self,
920 self,
921 path,
921 path,
922 outputdir,
922 outputdir,
923 tmpdir,
923 tmpdir,
924 keeptmpdir=False,
924 keeptmpdir=False,
925 debug=False,
925 debug=False,
926 first=False,
926 first=False,
927 timeout=None,
927 timeout=None,
928 startport=None,
928 startport=None,
929 extraconfigopts=None,
929 extraconfigopts=None,
930 shell=None,
930 shell=None,
931 hgcommand=None,
931 hgcommand=None,
932 slowtimeout=None,
932 slowtimeout=None,
933 usechg=False,
933 usechg=False,
934 chgdebug=False,
934 chgdebug=False,
935 useipv6=False,
935 useipv6=False,
936 ):
936 ):
937 """Create a test from parameters.
937 """Create a test from parameters.
938
938
939 path is the full path to the file defining the test.
939 path is the full path to the file defining the test.
940
940
941 tmpdir is the main temporary directory to use for this test.
941 tmpdir is the main temporary directory to use for this test.
942
942
943 keeptmpdir determines whether to keep the test's temporary directory
943 keeptmpdir determines whether to keep the test's temporary directory
944 after execution. It defaults to removal (False).
944 after execution. It defaults to removal (False).
945
945
946 debug mode will make the test execute verbosely, with unfiltered
946 debug mode will make the test execute verbosely, with unfiltered
947 output.
947 output.
948
948
949 timeout controls the maximum run time of the test. It is ignored when
949 timeout controls the maximum run time of the test. It is ignored when
950 debug is True. See slowtimeout for tests with #require slow.
950 debug is True. See slowtimeout for tests with #require slow.
951
951
952 slowtimeout overrides timeout if the test has #require slow.
952 slowtimeout overrides timeout if the test has #require slow.
953
953
954 startport controls the starting port number to use for this test. Each
954 startport controls the starting port number to use for this test. Each
955 test will reserve 3 port numbers for execution. It is the caller's
955 test will reserve 3 port numbers for execution. It is the caller's
956 responsibility to allocate a non-overlapping port range to Test
956 responsibility to allocate a non-overlapping port range to Test
957 instances.
957 instances.
958
958
959 extraconfigopts is an iterable of extra hgrc config options. Values
959 extraconfigopts is an iterable of extra hgrc config options. Values
960 must have the form "key=value" (something understood by hgrc). Values
960 must have the form "key=value" (something understood by hgrc). Values
961 of the form "foo.key=value" will result in "[foo] key=value".
961 of the form "foo.key=value" will result in "[foo] key=value".
962
962
963 shell is the shell to execute tests in.
963 shell is the shell to execute tests in.
964 """
964 """
965 if timeout is None:
965 if timeout is None:
966 timeout = defaults['timeout']
966 timeout = defaults['timeout']
967 if startport is None:
967 if startport is None:
968 startport = defaults['port']
968 startport = defaults['port']
969 if slowtimeout is None:
969 if slowtimeout is None:
970 slowtimeout = defaults['slowtimeout']
970 slowtimeout = defaults['slowtimeout']
971 self.path = path
971 self.path = path
972 self.relpath = os.path.relpath(path)
972 self.relpath = os.path.relpath(path)
973 self.bname = os.path.basename(path)
973 self.bname = os.path.basename(path)
974 self.name = _bytes2sys(self.bname)
974 self.name = _bytes2sys(self.bname)
975 self._testdir = os.path.dirname(path)
975 self._testdir = os.path.dirname(path)
976 self._outputdir = outputdir
976 self._outputdir = outputdir
977 self._tmpname = os.path.basename(path)
977 self._tmpname = os.path.basename(path)
978 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
978 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
979
979
980 self._threadtmp = tmpdir
980 self._threadtmp = tmpdir
981 self._keeptmpdir = keeptmpdir
981 self._keeptmpdir = keeptmpdir
982 self._debug = debug
982 self._debug = debug
983 self._first = first
983 self._first = first
984 self._timeout = timeout
984 self._timeout = timeout
985 self._slowtimeout = slowtimeout
985 self._slowtimeout = slowtimeout
986 self._startport = startport
986 self._startport = startport
987 self._extraconfigopts = extraconfigopts or []
987 self._extraconfigopts = extraconfigopts or []
988 self._shell = _sys2bytes(shell)
988 self._shell = _sys2bytes(shell)
989 self._hgcommand = hgcommand or b'hg'
989 self._hgcommand = hgcommand or b'hg'
990 self._usechg = usechg
990 self._usechg = usechg
991 self._chgdebug = chgdebug
991 self._chgdebug = chgdebug
992 self._useipv6 = useipv6
992 self._useipv6 = useipv6
993
993
994 self._aborted = False
994 self._aborted = False
995 self._daemonpids = []
995 self._daemonpids = []
996 self._finished = None
996 self._finished = None
997 self._ret = None
997 self._ret = None
998 self._out = None
998 self._out = None
999 self._skipped = None
999 self._skipped = None
1000 self._testtmp = None
1000 self._testtmp = None
1001 self._chgsockdir = None
1001 self._chgsockdir = None
1002
1002
1003 self._refout = self.readrefout()
1003 self._refout = self.readrefout()
1004
1004
1005 def readrefout(self):
1005 def readrefout(self):
1006 """read reference output"""
1006 """read reference output"""
1007 # If we're not in --debug mode and reference output file exists,
1007 # If we're not in --debug mode and reference output file exists,
1008 # check test output against it.
1008 # check test output against it.
1009 if self._debug:
1009 if self._debug:
1010 return None # to match "out is None"
1010 return None # to match "out is None"
1011 elif os.path.exists(self.refpath):
1011 elif os.path.exists(self.refpath):
1012 with open(self.refpath, 'rb') as f:
1012 with open(self.refpath, 'rb') as f:
1013 return f.read().splitlines(True)
1013 return f.read().splitlines(True)
1014 else:
1014 else:
1015 return []
1015 return []
1016
1016
1017 # needed to get base class __repr__ running
1017 # needed to get base class __repr__ running
1018 @property
1018 @property
1019 def _testMethodName(self):
1019 def _testMethodName(self):
1020 return self.name
1020 return self.name
1021
1021
1022 def __str__(self):
1022 def __str__(self):
1023 return self.name
1023 return self.name
1024
1024
1025 def shortDescription(self):
1025 def shortDescription(self):
1026 return self.name
1026 return self.name
1027
1027
1028 def setUp(self):
1028 def setUp(self):
1029 """Tasks to perform before run()."""
1029 """Tasks to perform before run()."""
1030 self._finished = False
1030 self._finished = False
1031 self._ret = None
1031 self._ret = None
1032 self._out = None
1032 self._out = None
1033 self._skipped = None
1033 self._skipped = None
1034
1034
1035 try:
1035 try:
1036 os.mkdir(self._threadtmp)
1036 os.mkdir(self._threadtmp)
1037 except OSError as e:
1037 except OSError as e:
1038 if e.errno != errno.EEXIST:
1038 if e.errno != errno.EEXIST:
1039 raise
1039 raise
1040
1040
1041 name = self._tmpname
1041 name = self._tmpname
1042 self._testtmp = os.path.join(self._threadtmp, name)
1042 self._testtmp = os.path.join(self._threadtmp, name)
1043 os.mkdir(self._testtmp)
1043 os.mkdir(self._testtmp)
1044
1044
1045 # Remove any previous output files.
1045 # Remove any previous output files.
1046 if os.path.exists(self.errpath):
1046 if os.path.exists(self.errpath):
1047 try:
1047 try:
1048 os.remove(self.errpath)
1048 os.remove(self.errpath)
1049 except OSError as e:
1049 except OSError as e:
1050 # We might have raced another test to clean up a .err
1050 # We might have raced another test to clean up a .err
1051 # file, so ignore ENOENT when removing a previous .err
1051 # file, so ignore ENOENT when removing a previous .err
1052 # file.
1052 # file.
1053 if e.errno != errno.ENOENT:
1053 if e.errno != errno.ENOENT:
1054 raise
1054 raise
1055
1055
1056 if self._usechg:
1056 if self._usechg:
1057 self._chgsockdir = os.path.join(
1057 self._chgsockdir = os.path.join(
1058 self._threadtmp, b'%s.chgsock' % name
1058 self._threadtmp, b'%s.chgsock' % name
1059 )
1059 )
1060 os.mkdir(self._chgsockdir)
1060 os.mkdir(self._chgsockdir)
1061
1061
1062 def run(self, result):
1062 def run(self, result):
1063 """Run this test and report results against a TestResult instance."""
1063 """Run this test and report results against a TestResult instance."""
1064 # This function is extremely similar to unittest.TestCase.run(). Once
1064 # This function is extremely similar to unittest.TestCase.run(). Once
1065 # we require Python 2.7 (or at least its version of unittest), this
1065 # we require Python 2.7 (or at least its version of unittest), this
1066 # function can largely go away.
1066 # function can largely go away.
1067 self._result = result
1067 self._result = result
1068 result.startTest(self)
1068 result.startTest(self)
1069 try:
1069 try:
1070 try:
1070 try:
1071 self.setUp()
1071 self.setUp()
1072 except (KeyboardInterrupt, SystemExit):
1072 except (KeyboardInterrupt, SystemExit):
1073 self._aborted = True
1073 self._aborted = True
1074 raise
1074 raise
1075 except Exception:
1075 except Exception:
1076 result.addError(self, sys.exc_info())
1076 result.addError(self, sys.exc_info())
1077 return
1077 return
1078
1078
1079 success = False
1079 success = False
1080 try:
1080 try:
1081 self.runTest()
1081 self.runTest()
1082 except KeyboardInterrupt:
1082 except KeyboardInterrupt:
1083 self._aborted = True
1083 self._aborted = True
1084 raise
1084 raise
1085 except unittest.SkipTest as e:
1085 except unittest.SkipTest as e:
1086 result.addSkip(self, str(e))
1086 result.addSkip(self, str(e))
1087 # The base class will have already counted this as a
1087 # The base class will have already counted this as a
1088 # test we "ran", but we want to exclude skipped tests
1088 # test we "ran", but we want to exclude skipped tests
1089 # from those we count towards those run.
1089 # from those we count towards those run.
1090 result.testsRun -= 1
1090 result.testsRun -= 1
1091 except self.failureException as e:
1091 except self.failureException as e:
1092 # This differs from unittest in that we don't capture
1092 # This differs from unittest in that we don't capture
1093 # the stack trace. This is for historical reasons and
1093 # the stack trace. This is for historical reasons and
1094 # this decision could be revisited in the future,
1094 # this decision could be revisited in the future,
1095 # especially for PythonTest instances.
1095 # especially for PythonTest instances.
1096 if result.addFailure(self, str(e)):
1096 if result.addFailure(self, str(e)):
1097 success = True
1097 success = True
1098 except Exception:
1098 except Exception:
1099 result.addError(self, sys.exc_info())
1099 result.addError(self, sys.exc_info())
1100 else:
1100 else:
1101 success = True
1101 success = True
1102
1102
1103 try:
1103 try:
1104 self.tearDown()
1104 self.tearDown()
1105 except (KeyboardInterrupt, SystemExit):
1105 except (KeyboardInterrupt, SystemExit):
1106 self._aborted = True
1106 self._aborted = True
1107 raise
1107 raise
1108 except Exception:
1108 except Exception:
1109 result.addError(self, sys.exc_info())
1109 result.addError(self, sys.exc_info())
1110 success = False
1110 success = False
1111
1111
1112 if success:
1112 if success:
1113 result.addSuccess(self)
1113 result.addSuccess(self)
1114 finally:
1114 finally:
1115 result.stopTest(self, interrupted=self._aborted)
1115 result.stopTest(self, interrupted=self._aborted)
1116
1116
1117 def runTest(self):
1117 def runTest(self):
1118 """Run this test instance.
1118 """Run this test instance.
1119
1119
1120 This will return a tuple describing the result of the test.
1120 This will return a tuple describing the result of the test.
1121 """
1121 """
1122 env = self._getenv()
1122 env = self._getenv()
1123 self._genrestoreenv(env)
1123 self._genrestoreenv(env)
1124 self._daemonpids.append(env['DAEMON_PIDS'])
1124 self._daemonpids.append(env['DAEMON_PIDS'])
1125 self._createhgrc(env['HGRCPATH'])
1125 self._createhgrc(env['HGRCPATH'])
1126
1126
1127 vlog('# Test', self.name)
1127 vlog('# Test', self.name)
1128
1128
1129 ret, out = self._run(env)
1129 ret, out = self._run(env)
1130 self._finished = True
1130 self._finished = True
1131 self._ret = ret
1131 self._ret = ret
1132 self._out = out
1132 self._out = out
1133
1133
1134 def describe(ret):
1134 def describe(ret):
1135 if ret < 0:
1135 if ret < 0:
1136 return 'killed by signal: %d' % -ret
1136 return 'killed by signal: %d' % -ret
1137 return 'returned error code %d' % ret
1137 return 'returned error code %d' % ret
1138
1138
1139 self._skipped = False
1139 self._skipped = False
1140
1140
1141 if ret == self.SKIPPED_STATUS:
1141 if ret == self.SKIPPED_STATUS:
1142 if out is None: # Debug mode, nothing to parse.
1142 if out is None: # Debug mode, nothing to parse.
1143 missing = ['unknown']
1143 missing = ['unknown']
1144 failed = None
1144 failed = None
1145 else:
1145 else:
1146 missing, failed = TTest.parsehghaveoutput(out)
1146 missing, failed = TTest.parsehghaveoutput(out)
1147
1147
1148 if not missing:
1148 if not missing:
1149 missing = ['skipped']
1149 missing = ['skipped']
1150
1150
1151 if failed:
1151 if failed:
1152 self.fail('hg have failed checking for %s' % failed[-1])
1152 self.fail('hg have failed checking for %s' % failed[-1])
1153 else:
1153 else:
1154 self._skipped = True
1154 self._skipped = True
1155 raise unittest.SkipTest(missing[-1])
1155 raise unittest.SkipTest(missing[-1])
1156 elif ret == 'timeout':
1156 elif ret == 'timeout':
1157 self.fail('timed out')
1157 self.fail('timed out')
1158 elif ret is False:
1158 elif ret is False:
1159 self.fail('no result code from test')
1159 self.fail('no result code from test')
1160 elif out != self._refout:
1160 elif out != self._refout:
1161 # Diff generation may rely on written .err file.
1161 # Diff generation may rely on written .err file.
1162 if (
1162 if (
1163 (ret != 0 or out != self._refout)
1163 (ret != 0 or out != self._refout)
1164 and not self._skipped
1164 and not self._skipped
1165 and not self._debug
1165 and not self._debug
1166 ):
1166 ):
1167 with open(self.errpath, 'wb') as f:
1167 with open(self.errpath, 'wb') as f:
1168 for line in out:
1168 for line in out:
1169 f.write(line)
1169 f.write(line)
1170
1170
1171 # The result object handles diff calculation for us.
1171 # The result object handles diff calculation for us.
1172 with firstlock:
1172 with firstlock:
1173 if self._result.addOutputMismatch(self, ret, out, self._refout):
1173 if self._result.addOutputMismatch(self, ret, out, self._refout):
1174 # change was accepted, skip failing
1174 # change was accepted, skip failing
1175 return
1175 return
1176 if self._first:
1176 if self._first:
1177 global firsterror
1177 global firsterror
1178 firsterror = True
1178 firsterror = True
1179
1179
1180 if ret:
1180 if ret:
1181 msg = 'output changed and ' + describe(ret)
1181 msg = 'output changed and ' + describe(ret)
1182 else:
1182 else:
1183 msg = 'output changed'
1183 msg = 'output changed'
1184
1184
1185 self.fail(msg)
1185 self.fail(msg)
1186 elif ret:
1186 elif ret:
1187 self.fail(describe(ret))
1187 self.fail(describe(ret))
1188
1188
1189 def tearDown(self):
1189 def tearDown(self):
1190 """Tasks to perform after run()."""
1190 """Tasks to perform after run()."""
1191 for entry in self._daemonpids:
1191 for entry in self._daemonpids:
1192 killdaemons(entry)
1192 killdaemons(entry)
1193 self._daemonpids = []
1193 self._daemonpids = []
1194
1194
1195 if self._keeptmpdir:
1195 if self._keeptmpdir:
1196 log(
1196 log(
1197 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1197 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1198 % (
1198 % (
1199 _bytes2sys(self._testtmp),
1199 _bytes2sys(self._testtmp),
1200 _bytes2sys(self._threadtmp),
1200 _bytes2sys(self._threadtmp),
1201 )
1201 )
1202 )
1202 )
1203 else:
1203 else:
1204 try:
1204 try:
1205 shutil.rmtree(self._testtmp)
1205 shutil.rmtree(self._testtmp)
1206 except OSError:
1206 except OSError:
1207 # unreadable directory may be left in $TESTTMP; fix permission
1207 # unreadable directory may be left in $TESTTMP; fix permission
1208 # and try again
1208 # and try again
1209 makecleanable(self._testtmp)
1209 makecleanable(self._testtmp)
1210 shutil.rmtree(self._testtmp, True)
1210 shutil.rmtree(self._testtmp, True)
1211 shutil.rmtree(self._threadtmp, True)
1211 shutil.rmtree(self._threadtmp, True)
1212
1212
1213 if self._usechg:
1213 if self._usechg:
1214 # chgservers will stop automatically after they find the socket
1214 # chgservers will stop automatically after they find the socket
1215 # files are deleted
1215 # files are deleted
1216 shutil.rmtree(self._chgsockdir, True)
1216 shutil.rmtree(self._chgsockdir, True)
1217
1217
1218 if (
1218 if (
1219 (self._ret != 0 or self._out != self._refout)
1219 (self._ret != 0 or self._out != self._refout)
1220 and not self._skipped
1220 and not self._skipped
1221 and not self._debug
1221 and not self._debug
1222 and self._out
1222 and self._out
1223 ):
1223 ):
1224 with open(self.errpath, 'wb') as f:
1224 with open(self.errpath, 'wb') as f:
1225 for line in self._out:
1225 for line in self._out:
1226 f.write(line)
1226 f.write(line)
1227
1227
1228 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1228 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1229
1229
1230 def _run(self, env):
1230 def _run(self, env):
1231 # This should be implemented in child classes to run tests.
1231 # This should be implemented in child classes to run tests.
1232 raise unittest.SkipTest('unknown test type')
1232 raise unittest.SkipTest('unknown test type')
1233
1233
1234 def abort(self):
1234 def abort(self):
1235 """Terminate execution of this test."""
1235 """Terminate execution of this test."""
1236 self._aborted = True
1236 self._aborted = True
1237
1237
1238 def _portmap(self, i):
1238 def _portmap(self, i):
1239 offset = b'' if i == 0 else b'%d' % i
1239 offset = b'' if i == 0 else b'%d' % i
1240 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1240 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1241
1241
1242 def _getreplacements(self):
1242 def _getreplacements(self):
1243 """Obtain a mapping of text replacements to apply to test output.
1243 """Obtain a mapping of text replacements to apply to test output.
1244
1244
1245 Test output needs to be normalized so it can be compared to expected
1245 Test output needs to be normalized so it can be compared to expected
1246 output. This function defines how some of that normalization will
1246 output. This function defines how some of that normalization will
1247 occur.
1247 occur.
1248 """
1248 """
1249 r = [
1249 r = [
1250 # This list should be parallel to defineport in _getenv
1250 # This list should be parallel to defineport in _getenv
1251 self._portmap(0),
1251 self._portmap(0),
1252 self._portmap(1),
1252 self._portmap(1),
1253 self._portmap(2),
1253 self._portmap(2),
1254 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1254 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1255 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1255 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1256 ]
1256 ]
1257 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1257 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1258
1258
1259 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1259 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1260
1260
1261 if os.path.exists(replacementfile):
1261 if os.path.exists(replacementfile):
1262 data = {}
1262 data = {}
1263 with open(replacementfile, mode='rb') as source:
1263 with open(replacementfile, mode='rb') as source:
1264 # the intermediate 'compile' step help with debugging
1264 # the intermediate 'compile' step help with debugging
1265 code = compile(source.read(), replacementfile, 'exec')
1265 code = compile(source.read(), replacementfile, 'exec')
1266 exec(code, data)
1266 exec(code, data)
1267 for value in data.get('substitutions', ()):
1267 for value in data.get('substitutions', ()):
1268 if len(value) != 2:
1268 if len(value) != 2:
1269 msg = 'malformatted substitution in %s: %r'
1269 msg = 'malformatted substitution in %s: %r'
1270 msg %= (replacementfile, value)
1270 msg %= (replacementfile, value)
1271 raise ValueError(msg)
1271 raise ValueError(msg)
1272 r.append(value)
1272 r.append(value)
1273 return r
1273 return r
1274
1274
1275 def _escapepath(self, p):
1275 def _escapepath(self, p):
1276 if os.name == 'nt':
1276 if os.name == 'nt':
1277 return b''.join(
1277 return b''.join(
1278 c.isalpha()
1278 c.isalpha()
1279 and b'[%s%s]' % (c.lower(), c.upper())
1279 and b'[%s%s]' % (c.lower(), c.upper())
1280 or c in b'/\\'
1280 or c in b'/\\'
1281 and br'[/\\]'
1281 and br'[/\\]'
1282 or c.isdigit()
1282 or c.isdigit()
1283 and c
1283 and c
1284 or b'\\' + c
1284 or b'\\' + c
1285 for c in [p[i : i + 1] for i in range(len(p))]
1285 for c in [p[i : i + 1] for i in range(len(p))]
1286 )
1286 )
1287 else:
1287 else:
1288 return re.escape(p)
1288 return re.escape(p)
1289
1289
1290 def _localip(self):
1290 def _localip(self):
1291 if self._useipv6:
1291 if self._useipv6:
1292 return b'::1'
1292 return b'::1'
1293 else:
1293 else:
1294 return b'127.0.0.1'
1294 return b'127.0.0.1'
1295
1295
1296 def _genrestoreenv(self, testenv):
1296 def _genrestoreenv(self, testenv):
1297 """Generate a script that can be used by tests to restore the original
1297 """Generate a script that can be used by tests to restore the original
1298 environment."""
1298 environment."""
1299 # Put the restoreenv script inside self._threadtmp
1299 # Put the restoreenv script inside self._threadtmp
1300 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1300 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1301 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1301 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1302
1302
1303 # Only restore environment variable names that the shell allows
1303 # Only restore environment variable names that the shell allows
1304 # us to export.
1304 # us to export.
1305 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1305 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1306
1306
1307 # Do not restore these variables; otherwise tests would fail.
1307 # Do not restore these variables; otherwise tests would fail.
1308 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1308 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1309
1309
1310 with open(scriptpath, 'w') as envf:
1310 with open(scriptpath, 'w') as envf:
1311 for name, value in origenviron.items():
1311 for name, value in origenviron.items():
1312 if not name_regex.match(name):
1312 if not name_regex.match(name):
1313 # Skip environment variables with unusual names not
1313 # Skip environment variables with unusual names not
1314 # allowed by most shells.
1314 # allowed by most shells.
1315 continue
1315 continue
1316 if name in reqnames:
1316 if name in reqnames:
1317 continue
1317 continue
1318 envf.write('%s=%s\n' % (name, shellquote(value)))
1318 envf.write('%s=%s\n' % (name, shellquote(value)))
1319
1319
1320 for name in testenv:
1320 for name in testenv:
1321 if name in origenviron or name in reqnames:
1321 if name in origenviron or name in reqnames:
1322 continue
1322 continue
1323 envf.write('unset %s\n' % (name,))
1323 envf.write('unset %s\n' % (name,))
1324
1324
1325 def _getenv(self):
1325 def _getenv(self):
1326 """Obtain environment variables to use during test execution."""
1326 """Obtain environment variables to use during test execution."""
1327
1327
1328 def defineport(i):
1328 def defineport(i):
1329 offset = '' if i == 0 else '%s' % i
1329 offset = '' if i == 0 else '%s' % i
1330 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1330 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1331
1331
1332 env = os.environ.copy()
1332 env = os.environ.copy()
1333 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1333 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1334 env['HGEMITWARNINGS'] = '1'
1334 env['HGEMITWARNINGS'] = '1'
1335 env['TESTTMP'] = _bytes2sys(self._testtmp)
1335 env['TESTTMP'] = _bytes2sys(self._testtmp)
1336 env['TESTNAME'] = self.name
1336 env['TESTNAME'] = self.name
1337 env['HOME'] = _bytes2sys(self._testtmp)
1337 env['HOME'] = _bytes2sys(self._testtmp)
1338 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1338 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1339 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1339 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1340 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1340 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1341 # This number should match portneeded in _getport
1341 # This number should match portneeded in _getport
1342 for port in xrange(3):
1342 for port in xrange(3):
1343 # This list should be parallel to _portmap in _getreplacements
1343 # This list should be parallel to _portmap in _getreplacements
1344 defineport(port)
1344 defineport(port)
1345 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1345 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1346 env["DAEMON_PIDS"] = _bytes2sys(
1346 env["DAEMON_PIDS"] = _bytes2sys(
1347 os.path.join(self._threadtmp, b'daemon.pids')
1347 os.path.join(self._threadtmp, b'daemon.pids')
1348 )
1348 )
1349 env["HGEDITOR"] = (
1349 env["HGEDITOR"] = (
1350 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1350 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1351 )
1351 )
1352 env["HGUSER"] = "test"
1352 env["HGUSER"] = "test"
1353 env["HGENCODING"] = "ascii"
1353 env["HGENCODING"] = "ascii"
1354 env["HGENCODINGMODE"] = "strict"
1354 env["HGENCODINGMODE"] = "strict"
1355 env["HGHOSTNAME"] = "test-hostname"
1355 env["HGHOSTNAME"] = "test-hostname"
1356 env['HGIPV6'] = str(int(self._useipv6))
1356 env['HGIPV6'] = str(int(self._useipv6))
1357 # See contrib/catapipe.py for how to use this functionality.
1357 # See contrib/catapipe.py for how to use this functionality.
1358 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1358 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1359 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1359 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1360 # non-test one in as a default, otherwise set to devnull
1360 # non-test one in as a default, otherwise set to devnull
1361 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1361 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1362 'HGCATAPULTSERVERPIPE', os.devnull
1362 'HGCATAPULTSERVERPIPE', os.devnull
1363 )
1363 )
1364
1364
1365 extraextensions = []
1365 extraextensions = []
1366 for opt in self._extraconfigopts:
1366 for opt in self._extraconfigopts:
1367 section, key = _sys2bytes(opt).split(b'.', 1)
1367 section, key = _sys2bytes(opt).split(b'.', 1)
1368 if section != 'extensions':
1368 if section != 'extensions':
1369 continue
1369 continue
1370 name = key.split(b'=', 1)[0]
1370 name = key.split(b'=', 1)[0]
1371 extraextensions.append(name)
1371 extraextensions.append(name)
1372
1372
1373 if extraextensions:
1373 if extraextensions:
1374 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1374 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1375
1375
1376 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1376 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1377 # IP addresses.
1377 # IP addresses.
1378 env['LOCALIP'] = _bytes2sys(self._localip())
1378 env['LOCALIP'] = _bytes2sys(self._localip())
1379
1379
1380 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1380 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1381 # but this is needed for testing python instances like dummyssh,
1381 # but this is needed for testing python instances like dummyssh,
1382 # dummysmtpd.py, and dumbhttp.py.
1382 # dummysmtpd.py, and dumbhttp.py.
1383 if PYTHON3 and os.name == 'nt':
1383 if PYTHON3 and os.name == 'nt':
1384 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1384 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1385
1385
1386 # Modified HOME in test environment can confuse Rust tools. So set
1386 # Modified HOME in test environment can confuse Rust tools. So set
1387 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1387 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1388 # present and these variables aren't already defined.
1388 # present and these variables aren't already defined.
1389 cargo_home_path = os.path.expanduser('~/.cargo')
1389 cargo_home_path = os.path.expanduser('~/.cargo')
1390 rustup_home_path = os.path.expanduser('~/.rustup')
1390 rustup_home_path = os.path.expanduser('~/.rustup')
1391
1391
1392 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1392 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1393 env['CARGO_HOME'] = cargo_home_path
1393 env['CARGO_HOME'] = cargo_home_path
1394 if (
1394 if (
1395 os.path.exists(rustup_home_path)
1395 os.path.exists(rustup_home_path)
1396 and b'RUSTUP_HOME' not in osenvironb
1396 and b'RUSTUP_HOME' not in osenvironb
1397 ):
1397 ):
1398 env['RUSTUP_HOME'] = rustup_home_path
1398 env['RUSTUP_HOME'] = rustup_home_path
1399
1399
1400 # Reset some environment variables to well-known values so that
1400 # Reset some environment variables to well-known values so that
1401 # the tests produce repeatable output.
1401 # the tests produce repeatable output.
1402 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1402 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1403 env['TZ'] = 'GMT'
1403 env['TZ'] = 'GMT'
1404 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1404 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1405 env['COLUMNS'] = '80'
1405 env['COLUMNS'] = '80'
1406 env['TERM'] = 'xterm'
1406 env['TERM'] = 'xterm'
1407
1407
1408 dropped = [
1408 dropped = [
1409 'CDPATH',
1409 'CDPATH',
1410 'CHGDEBUG',
1410 'CHGDEBUG',
1411 'EDITOR',
1411 'EDITOR',
1412 'GREP_OPTIONS',
1412 'GREP_OPTIONS',
1413 'HG',
1413 'HG',
1414 'HGMERGE',
1414 'HGMERGE',
1415 'HGPLAIN',
1415 'HGPLAIN',
1416 'HGPLAINEXCEPT',
1416 'HGPLAINEXCEPT',
1417 'HGPROF',
1417 'HGPROF',
1418 'http_proxy',
1418 'http_proxy',
1419 'no_proxy',
1419 'no_proxy',
1420 'NO_PROXY',
1420 'NO_PROXY',
1421 'PAGER',
1421 'PAGER',
1422 'VISUAL',
1422 'VISUAL',
1423 ]
1423 ]
1424
1424
1425 for k in dropped:
1425 for k in dropped:
1426 if k in env:
1426 if k in env:
1427 del env[k]
1427 del env[k]
1428
1428
1429 # unset env related to hooks
1429 # unset env related to hooks
1430 for k in list(env):
1430 for k in list(env):
1431 if k.startswith('HG_'):
1431 if k.startswith('HG_'):
1432 del env[k]
1432 del env[k]
1433
1433
1434 if self._usechg:
1434 if self._usechg:
1435 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1435 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1436 if self._chgdebug:
1436 if self._chgdebug:
1437 env['CHGDEBUG'] = 'true'
1437 env['CHGDEBUG'] = 'true'
1438
1438
1439 return env
1439 return env
1440
1440
1441 def _createhgrc(self, path):
1441 def _createhgrc(self, path):
1442 """Create an hgrc file for this test."""
1442 """Create an hgrc file for this test."""
1443 with open(path, 'wb') as hgrc:
1443 with open(path, 'wb') as hgrc:
1444 hgrc.write(b'[ui]\n')
1444 hgrc.write(b'[ui]\n')
1445 hgrc.write(b'slash = True\n')
1445 hgrc.write(b'slash = True\n')
1446 hgrc.write(b'interactive = False\n')
1446 hgrc.write(b'interactive = False\n')
1447 hgrc.write(b'detailed-exit-code = True\n')
1447 hgrc.write(b'detailed-exit-code = True\n')
1448 hgrc.write(b'merge = internal:merge\n')
1448 hgrc.write(b'merge = internal:merge\n')
1449 hgrc.write(b'mergemarkers = detailed\n')
1449 hgrc.write(b'mergemarkers = detailed\n')
1450 hgrc.write(b'promptecho = True\n')
1450 hgrc.write(b'promptecho = True\n')
1451 hgrc.write(b'timeout.warn=15\n')
1451 hgrc.write(b'[defaults]\n')
1452 hgrc.write(b'[defaults]\n')
1452 hgrc.write(b'[devel]\n')
1453 hgrc.write(b'[devel]\n')
1453 hgrc.write(b'all-warnings = true\n')
1454 hgrc.write(b'all-warnings = true\n')
1454 hgrc.write(b'default-date = 0 0\n')
1455 hgrc.write(b'default-date = 0 0\n')
1455 hgrc.write(b'[largefiles]\n')
1456 hgrc.write(b'[largefiles]\n')
1456 hgrc.write(
1457 hgrc.write(
1457 b'usercache = %s\n'
1458 b'usercache = %s\n'
1458 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1459 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1459 )
1460 )
1460 hgrc.write(b'[lfs]\n')
1461 hgrc.write(b'[lfs]\n')
1461 hgrc.write(
1462 hgrc.write(
1462 b'usercache = %s\n'
1463 b'usercache = %s\n'
1463 % (os.path.join(self._testtmp, b'.cache/lfs'))
1464 % (os.path.join(self._testtmp, b'.cache/lfs'))
1464 )
1465 )
1465 hgrc.write(b'[web]\n')
1466 hgrc.write(b'[web]\n')
1466 hgrc.write(b'address = localhost\n')
1467 hgrc.write(b'address = localhost\n')
1467 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1468 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1468 hgrc.write(b'server-header = testing stub value\n')
1469 hgrc.write(b'server-header = testing stub value\n')
1469
1470
1470 for opt in self._extraconfigopts:
1471 for opt in self._extraconfigopts:
1471 section, key = _sys2bytes(opt).split(b'.', 1)
1472 section, key = _sys2bytes(opt).split(b'.', 1)
1472 assert b'=' in key, (
1473 assert b'=' in key, (
1473 'extra config opt %s must ' 'have an = for assignment' % opt
1474 'extra config opt %s must ' 'have an = for assignment' % opt
1474 )
1475 )
1475 hgrc.write(b'[%s]\n%s\n' % (section, key))
1476 hgrc.write(b'[%s]\n%s\n' % (section, key))
1476
1477
1477 def fail(self, msg):
1478 def fail(self, msg):
1478 # unittest differentiates between errored and failed.
1479 # unittest differentiates between errored and failed.
1479 # Failed is denoted by AssertionError (by default at least).
1480 # Failed is denoted by AssertionError (by default at least).
1480 raise AssertionError(msg)
1481 raise AssertionError(msg)
1481
1482
1482 def _runcommand(self, cmd, env, normalizenewlines=False):
1483 def _runcommand(self, cmd, env, normalizenewlines=False):
1483 """Run command in a sub-process, capturing the output (stdout and
1484 """Run command in a sub-process, capturing the output (stdout and
1484 stderr).
1485 stderr).
1485
1486
1486 Return a tuple (exitcode, output). output is None in debug mode.
1487 Return a tuple (exitcode, output). output is None in debug mode.
1487 """
1488 """
1488 if self._debug:
1489 if self._debug:
1489 proc = subprocess.Popen(
1490 proc = subprocess.Popen(
1490 _bytes2sys(cmd),
1491 _bytes2sys(cmd),
1491 shell=True,
1492 shell=True,
1492 cwd=_bytes2sys(self._testtmp),
1493 cwd=_bytes2sys(self._testtmp),
1493 env=env,
1494 env=env,
1494 )
1495 )
1495 ret = proc.wait()
1496 ret = proc.wait()
1496 return (ret, None)
1497 return (ret, None)
1497
1498
1498 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1499 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1499
1500
1500 def cleanup():
1501 def cleanup():
1501 terminate(proc)
1502 terminate(proc)
1502 ret = proc.wait()
1503 ret = proc.wait()
1503 if ret == 0:
1504 if ret == 0:
1504 ret = signal.SIGTERM << 8
1505 ret = signal.SIGTERM << 8
1505 killdaemons(env['DAEMON_PIDS'])
1506 killdaemons(env['DAEMON_PIDS'])
1506 return ret
1507 return ret
1507
1508
1508 proc.tochild.close()
1509 proc.tochild.close()
1509
1510
1510 try:
1511 try:
1511 output = proc.fromchild.read()
1512 output = proc.fromchild.read()
1512 except KeyboardInterrupt:
1513 except KeyboardInterrupt:
1513 vlog('# Handling keyboard interrupt')
1514 vlog('# Handling keyboard interrupt')
1514 cleanup()
1515 cleanup()
1515 raise
1516 raise
1516
1517
1517 ret = proc.wait()
1518 ret = proc.wait()
1518 if wifexited(ret):
1519 if wifexited(ret):
1519 ret = os.WEXITSTATUS(ret)
1520 ret = os.WEXITSTATUS(ret)
1520
1521
1521 if proc.timeout:
1522 if proc.timeout:
1522 ret = 'timeout'
1523 ret = 'timeout'
1523
1524
1524 if ret:
1525 if ret:
1525 killdaemons(env['DAEMON_PIDS'])
1526 killdaemons(env['DAEMON_PIDS'])
1526
1527
1527 for s, r in self._getreplacements():
1528 for s, r in self._getreplacements():
1528 output = re.sub(s, r, output)
1529 output = re.sub(s, r, output)
1529
1530
1530 if normalizenewlines:
1531 if normalizenewlines:
1531 output = output.replace(b'\r\n', b'\n')
1532 output = output.replace(b'\r\n', b'\n')
1532
1533
1533 return ret, output.splitlines(True)
1534 return ret, output.splitlines(True)
1534
1535
1535
1536
1536 class PythonTest(Test):
1537 class PythonTest(Test):
1537 """A Python-based test."""
1538 """A Python-based test."""
1538
1539
1539 @property
1540 @property
1540 def refpath(self):
1541 def refpath(self):
1541 return os.path.join(self._testdir, b'%s.out' % self.bname)
1542 return os.path.join(self._testdir, b'%s.out' % self.bname)
1542
1543
1543 def _run(self, env):
1544 def _run(self, env):
1544 # Quote the python(3) executable for Windows
1545 # Quote the python(3) executable for Windows
1545 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1546 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1546 vlog("# Running", cmd.decode("utf-8"))
1547 vlog("# Running", cmd.decode("utf-8"))
1547 normalizenewlines = os.name == 'nt'
1548 normalizenewlines = os.name == 'nt'
1548 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1549 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1549 if self._aborted:
1550 if self._aborted:
1550 raise KeyboardInterrupt()
1551 raise KeyboardInterrupt()
1551
1552
1552 return result
1553 return result
1553
1554
1554
1555
1555 # Some glob patterns apply only in some circumstances, so the script
1556 # Some glob patterns apply only in some circumstances, so the script
1556 # might want to remove (glob) annotations that otherwise should be
1557 # might want to remove (glob) annotations that otherwise should be
1557 # retained.
1558 # retained.
1558 checkcodeglobpats = [
1559 checkcodeglobpats = [
1559 # On Windows it looks like \ doesn't require a (glob), but we know
1560 # On Windows it looks like \ doesn't require a (glob), but we know
1560 # better.
1561 # better.
1561 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1562 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1562 re.compile(br'^moving \S+/.*[^)]$'),
1563 re.compile(br'^moving \S+/.*[^)]$'),
1563 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1564 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1564 # Not all platforms have 127.0.0.1 as loopback (though most do),
1565 # Not all platforms have 127.0.0.1 as loopback (though most do),
1565 # so we always glob that too.
1566 # so we always glob that too.
1566 re.compile(br'.*\$LOCALIP.*$'),
1567 re.compile(br'.*\$LOCALIP.*$'),
1567 ]
1568 ]
1568
1569
1569 bchr = chr
1570 bchr = chr
1570 if PYTHON3:
1571 if PYTHON3:
1571 bchr = lambda x: bytes([x])
1572 bchr = lambda x: bytes([x])
1572
1573
1573 WARN_UNDEFINED = 1
1574 WARN_UNDEFINED = 1
1574 WARN_YES = 2
1575 WARN_YES = 2
1575 WARN_NO = 3
1576 WARN_NO = 3
1576
1577
1577 MARK_OPTIONAL = b" (?)\n"
1578 MARK_OPTIONAL = b" (?)\n"
1578
1579
1579
1580
1580 def isoptional(line):
1581 def isoptional(line):
1581 return line.endswith(MARK_OPTIONAL)
1582 return line.endswith(MARK_OPTIONAL)
1582
1583
1583
1584
1584 class TTest(Test):
1585 class TTest(Test):
1585 """A "t test" is a test backed by a .t file."""
1586 """A "t test" is a test backed by a .t file."""
1586
1587
1587 SKIPPED_PREFIX = b'skipped: '
1588 SKIPPED_PREFIX = b'skipped: '
1588 FAILED_PREFIX = b'hghave check failed: '
1589 FAILED_PREFIX = b'hghave check failed: '
1589 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1590 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1590
1591
1591 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1592 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1592 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1593 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1593 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1594 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1594
1595
1595 def __init__(self, path, *args, **kwds):
1596 def __init__(self, path, *args, **kwds):
1596 # accept an extra "case" parameter
1597 # accept an extra "case" parameter
1597 case = kwds.pop('case', [])
1598 case = kwds.pop('case', [])
1598 self._case = case
1599 self._case = case
1599 self._allcases = {x for y in parsettestcases(path) for x in y}
1600 self._allcases = {x for y in parsettestcases(path) for x in y}
1600 super(TTest, self).__init__(path, *args, **kwds)
1601 super(TTest, self).__init__(path, *args, **kwds)
1601 if case:
1602 if case:
1602 casepath = b'#'.join(case)
1603 casepath = b'#'.join(case)
1603 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1604 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1604 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1605 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1605 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1606 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1606 self._have = {}
1607 self._have = {}
1607
1608
1608 @property
1609 @property
1609 def refpath(self):
1610 def refpath(self):
1610 return os.path.join(self._testdir, self.bname)
1611 return os.path.join(self._testdir, self.bname)
1611
1612
1612 def _run(self, env):
1613 def _run(self, env):
1613 with open(self.path, 'rb') as f:
1614 with open(self.path, 'rb') as f:
1614 lines = f.readlines()
1615 lines = f.readlines()
1615
1616
1616 # .t file is both reference output and the test input, keep reference
1617 # .t file is both reference output and the test input, keep reference
1617 # output updated with the the test input. This avoids some race
1618 # output updated with the the test input. This avoids some race
1618 # conditions where the reference output does not match the actual test.
1619 # conditions where the reference output does not match the actual test.
1619 if self._refout is not None:
1620 if self._refout is not None:
1620 self._refout = lines
1621 self._refout = lines
1621
1622
1622 salt, script, after, expected = self._parsetest(lines)
1623 salt, script, after, expected = self._parsetest(lines)
1623
1624
1624 # Write out the generated script.
1625 # Write out the generated script.
1625 fname = b'%s.sh' % self._testtmp
1626 fname = b'%s.sh' % self._testtmp
1626 with open(fname, 'wb') as f:
1627 with open(fname, 'wb') as f:
1627 for l in script:
1628 for l in script:
1628 f.write(l)
1629 f.write(l)
1629
1630
1630 cmd = b'%s "%s"' % (self._shell, fname)
1631 cmd = b'%s "%s"' % (self._shell, fname)
1631 vlog("# Running", cmd.decode("utf-8"))
1632 vlog("# Running", cmd.decode("utf-8"))
1632
1633
1633 exitcode, output = self._runcommand(cmd, env)
1634 exitcode, output = self._runcommand(cmd, env)
1634
1635
1635 if self._aborted:
1636 if self._aborted:
1636 raise KeyboardInterrupt()
1637 raise KeyboardInterrupt()
1637
1638
1638 # Do not merge output if skipped. Return hghave message instead.
1639 # Do not merge output if skipped. Return hghave message instead.
1639 # Similarly, with --debug, output is None.
1640 # Similarly, with --debug, output is None.
1640 if exitcode == self.SKIPPED_STATUS or output is None:
1641 if exitcode == self.SKIPPED_STATUS or output is None:
1641 return exitcode, output
1642 return exitcode, output
1642
1643
1643 return self._processoutput(exitcode, output, salt, after, expected)
1644 return self._processoutput(exitcode, output, salt, after, expected)
1644
1645
1645 def _hghave(self, reqs):
1646 def _hghave(self, reqs):
1646 allreqs = b' '.join(reqs)
1647 allreqs = b' '.join(reqs)
1647
1648
1648 self._detectslow(reqs)
1649 self._detectslow(reqs)
1649
1650
1650 if allreqs in self._have:
1651 if allreqs in self._have:
1651 return self._have.get(allreqs)
1652 return self._have.get(allreqs)
1652
1653
1653 # TODO do something smarter when all other uses of hghave are gone.
1654 # TODO do something smarter when all other uses of hghave are gone.
1654 runtestdir = osenvironb[b'RUNTESTDIR']
1655 runtestdir = osenvironb[b'RUNTESTDIR']
1655 tdir = runtestdir.replace(b'\\', b'/')
1656 tdir = runtestdir.replace(b'\\', b'/')
1656 proc = Popen4(
1657 proc = Popen4(
1657 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1658 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1658 self._testtmp,
1659 self._testtmp,
1659 0,
1660 0,
1660 self._getenv(),
1661 self._getenv(),
1661 )
1662 )
1662 stdout, stderr = proc.communicate()
1663 stdout, stderr = proc.communicate()
1663 ret = proc.wait()
1664 ret = proc.wait()
1664 if wifexited(ret):
1665 if wifexited(ret):
1665 ret = os.WEXITSTATUS(ret)
1666 ret = os.WEXITSTATUS(ret)
1666 if ret == 2:
1667 if ret == 2:
1667 print(stdout.decode('utf-8'))
1668 print(stdout.decode('utf-8'))
1668 sys.exit(1)
1669 sys.exit(1)
1669
1670
1670 if ret != 0:
1671 if ret != 0:
1671 self._have[allreqs] = (False, stdout)
1672 self._have[allreqs] = (False, stdout)
1672 return False, stdout
1673 return False, stdout
1673
1674
1674 self._have[allreqs] = (True, None)
1675 self._have[allreqs] = (True, None)
1675 return True, None
1676 return True, None
1676
1677
1677 def _detectslow(self, reqs):
1678 def _detectslow(self, reqs):
1678 """update the timeout of slow test when appropriate"""
1679 """update the timeout of slow test when appropriate"""
1679 if b'slow' in reqs:
1680 if b'slow' in reqs:
1680 self._timeout = self._slowtimeout
1681 self._timeout = self._slowtimeout
1681
1682
1682 def _iftest(self, args):
1683 def _iftest(self, args):
1683 # implements "#if"
1684 # implements "#if"
1684 reqs = []
1685 reqs = []
1685 for arg in args:
1686 for arg in args:
1686 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1687 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1687 if arg[3:] in self._case:
1688 if arg[3:] in self._case:
1688 return False
1689 return False
1689 elif arg in self._allcases:
1690 elif arg in self._allcases:
1690 if arg not in self._case:
1691 if arg not in self._case:
1691 return False
1692 return False
1692 else:
1693 else:
1693 reqs.append(arg)
1694 reqs.append(arg)
1694 self._detectslow(reqs)
1695 self._detectslow(reqs)
1695 return self._hghave(reqs)[0]
1696 return self._hghave(reqs)[0]
1696
1697
1697 def _parsetest(self, lines):
1698 def _parsetest(self, lines):
1698 # We generate a shell script which outputs unique markers to line
1699 # We generate a shell script which outputs unique markers to line
1699 # up script results with our source. These markers include input
1700 # up script results with our source. These markers include input
1700 # line number and the last return code.
1701 # line number and the last return code.
1701 salt = b"SALT%d" % time.time()
1702 salt = b"SALT%d" % time.time()
1702
1703
1703 def addsalt(line, inpython):
1704 def addsalt(line, inpython):
1704 if inpython:
1705 if inpython:
1705 script.append(b'%s %d 0\n' % (salt, line))
1706 script.append(b'%s %d 0\n' % (salt, line))
1706 else:
1707 else:
1707 script.append(b'echo %s %d $?\n' % (salt, line))
1708 script.append(b'echo %s %d $?\n' % (salt, line))
1708
1709
1709 activetrace = []
1710 activetrace = []
1710 session = str(uuid.uuid4())
1711 session = str(uuid.uuid4())
1711 if PYTHON3:
1712 if PYTHON3:
1712 session = session.encode('ascii')
1713 session = session.encode('ascii')
1713 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1714 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1714 'HGCATAPULTSERVERPIPE'
1715 'HGCATAPULTSERVERPIPE'
1715 )
1716 )
1716
1717
1717 def toggletrace(cmd=None):
1718 def toggletrace(cmd=None):
1718 if not hgcatapult or hgcatapult == os.devnull:
1719 if not hgcatapult or hgcatapult == os.devnull:
1719 return
1720 return
1720
1721
1721 if activetrace:
1722 if activetrace:
1722 script.append(
1723 script.append(
1723 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1724 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1724 % (session, activetrace[0])
1725 % (session, activetrace[0])
1725 )
1726 )
1726 if cmd is None:
1727 if cmd is None:
1727 return
1728 return
1728
1729
1729 if isinstance(cmd, str):
1730 if isinstance(cmd, str):
1730 quoted = shellquote(cmd.strip())
1731 quoted = shellquote(cmd.strip())
1731 else:
1732 else:
1732 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1733 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1733 quoted = quoted.replace(b'\\', b'\\\\')
1734 quoted = quoted.replace(b'\\', b'\\\\')
1734 script.append(
1735 script.append(
1735 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1736 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1736 % (session, quoted)
1737 % (session, quoted)
1737 )
1738 )
1738 activetrace[0:] = [quoted]
1739 activetrace[0:] = [quoted]
1739
1740
1740 script = []
1741 script = []
1741
1742
1742 # After we run the shell script, we re-unify the script output
1743 # After we run the shell script, we re-unify the script output
1743 # with non-active parts of the source, with synchronization by our
1744 # with non-active parts of the source, with synchronization by our
1744 # SALT line number markers. The after table contains the non-active
1745 # SALT line number markers. The after table contains the non-active
1745 # components, ordered by line number.
1746 # components, ordered by line number.
1746 after = {}
1747 after = {}
1747
1748
1748 # Expected shell script output.
1749 # Expected shell script output.
1749 expected = {}
1750 expected = {}
1750
1751
1751 pos = prepos = -1
1752 pos = prepos = -1
1752
1753
1753 # True or False when in a true or false conditional section
1754 # True or False when in a true or false conditional section
1754 skipping = None
1755 skipping = None
1755
1756
1756 # We keep track of whether or not we're in a Python block so we
1757 # We keep track of whether or not we're in a Python block so we
1757 # can generate the surrounding doctest magic.
1758 # can generate the surrounding doctest magic.
1758 inpython = False
1759 inpython = False
1759
1760
1760 if self._debug:
1761 if self._debug:
1761 script.append(b'set -x\n')
1762 script.append(b'set -x\n')
1762 if self._hgcommand != b'hg':
1763 if self._hgcommand != b'hg':
1763 script.append(b'alias hg="%s"\n' % self._hgcommand)
1764 script.append(b'alias hg="%s"\n' % self._hgcommand)
1764 if os.getenv('MSYSTEM'):
1765 if os.getenv('MSYSTEM'):
1765 script.append(b'alias pwd="pwd -W"\n')
1766 script.append(b'alias pwd="pwd -W"\n')
1766
1767
1767 if hgcatapult and hgcatapult != os.devnull:
1768 if hgcatapult and hgcatapult != os.devnull:
1768 if PYTHON3:
1769 if PYTHON3:
1769 hgcatapult = hgcatapult.encode('utf8')
1770 hgcatapult = hgcatapult.encode('utf8')
1770 cataname = self.name.encode('utf8')
1771 cataname = self.name.encode('utf8')
1771 else:
1772 else:
1772 cataname = self.name
1773 cataname = self.name
1773
1774
1774 # Kludge: use a while loop to keep the pipe from getting
1775 # Kludge: use a while loop to keep the pipe from getting
1775 # closed by our echo commands. The still-running file gets
1776 # closed by our echo commands. The still-running file gets
1776 # reaped at the end of the script, which causes the while
1777 # reaped at the end of the script, which causes the while
1777 # loop to exit and closes the pipe. Sigh.
1778 # loop to exit and closes the pipe. Sigh.
1778 script.append(
1779 script.append(
1779 b'rtendtracing() {\n'
1780 b'rtendtracing() {\n'
1780 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1781 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1781 b' rm -f "$TESTTMP/.still-running"\n'
1782 b' rm -f "$TESTTMP/.still-running"\n'
1782 b'}\n'
1783 b'}\n'
1783 b'trap "rtendtracing" 0\n'
1784 b'trap "rtendtracing" 0\n'
1784 b'touch "$TESTTMP/.still-running"\n'
1785 b'touch "$TESTTMP/.still-running"\n'
1785 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1786 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1786 b'> %(catapult)s &\n'
1787 b'> %(catapult)s &\n'
1787 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1788 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1788 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1789 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1789 % {
1790 % {
1790 b'name': cataname,
1791 b'name': cataname,
1791 b'session': session,
1792 b'session': session,
1792 b'catapult': hgcatapult,
1793 b'catapult': hgcatapult,
1793 }
1794 }
1794 )
1795 )
1795
1796
1796 if self._case:
1797 if self._case:
1797 casestr = b'#'.join(self._case)
1798 casestr = b'#'.join(self._case)
1798 if isinstance(casestr, str):
1799 if isinstance(casestr, str):
1799 quoted = shellquote(casestr)
1800 quoted = shellquote(casestr)
1800 else:
1801 else:
1801 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1802 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1802 script.append(b'TESTCASE=%s\n' % quoted)
1803 script.append(b'TESTCASE=%s\n' % quoted)
1803 script.append(b'export TESTCASE\n')
1804 script.append(b'export TESTCASE\n')
1804
1805
1805 n = 0
1806 n = 0
1806 for n, l in enumerate(lines):
1807 for n, l in enumerate(lines):
1807 if not l.endswith(b'\n'):
1808 if not l.endswith(b'\n'):
1808 l += b'\n'
1809 l += b'\n'
1809 if l.startswith(b'#require'):
1810 if l.startswith(b'#require'):
1810 lsplit = l.split()
1811 lsplit = l.split()
1811 if len(lsplit) < 2 or lsplit[0] != b'#require':
1812 if len(lsplit) < 2 or lsplit[0] != b'#require':
1812 after.setdefault(pos, []).append(
1813 after.setdefault(pos, []).append(
1813 b' !!! invalid #require\n'
1814 b' !!! invalid #require\n'
1814 )
1815 )
1815 if not skipping:
1816 if not skipping:
1816 haveresult, message = self._hghave(lsplit[1:])
1817 haveresult, message = self._hghave(lsplit[1:])
1817 if not haveresult:
1818 if not haveresult:
1818 script = [b'echo "%s"\nexit 80\n' % message]
1819 script = [b'echo "%s"\nexit 80\n' % message]
1819 break
1820 break
1820 after.setdefault(pos, []).append(l)
1821 after.setdefault(pos, []).append(l)
1821 elif l.startswith(b'#if'):
1822 elif l.startswith(b'#if'):
1822 lsplit = l.split()
1823 lsplit = l.split()
1823 if len(lsplit) < 2 or lsplit[0] != b'#if':
1824 if len(lsplit) < 2 or lsplit[0] != b'#if':
1824 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1825 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1825 if skipping is not None:
1826 if skipping is not None:
1826 after.setdefault(pos, []).append(b' !!! nested #if\n')
1827 after.setdefault(pos, []).append(b' !!! nested #if\n')
1827 skipping = not self._iftest(lsplit[1:])
1828 skipping = not self._iftest(lsplit[1:])
1828 after.setdefault(pos, []).append(l)
1829 after.setdefault(pos, []).append(l)
1829 elif l.startswith(b'#else'):
1830 elif l.startswith(b'#else'):
1830 if skipping is None:
1831 if skipping is None:
1831 after.setdefault(pos, []).append(b' !!! missing #if\n')
1832 after.setdefault(pos, []).append(b' !!! missing #if\n')
1832 skipping = not skipping
1833 skipping = not skipping
1833 after.setdefault(pos, []).append(l)
1834 after.setdefault(pos, []).append(l)
1834 elif l.startswith(b'#endif'):
1835 elif l.startswith(b'#endif'):
1835 if skipping is None:
1836 if skipping is None:
1836 after.setdefault(pos, []).append(b' !!! missing #if\n')
1837 after.setdefault(pos, []).append(b' !!! missing #if\n')
1837 skipping = None
1838 skipping = None
1838 after.setdefault(pos, []).append(l)
1839 after.setdefault(pos, []).append(l)
1839 elif skipping:
1840 elif skipping:
1840 after.setdefault(pos, []).append(l)
1841 after.setdefault(pos, []).append(l)
1841 elif l.startswith(b' >>> '): # python inlines
1842 elif l.startswith(b' >>> '): # python inlines
1842 after.setdefault(pos, []).append(l)
1843 after.setdefault(pos, []).append(l)
1843 prepos = pos
1844 prepos = pos
1844 pos = n
1845 pos = n
1845 if not inpython:
1846 if not inpython:
1846 # We've just entered a Python block. Add the header.
1847 # We've just entered a Python block. Add the header.
1847 inpython = True
1848 inpython = True
1848 addsalt(prepos, False) # Make sure we report the exit code.
1849 addsalt(prepos, False) # Make sure we report the exit code.
1849 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1850 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1850 addsalt(n, True)
1851 addsalt(n, True)
1851 script.append(l[2:])
1852 script.append(l[2:])
1852 elif l.startswith(b' ... '): # python inlines
1853 elif l.startswith(b' ... '): # python inlines
1853 after.setdefault(prepos, []).append(l)
1854 after.setdefault(prepos, []).append(l)
1854 script.append(l[2:])
1855 script.append(l[2:])
1855 elif l.startswith(b' $ '): # commands
1856 elif l.startswith(b' $ '): # commands
1856 if inpython:
1857 if inpython:
1857 script.append(b'EOF\n')
1858 script.append(b'EOF\n')
1858 inpython = False
1859 inpython = False
1859 after.setdefault(pos, []).append(l)
1860 after.setdefault(pos, []).append(l)
1860 prepos = pos
1861 prepos = pos
1861 pos = n
1862 pos = n
1862 addsalt(n, False)
1863 addsalt(n, False)
1863 rawcmd = l[4:]
1864 rawcmd = l[4:]
1864 cmd = rawcmd.split()
1865 cmd = rawcmd.split()
1865 toggletrace(rawcmd)
1866 toggletrace(rawcmd)
1866 if len(cmd) == 2 and cmd[0] == b'cd':
1867 if len(cmd) == 2 and cmd[0] == b'cd':
1867 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1868 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1868 script.append(rawcmd)
1869 script.append(rawcmd)
1869 elif l.startswith(b' > '): # continuations
1870 elif l.startswith(b' > '): # continuations
1870 after.setdefault(prepos, []).append(l)
1871 after.setdefault(prepos, []).append(l)
1871 script.append(l[4:])
1872 script.append(l[4:])
1872 elif l.startswith(b' '): # results
1873 elif l.startswith(b' '): # results
1873 # Queue up a list of expected results.
1874 # Queue up a list of expected results.
1874 expected.setdefault(pos, []).append(l[2:])
1875 expected.setdefault(pos, []).append(l[2:])
1875 else:
1876 else:
1876 if inpython:
1877 if inpython:
1877 script.append(b'EOF\n')
1878 script.append(b'EOF\n')
1878 inpython = False
1879 inpython = False
1879 # Non-command/result. Queue up for merged output.
1880 # Non-command/result. Queue up for merged output.
1880 after.setdefault(pos, []).append(l)
1881 after.setdefault(pos, []).append(l)
1881
1882
1882 if inpython:
1883 if inpython:
1883 script.append(b'EOF\n')
1884 script.append(b'EOF\n')
1884 if skipping is not None:
1885 if skipping is not None:
1885 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1886 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1886 addsalt(n + 1, False)
1887 addsalt(n + 1, False)
1887 # Need to end any current per-command trace
1888 # Need to end any current per-command trace
1888 if activetrace:
1889 if activetrace:
1889 toggletrace()
1890 toggletrace()
1890 return salt, script, after, expected
1891 return salt, script, after, expected
1891
1892
1892 def _processoutput(self, exitcode, output, salt, after, expected):
1893 def _processoutput(self, exitcode, output, salt, after, expected):
1893 # Merge the script output back into a unified test.
1894 # Merge the script output back into a unified test.
1894 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1895 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1895 if exitcode != 0:
1896 if exitcode != 0:
1896 warnonly = WARN_NO
1897 warnonly = WARN_NO
1897
1898
1898 pos = -1
1899 pos = -1
1899 postout = []
1900 postout = []
1900 for out_rawline in output:
1901 for out_rawline in output:
1901 out_line, cmd_line = out_rawline, None
1902 out_line, cmd_line = out_rawline, None
1902 if salt in out_rawline:
1903 if salt in out_rawline:
1903 out_line, cmd_line = out_rawline.split(salt, 1)
1904 out_line, cmd_line = out_rawline.split(salt, 1)
1904
1905
1905 pos, postout, warnonly = self._process_out_line(
1906 pos, postout, warnonly = self._process_out_line(
1906 out_line, pos, postout, expected, warnonly
1907 out_line, pos, postout, expected, warnonly
1907 )
1908 )
1908 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1909 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1909
1910
1910 if pos in after:
1911 if pos in after:
1911 postout += after.pop(pos)
1912 postout += after.pop(pos)
1912
1913
1913 if warnonly == WARN_YES:
1914 if warnonly == WARN_YES:
1914 exitcode = False # Set exitcode to warned.
1915 exitcode = False # Set exitcode to warned.
1915
1916
1916 return exitcode, postout
1917 return exitcode, postout
1917
1918
1918 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1919 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1919 while out_line:
1920 while out_line:
1920 if not out_line.endswith(b'\n'):
1921 if not out_line.endswith(b'\n'):
1921 out_line += b' (no-eol)\n'
1922 out_line += b' (no-eol)\n'
1922
1923
1923 # Find the expected output at the current position.
1924 # Find the expected output at the current position.
1924 els = [None]
1925 els = [None]
1925 if expected.get(pos, None):
1926 if expected.get(pos, None):
1926 els = expected[pos]
1927 els = expected[pos]
1927
1928
1928 optional = []
1929 optional = []
1929 for i, el in enumerate(els):
1930 for i, el in enumerate(els):
1930 r = False
1931 r = False
1931 if el:
1932 if el:
1932 r, exact = self.linematch(el, out_line)
1933 r, exact = self.linematch(el, out_line)
1933 if isinstance(r, str):
1934 if isinstance(r, str):
1934 if r == '-glob':
1935 if r == '-glob':
1935 out_line = ''.join(el.rsplit(' (glob)', 1))
1936 out_line = ''.join(el.rsplit(' (glob)', 1))
1936 r = '' # Warn only this line.
1937 r = '' # Warn only this line.
1937 elif r == "retry":
1938 elif r == "retry":
1938 postout.append(b' ' + el)
1939 postout.append(b' ' + el)
1939 else:
1940 else:
1940 log('\ninfo, unknown linematch result: %r\n' % r)
1941 log('\ninfo, unknown linematch result: %r\n' % r)
1941 r = False
1942 r = False
1942 if r:
1943 if r:
1943 els.pop(i)
1944 els.pop(i)
1944 break
1945 break
1945 if el:
1946 if el:
1946 if isoptional(el):
1947 if isoptional(el):
1947 optional.append(i)
1948 optional.append(i)
1948 else:
1949 else:
1949 m = optline.match(el)
1950 m = optline.match(el)
1950 if m:
1951 if m:
1951 conditions = [c for c in m.group(2).split(b' ')]
1952 conditions = [c for c in m.group(2).split(b' ')]
1952
1953
1953 if not self._iftest(conditions):
1954 if not self._iftest(conditions):
1954 optional.append(i)
1955 optional.append(i)
1955 if exact:
1956 if exact:
1956 # Don't allow line to be matches against a later
1957 # Don't allow line to be matches against a later
1957 # line in the output
1958 # line in the output
1958 els.pop(i)
1959 els.pop(i)
1959 break
1960 break
1960
1961
1961 if r:
1962 if r:
1962 if r == "retry":
1963 if r == "retry":
1963 continue
1964 continue
1964 # clean up any optional leftovers
1965 # clean up any optional leftovers
1965 for i in optional:
1966 for i in optional:
1966 postout.append(b' ' + els[i])
1967 postout.append(b' ' + els[i])
1967 for i in reversed(optional):
1968 for i in reversed(optional):
1968 del els[i]
1969 del els[i]
1969 postout.append(b' ' + el)
1970 postout.append(b' ' + el)
1970 else:
1971 else:
1971 if self.NEEDESCAPE(out_line):
1972 if self.NEEDESCAPE(out_line):
1972 out_line = TTest._stringescape(
1973 out_line = TTest._stringescape(
1973 b'%s (esc)\n' % out_line.rstrip(b'\n')
1974 b'%s (esc)\n' % out_line.rstrip(b'\n')
1974 )
1975 )
1975 postout.append(b' ' + out_line) # Let diff deal with it.
1976 postout.append(b' ' + out_line) # Let diff deal with it.
1976 if r != '': # If line failed.
1977 if r != '': # If line failed.
1977 warnonly = WARN_NO
1978 warnonly = WARN_NO
1978 elif warnonly == WARN_UNDEFINED:
1979 elif warnonly == WARN_UNDEFINED:
1979 warnonly = WARN_YES
1980 warnonly = WARN_YES
1980 break
1981 break
1981 else:
1982 else:
1982 # clean up any optional leftovers
1983 # clean up any optional leftovers
1983 while expected.get(pos, None):
1984 while expected.get(pos, None):
1984 el = expected[pos].pop(0)
1985 el = expected[pos].pop(0)
1985 if el:
1986 if el:
1986 if not isoptional(el):
1987 if not isoptional(el):
1987 m = optline.match(el)
1988 m = optline.match(el)
1988 if m:
1989 if m:
1989 conditions = [c for c in m.group(2).split(b' ')]
1990 conditions = [c for c in m.group(2).split(b' ')]
1990
1991
1991 if self._iftest(conditions):
1992 if self._iftest(conditions):
1992 # Don't append as optional line
1993 # Don't append as optional line
1993 continue
1994 continue
1994 else:
1995 else:
1995 continue
1996 continue
1996 postout.append(b' ' + el)
1997 postout.append(b' ' + el)
1997 return pos, postout, warnonly
1998 return pos, postout, warnonly
1998
1999
1999 def _process_cmd_line(self, cmd_line, pos, postout, after):
2000 def _process_cmd_line(self, cmd_line, pos, postout, after):
2000 """process a "command" part of a line from unified test output"""
2001 """process a "command" part of a line from unified test output"""
2001 if cmd_line:
2002 if cmd_line:
2002 # Add on last return code.
2003 # Add on last return code.
2003 ret = int(cmd_line.split()[1])
2004 ret = int(cmd_line.split()[1])
2004 if ret != 0:
2005 if ret != 0:
2005 postout.append(b' [%d]\n' % ret)
2006 postout.append(b' [%d]\n' % ret)
2006 if pos in after:
2007 if pos in after:
2007 # Merge in non-active test bits.
2008 # Merge in non-active test bits.
2008 postout += after.pop(pos)
2009 postout += after.pop(pos)
2009 pos = int(cmd_line.split()[0])
2010 pos = int(cmd_line.split()[0])
2010 return pos, postout
2011 return pos, postout
2011
2012
2012 @staticmethod
2013 @staticmethod
2013 def rematch(el, l):
2014 def rematch(el, l):
2014 try:
2015 try:
2015 # parse any flags at the beginning of the regex. Only 'i' is
2016 # parse any flags at the beginning of the regex. Only 'i' is
2016 # supported right now, but this should be easy to extend.
2017 # supported right now, but this should be easy to extend.
2017 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2018 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2018 flags = flags or b''
2019 flags = flags or b''
2019 el = flags + b'(?:' + el + b')'
2020 el = flags + b'(?:' + el + b')'
2020 # use \Z to ensure that the regex matches to the end of the string
2021 # use \Z to ensure that the regex matches to the end of the string
2021 if os.name == 'nt':
2022 if os.name == 'nt':
2022 return re.match(el + br'\r?\n\Z', l)
2023 return re.match(el + br'\r?\n\Z', l)
2023 return re.match(el + br'\n\Z', l)
2024 return re.match(el + br'\n\Z', l)
2024 except re.error:
2025 except re.error:
2025 # el is an invalid regex
2026 # el is an invalid regex
2026 return False
2027 return False
2027
2028
2028 @staticmethod
2029 @staticmethod
2029 def globmatch(el, l):
2030 def globmatch(el, l):
2030 # The only supported special characters are * and ? plus / which also
2031 # The only supported special characters are * and ? plus / which also
2031 # matches \ on windows. Escaping of these characters is supported.
2032 # matches \ on windows. Escaping of these characters is supported.
2032 if el + b'\n' == l:
2033 if el + b'\n' == l:
2033 if os.altsep:
2034 if os.altsep:
2034 # matching on "/" is not needed for this line
2035 # matching on "/" is not needed for this line
2035 for pat in checkcodeglobpats:
2036 for pat in checkcodeglobpats:
2036 if pat.match(el):
2037 if pat.match(el):
2037 return True
2038 return True
2038 return b'-glob'
2039 return b'-glob'
2039 return True
2040 return True
2040 el = el.replace(b'$LOCALIP', b'*')
2041 el = el.replace(b'$LOCALIP', b'*')
2041 i, n = 0, len(el)
2042 i, n = 0, len(el)
2042 res = b''
2043 res = b''
2043 while i < n:
2044 while i < n:
2044 c = el[i : i + 1]
2045 c = el[i : i + 1]
2045 i += 1
2046 i += 1
2046 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2047 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2047 res += el[i - 1 : i + 1]
2048 res += el[i - 1 : i + 1]
2048 i += 1
2049 i += 1
2049 elif c == b'*':
2050 elif c == b'*':
2050 res += b'.*'
2051 res += b'.*'
2051 elif c == b'?':
2052 elif c == b'?':
2052 res += b'.'
2053 res += b'.'
2053 elif c == b'/' and os.altsep:
2054 elif c == b'/' and os.altsep:
2054 res += b'[/\\\\]'
2055 res += b'[/\\\\]'
2055 else:
2056 else:
2056 res += re.escape(c)
2057 res += re.escape(c)
2057 return TTest.rematch(res, l)
2058 return TTest.rematch(res, l)
2058
2059
2059 def linematch(self, el, l):
2060 def linematch(self, el, l):
2060 if el == l: # perfect match (fast)
2061 if el == l: # perfect match (fast)
2061 return True, True
2062 return True, True
2062 retry = False
2063 retry = False
2063 if isoptional(el):
2064 if isoptional(el):
2064 retry = "retry"
2065 retry = "retry"
2065 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2066 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2066 else:
2067 else:
2067 m = optline.match(el)
2068 m = optline.match(el)
2068 if m:
2069 if m:
2069 conditions = [c for c in m.group(2).split(b' ')]
2070 conditions = [c for c in m.group(2).split(b' ')]
2070
2071
2071 el = m.group(1) + b"\n"
2072 el = m.group(1) + b"\n"
2072 if not self._iftest(conditions):
2073 if not self._iftest(conditions):
2073 # listed feature missing, should not match
2074 # listed feature missing, should not match
2074 return "retry", False
2075 return "retry", False
2075
2076
2076 if el.endswith(b" (esc)\n"):
2077 if el.endswith(b" (esc)\n"):
2077 if PYTHON3:
2078 if PYTHON3:
2078 el = el[:-7].decode('unicode_escape') + '\n'
2079 el = el[:-7].decode('unicode_escape') + '\n'
2079 el = el.encode('latin-1')
2080 el = el.encode('latin-1')
2080 else:
2081 else:
2081 el = el[:-7].decode('string-escape') + '\n'
2082 el = el[:-7].decode('string-escape') + '\n'
2082 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2083 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2083 return True, True
2084 return True, True
2084 if el.endswith(b" (re)\n"):
2085 if el.endswith(b" (re)\n"):
2085 return (TTest.rematch(el[:-6], l) or retry), False
2086 return (TTest.rematch(el[:-6], l) or retry), False
2086 if el.endswith(b" (glob)\n"):
2087 if el.endswith(b" (glob)\n"):
2087 # ignore '(glob)' added to l by 'replacements'
2088 # ignore '(glob)' added to l by 'replacements'
2088 if l.endswith(b" (glob)\n"):
2089 if l.endswith(b" (glob)\n"):
2089 l = l[:-8] + b"\n"
2090 l = l[:-8] + b"\n"
2090 return (TTest.globmatch(el[:-8], l) or retry), False
2091 return (TTest.globmatch(el[:-8], l) or retry), False
2091 if os.altsep:
2092 if os.altsep:
2092 _l = l.replace(b'\\', b'/')
2093 _l = l.replace(b'\\', b'/')
2093 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2094 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2094 return True, True
2095 return True, True
2095 return retry, True
2096 return retry, True
2096
2097
2097 @staticmethod
2098 @staticmethod
2098 def parsehghaveoutput(lines):
2099 def parsehghaveoutput(lines):
2099 """Parse hghave log lines.
2100 """Parse hghave log lines.
2100
2101
2101 Return tuple of lists (missing, failed):
2102 Return tuple of lists (missing, failed):
2102 * the missing/unknown features
2103 * the missing/unknown features
2103 * the features for which existence check failed"""
2104 * the features for which existence check failed"""
2104 missing = []
2105 missing = []
2105 failed = []
2106 failed = []
2106 for line in lines:
2107 for line in lines:
2107 if line.startswith(TTest.SKIPPED_PREFIX):
2108 if line.startswith(TTest.SKIPPED_PREFIX):
2108 line = line.splitlines()[0]
2109 line = line.splitlines()[0]
2109 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2110 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2110 elif line.startswith(TTest.FAILED_PREFIX):
2111 elif line.startswith(TTest.FAILED_PREFIX):
2111 line = line.splitlines()[0]
2112 line = line.splitlines()[0]
2112 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2113 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2113
2114
2114 return missing, failed
2115 return missing, failed
2115
2116
2116 @staticmethod
2117 @staticmethod
2117 def _escapef(m):
2118 def _escapef(m):
2118 return TTest.ESCAPEMAP[m.group(0)]
2119 return TTest.ESCAPEMAP[m.group(0)]
2119
2120
2120 @staticmethod
2121 @staticmethod
2121 def _stringescape(s):
2122 def _stringescape(s):
2122 return TTest.ESCAPESUB(TTest._escapef, s)
2123 return TTest.ESCAPESUB(TTest._escapef, s)
2123
2124
2124
2125
2125 iolock = threading.RLock()
2126 iolock = threading.RLock()
2126 firstlock = threading.RLock()
2127 firstlock = threading.RLock()
2127 firsterror = False
2128 firsterror = False
2128
2129
2129
2130
2130 class TestResult(unittest._TextTestResult):
2131 class TestResult(unittest._TextTestResult):
2131 """Holds results when executing via unittest."""
2132 """Holds results when executing via unittest."""
2132
2133
2133 # Don't worry too much about accessing the non-public _TextTestResult.
2134 # Don't worry too much about accessing the non-public _TextTestResult.
2134 # It is relatively common in Python testing tools.
2135 # It is relatively common in Python testing tools.
2135 def __init__(self, options, *args, **kwargs):
2136 def __init__(self, options, *args, **kwargs):
2136 super(TestResult, self).__init__(*args, **kwargs)
2137 super(TestResult, self).__init__(*args, **kwargs)
2137
2138
2138 self._options = options
2139 self._options = options
2139
2140
2140 # unittest.TestResult didn't have skipped until 2.7. We need to
2141 # unittest.TestResult didn't have skipped until 2.7. We need to
2141 # polyfill it.
2142 # polyfill it.
2142 self.skipped = []
2143 self.skipped = []
2143
2144
2144 # We have a custom "ignored" result that isn't present in any Python
2145 # We have a custom "ignored" result that isn't present in any Python
2145 # unittest implementation. It is very similar to skipped. It may make
2146 # unittest implementation. It is very similar to skipped. It may make
2146 # sense to map it into skip some day.
2147 # sense to map it into skip some day.
2147 self.ignored = []
2148 self.ignored = []
2148
2149
2149 self.times = []
2150 self.times = []
2150 self._firststarttime = None
2151 self._firststarttime = None
2151 # Data stored for the benefit of generating xunit reports.
2152 # Data stored for the benefit of generating xunit reports.
2152 self.successes = []
2153 self.successes = []
2153 self.faildata = {}
2154 self.faildata = {}
2154
2155
2155 if options.color == 'auto':
2156 if options.color == 'auto':
2156 self.color = pygmentspresent and self.stream.isatty()
2157 self.color = pygmentspresent and self.stream.isatty()
2157 elif options.color == 'never':
2158 elif options.color == 'never':
2158 self.color = False
2159 self.color = False
2159 else: # 'always', for testing purposes
2160 else: # 'always', for testing purposes
2160 self.color = pygmentspresent
2161 self.color = pygmentspresent
2161
2162
2162 def onStart(self, test):
2163 def onStart(self, test):
2163 """Can be overriden by custom TestResult"""
2164 """Can be overriden by custom TestResult"""
2164
2165
2165 def onEnd(self):
2166 def onEnd(self):
2166 """Can be overriden by custom TestResult"""
2167 """Can be overriden by custom TestResult"""
2167
2168
2168 def addFailure(self, test, reason):
2169 def addFailure(self, test, reason):
2169 self.failures.append((test, reason))
2170 self.failures.append((test, reason))
2170
2171
2171 if self._options.first:
2172 if self._options.first:
2172 self.stop()
2173 self.stop()
2173 else:
2174 else:
2174 with iolock:
2175 with iolock:
2175 if reason == "timed out":
2176 if reason == "timed out":
2176 self.stream.write('t')
2177 self.stream.write('t')
2177 else:
2178 else:
2178 if not self._options.nodiff:
2179 if not self._options.nodiff:
2179 self.stream.write('\n')
2180 self.stream.write('\n')
2180 # Exclude the '\n' from highlighting to lex correctly
2181 # Exclude the '\n' from highlighting to lex correctly
2181 formatted = 'ERROR: %s output changed\n' % test
2182 formatted = 'ERROR: %s output changed\n' % test
2182 self.stream.write(highlightmsg(formatted, self.color))
2183 self.stream.write(highlightmsg(formatted, self.color))
2183 self.stream.write('!')
2184 self.stream.write('!')
2184
2185
2185 self.stream.flush()
2186 self.stream.flush()
2186
2187
2187 def addSuccess(self, test):
2188 def addSuccess(self, test):
2188 with iolock:
2189 with iolock:
2189 super(TestResult, self).addSuccess(test)
2190 super(TestResult, self).addSuccess(test)
2190 self.successes.append(test)
2191 self.successes.append(test)
2191
2192
2192 def addError(self, test, err):
2193 def addError(self, test, err):
2193 super(TestResult, self).addError(test, err)
2194 super(TestResult, self).addError(test, err)
2194 if self._options.first:
2195 if self._options.first:
2195 self.stop()
2196 self.stop()
2196
2197
2197 # Polyfill.
2198 # Polyfill.
2198 def addSkip(self, test, reason):
2199 def addSkip(self, test, reason):
2199 self.skipped.append((test, reason))
2200 self.skipped.append((test, reason))
2200 with iolock:
2201 with iolock:
2201 if self.showAll:
2202 if self.showAll:
2202 self.stream.writeln('skipped %s' % reason)
2203 self.stream.writeln('skipped %s' % reason)
2203 else:
2204 else:
2204 self.stream.write('s')
2205 self.stream.write('s')
2205 self.stream.flush()
2206 self.stream.flush()
2206
2207
2207 def addIgnore(self, test, reason):
2208 def addIgnore(self, test, reason):
2208 self.ignored.append((test, reason))
2209 self.ignored.append((test, reason))
2209 with iolock:
2210 with iolock:
2210 if self.showAll:
2211 if self.showAll:
2211 self.stream.writeln('ignored %s' % reason)
2212 self.stream.writeln('ignored %s' % reason)
2212 else:
2213 else:
2213 if reason not in ('not retesting', "doesn't match keyword"):
2214 if reason not in ('not retesting', "doesn't match keyword"):
2214 self.stream.write('i')
2215 self.stream.write('i')
2215 else:
2216 else:
2216 self.testsRun += 1
2217 self.testsRun += 1
2217 self.stream.flush()
2218 self.stream.flush()
2218
2219
2219 def addOutputMismatch(self, test, ret, got, expected):
2220 def addOutputMismatch(self, test, ret, got, expected):
2220 """Record a mismatch in test output for a particular test."""
2221 """Record a mismatch in test output for a particular test."""
2221 if self.shouldStop or firsterror:
2222 if self.shouldStop or firsterror:
2222 # don't print, some other test case already failed and
2223 # don't print, some other test case already failed and
2223 # printed, we're just stale and probably failed due to our
2224 # printed, we're just stale and probably failed due to our
2224 # temp dir getting cleaned up.
2225 # temp dir getting cleaned up.
2225 return
2226 return
2226
2227
2227 accepted = False
2228 accepted = False
2228 lines = []
2229 lines = []
2229
2230
2230 with iolock:
2231 with iolock:
2231 if self._options.nodiff:
2232 if self._options.nodiff:
2232 pass
2233 pass
2233 elif self._options.view:
2234 elif self._options.view:
2234 v = self._options.view
2235 v = self._options.view
2235 subprocess.call(
2236 subprocess.call(
2236 r'"%s" "%s" "%s"'
2237 r'"%s" "%s" "%s"'
2237 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2238 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2238 shell=True,
2239 shell=True,
2239 )
2240 )
2240 else:
2241 else:
2241 servefail, lines = getdiff(
2242 servefail, lines = getdiff(
2242 expected, got, test.refpath, test.errpath
2243 expected, got, test.refpath, test.errpath
2243 )
2244 )
2244 self.stream.write('\n')
2245 self.stream.write('\n')
2245 for line in lines:
2246 for line in lines:
2246 line = highlightdiff(line, self.color)
2247 line = highlightdiff(line, self.color)
2247 if PYTHON3:
2248 if PYTHON3:
2248 self.stream.flush()
2249 self.stream.flush()
2249 self.stream.buffer.write(line)
2250 self.stream.buffer.write(line)
2250 self.stream.buffer.flush()
2251 self.stream.buffer.flush()
2251 else:
2252 else:
2252 self.stream.write(line)
2253 self.stream.write(line)
2253 self.stream.flush()
2254 self.stream.flush()
2254
2255
2255 if servefail:
2256 if servefail:
2256 raise test.failureException(
2257 raise test.failureException(
2257 'server failed to start (HGPORT=%s)' % test._startport
2258 'server failed to start (HGPORT=%s)' % test._startport
2258 )
2259 )
2259
2260
2260 # handle interactive prompt without releasing iolock
2261 # handle interactive prompt without releasing iolock
2261 if self._options.interactive:
2262 if self._options.interactive:
2262 if test.readrefout() != expected:
2263 if test.readrefout() != expected:
2263 self.stream.write(
2264 self.stream.write(
2264 'Reference output has changed (run again to prompt '
2265 'Reference output has changed (run again to prompt '
2265 'changes)'
2266 'changes)'
2266 )
2267 )
2267 else:
2268 else:
2268 self.stream.write('Accept this change? [y/N] ')
2269 self.stream.write('Accept this change? [y/N] ')
2269 self.stream.flush()
2270 self.stream.flush()
2270 answer = sys.stdin.readline().strip()
2271 answer = sys.stdin.readline().strip()
2271 if answer.lower() in ('y', 'yes'):
2272 if answer.lower() in ('y', 'yes'):
2272 if test.path.endswith(b'.t'):
2273 if test.path.endswith(b'.t'):
2273 rename(test.errpath, test.path)
2274 rename(test.errpath, test.path)
2274 else:
2275 else:
2275 rename(test.errpath, '%s.out' % test.path)
2276 rename(test.errpath, '%s.out' % test.path)
2276 accepted = True
2277 accepted = True
2277 if not accepted:
2278 if not accepted:
2278 self.faildata[test.name] = b''.join(lines)
2279 self.faildata[test.name] = b''.join(lines)
2279
2280
2280 return accepted
2281 return accepted
2281
2282
2282 def startTest(self, test):
2283 def startTest(self, test):
2283 super(TestResult, self).startTest(test)
2284 super(TestResult, self).startTest(test)
2284
2285
2285 # os.times module computes the user time and system time spent by
2286 # os.times module computes the user time and system time spent by
2286 # child's processes along with real elapsed time taken by a process.
2287 # child's processes along with real elapsed time taken by a process.
2287 # This module has one limitation. It can only work for Linux user
2288 # This module has one limitation. It can only work for Linux user
2288 # and not for Windows. Hence why we fall back to another function
2289 # and not for Windows. Hence why we fall back to another function
2289 # for wall time calculations.
2290 # for wall time calculations.
2290 test.started_times = os.times()
2291 test.started_times = os.times()
2291 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2292 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2292 test.started_time = time.time()
2293 test.started_time = time.time()
2293 if self._firststarttime is None: # thread racy but irrelevant
2294 if self._firststarttime is None: # thread racy but irrelevant
2294 self._firststarttime = test.started_time
2295 self._firststarttime = test.started_time
2295
2296
2296 def stopTest(self, test, interrupted=False):
2297 def stopTest(self, test, interrupted=False):
2297 super(TestResult, self).stopTest(test)
2298 super(TestResult, self).stopTest(test)
2298
2299
2299 test.stopped_times = os.times()
2300 test.stopped_times = os.times()
2300 stopped_time = time.time()
2301 stopped_time = time.time()
2301
2302
2302 starttime = test.started_times
2303 starttime = test.started_times
2303 endtime = test.stopped_times
2304 endtime = test.stopped_times
2304 origin = self._firststarttime
2305 origin = self._firststarttime
2305 self.times.append(
2306 self.times.append(
2306 (
2307 (
2307 test.name,
2308 test.name,
2308 endtime[2] - starttime[2], # user space CPU time
2309 endtime[2] - starttime[2], # user space CPU time
2309 endtime[3] - starttime[3], # sys space CPU time
2310 endtime[3] - starttime[3], # sys space CPU time
2310 stopped_time - test.started_time, # real time
2311 stopped_time - test.started_time, # real time
2311 test.started_time - origin, # start date in run context
2312 test.started_time - origin, # start date in run context
2312 stopped_time - origin, # end date in run context
2313 stopped_time - origin, # end date in run context
2313 )
2314 )
2314 )
2315 )
2315
2316
2316 if interrupted:
2317 if interrupted:
2317 with iolock:
2318 with iolock:
2318 self.stream.writeln(
2319 self.stream.writeln(
2319 'INTERRUPTED: %s (after %d seconds)'
2320 'INTERRUPTED: %s (after %d seconds)'
2320 % (test.name, self.times[-1][3])
2321 % (test.name, self.times[-1][3])
2321 )
2322 )
2322
2323
2323
2324
2324 def getTestResult():
2325 def getTestResult():
2325 """
2326 """
2326 Returns the relevant test result
2327 Returns the relevant test result
2327 """
2328 """
2328 if "CUSTOM_TEST_RESULT" in os.environ:
2329 if "CUSTOM_TEST_RESULT" in os.environ:
2329 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2330 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2330 return testresultmodule.TestResult
2331 return testresultmodule.TestResult
2331 else:
2332 else:
2332 return TestResult
2333 return TestResult
2333
2334
2334
2335
2335 class TestSuite(unittest.TestSuite):
2336 class TestSuite(unittest.TestSuite):
2336 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2337 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2337
2338
2338 def __init__(
2339 def __init__(
2339 self,
2340 self,
2340 testdir,
2341 testdir,
2341 jobs=1,
2342 jobs=1,
2342 whitelist=None,
2343 whitelist=None,
2343 blacklist=None,
2344 blacklist=None,
2344 keywords=None,
2345 keywords=None,
2345 loop=False,
2346 loop=False,
2346 runs_per_test=1,
2347 runs_per_test=1,
2347 loadtest=None,
2348 loadtest=None,
2348 showchannels=False,
2349 showchannels=False,
2349 *args,
2350 *args,
2350 **kwargs
2351 **kwargs
2351 ):
2352 ):
2352 """Create a new instance that can run tests with a configuration.
2353 """Create a new instance that can run tests with a configuration.
2353
2354
2354 testdir specifies the directory where tests are executed from. This
2355 testdir specifies the directory where tests are executed from. This
2355 is typically the ``tests`` directory from Mercurial's source
2356 is typically the ``tests`` directory from Mercurial's source
2356 repository.
2357 repository.
2357
2358
2358 jobs specifies the number of jobs to run concurrently. Each test
2359 jobs specifies the number of jobs to run concurrently. Each test
2359 executes on its own thread. Tests actually spawn new processes, so
2360 executes on its own thread. Tests actually spawn new processes, so
2360 state mutation should not be an issue.
2361 state mutation should not be an issue.
2361
2362
2362 If there is only one job, it will use the main thread.
2363 If there is only one job, it will use the main thread.
2363
2364
2364 whitelist and blacklist denote tests that have been whitelisted and
2365 whitelist and blacklist denote tests that have been whitelisted and
2365 blacklisted, respectively. These arguments don't belong in TestSuite.
2366 blacklisted, respectively. These arguments don't belong in TestSuite.
2366 Instead, whitelist and blacklist should be handled by the thing that
2367 Instead, whitelist and blacklist should be handled by the thing that
2367 populates the TestSuite with tests. They are present to preserve
2368 populates the TestSuite with tests. They are present to preserve
2368 backwards compatible behavior which reports skipped tests as part
2369 backwards compatible behavior which reports skipped tests as part
2369 of the results.
2370 of the results.
2370
2371
2371 keywords denotes key words that will be used to filter which tests
2372 keywords denotes key words that will be used to filter which tests
2372 to execute. This arguably belongs outside of TestSuite.
2373 to execute. This arguably belongs outside of TestSuite.
2373
2374
2374 loop denotes whether to loop over tests forever.
2375 loop denotes whether to loop over tests forever.
2375 """
2376 """
2376 super(TestSuite, self).__init__(*args, **kwargs)
2377 super(TestSuite, self).__init__(*args, **kwargs)
2377
2378
2378 self._jobs = jobs
2379 self._jobs = jobs
2379 self._whitelist = whitelist
2380 self._whitelist = whitelist
2380 self._blacklist = blacklist
2381 self._blacklist = blacklist
2381 self._keywords = keywords
2382 self._keywords = keywords
2382 self._loop = loop
2383 self._loop = loop
2383 self._runs_per_test = runs_per_test
2384 self._runs_per_test = runs_per_test
2384 self._loadtest = loadtest
2385 self._loadtest = loadtest
2385 self._showchannels = showchannels
2386 self._showchannels = showchannels
2386
2387
2387 def run(self, result):
2388 def run(self, result):
2388 # We have a number of filters that need to be applied. We do this
2389 # We have a number of filters that need to be applied. We do this
2389 # here instead of inside Test because it makes the running logic for
2390 # here instead of inside Test because it makes the running logic for
2390 # Test simpler.
2391 # Test simpler.
2391 tests = []
2392 tests = []
2392 num_tests = [0]
2393 num_tests = [0]
2393 for test in self._tests:
2394 for test in self._tests:
2394
2395
2395 def get():
2396 def get():
2396 num_tests[0] += 1
2397 num_tests[0] += 1
2397 if getattr(test, 'should_reload', False):
2398 if getattr(test, 'should_reload', False):
2398 return self._loadtest(test, num_tests[0])
2399 return self._loadtest(test, num_tests[0])
2399 return test
2400 return test
2400
2401
2401 if not os.path.exists(test.path):
2402 if not os.path.exists(test.path):
2402 result.addSkip(test, "Doesn't exist")
2403 result.addSkip(test, "Doesn't exist")
2403 continue
2404 continue
2404
2405
2405 is_whitelisted = self._whitelist and (
2406 is_whitelisted = self._whitelist and (
2406 test.relpath in self._whitelist or test.bname in self._whitelist
2407 test.relpath in self._whitelist or test.bname in self._whitelist
2407 )
2408 )
2408 if not is_whitelisted:
2409 if not is_whitelisted:
2409 is_blacklisted = self._blacklist and (
2410 is_blacklisted = self._blacklist and (
2410 test.relpath in self._blacklist
2411 test.relpath in self._blacklist
2411 or test.bname in self._blacklist
2412 or test.bname in self._blacklist
2412 )
2413 )
2413 if is_blacklisted:
2414 if is_blacklisted:
2414 result.addSkip(test, 'blacklisted')
2415 result.addSkip(test, 'blacklisted')
2415 continue
2416 continue
2416 if self._keywords:
2417 if self._keywords:
2417 with open(test.path, 'rb') as f:
2418 with open(test.path, 'rb') as f:
2418 t = f.read().lower() + test.bname.lower()
2419 t = f.read().lower() + test.bname.lower()
2419 ignored = False
2420 ignored = False
2420 for k in self._keywords.lower().split():
2421 for k in self._keywords.lower().split():
2421 if k not in t:
2422 if k not in t:
2422 result.addIgnore(test, "doesn't match keyword")
2423 result.addIgnore(test, "doesn't match keyword")
2423 ignored = True
2424 ignored = True
2424 break
2425 break
2425
2426
2426 if ignored:
2427 if ignored:
2427 continue
2428 continue
2428 for _ in xrange(self._runs_per_test):
2429 for _ in xrange(self._runs_per_test):
2429 tests.append(get())
2430 tests.append(get())
2430
2431
2431 runtests = list(tests)
2432 runtests = list(tests)
2432 done = queue.Queue()
2433 done = queue.Queue()
2433 running = 0
2434 running = 0
2434
2435
2435 channels = [""] * self._jobs
2436 channels = [""] * self._jobs
2436
2437
2437 def job(test, result):
2438 def job(test, result):
2438 for n, v in enumerate(channels):
2439 for n, v in enumerate(channels):
2439 if not v:
2440 if not v:
2440 channel = n
2441 channel = n
2441 break
2442 break
2442 else:
2443 else:
2443 raise ValueError('Could not find output channel')
2444 raise ValueError('Could not find output channel')
2444 channels[channel] = "=" + test.name[5:].split(".")[0]
2445 channels[channel] = "=" + test.name[5:].split(".")[0]
2445 try:
2446 try:
2446 test(result)
2447 test(result)
2447 done.put(None)
2448 done.put(None)
2448 except KeyboardInterrupt:
2449 except KeyboardInterrupt:
2449 pass
2450 pass
2450 except: # re-raises
2451 except: # re-raises
2451 done.put(('!', test, 'run-test raised an error, see traceback'))
2452 done.put(('!', test, 'run-test raised an error, see traceback'))
2452 raise
2453 raise
2453 finally:
2454 finally:
2454 try:
2455 try:
2455 channels[channel] = ''
2456 channels[channel] = ''
2456 except IndexError:
2457 except IndexError:
2457 pass
2458 pass
2458
2459
2459 def stat():
2460 def stat():
2460 count = 0
2461 count = 0
2461 while channels:
2462 while channels:
2462 d = '\n%03s ' % count
2463 d = '\n%03s ' % count
2463 for n, v in enumerate(channels):
2464 for n, v in enumerate(channels):
2464 if v:
2465 if v:
2465 d += v[0]
2466 d += v[0]
2466 channels[n] = v[1:] or '.'
2467 channels[n] = v[1:] or '.'
2467 else:
2468 else:
2468 d += ' '
2469 d += ' '
2469 d += ' '
2470 d += ' '
2470 with iolock:
2471 with iolock:
2471 sys.stdout.write(d + ' ')
2472 sys.stdout.write(d + ' ')
2472 sys.stdout.flush()
2473 sys.stdout.flush()
2473 for x in xrange(10):
2474 for x in xrange(10):
2474 if channels:
2475 if channels:
2475 time.sleep(0.1)
2476 time.sleep(0.1)
2476 count += 1
2477 count += 1
2477
2478
2478 stoppedearly = False
2479 stoppedearly = False
2479
2480
2480 if self._showchannels:
2481 if self._showchannels:
2481 statthread = threading.Thread(target=stat, name="stat")
2482 statthread = threading.Thread(target=stat, name="stat")
2482 statthread.start()
2483 statthread.start()
2483
2484
2484 try:
2485 try:
2485 while tests or running:
2486 while tests or running:
2486 if not done.empty() or running == self._jobs or not tests:
2487 if not done.empty() or running == self._jobs or not tests:
2487 try:
2488 try:
2488 done.get(True, 1)
2489 done.get(True, 1)
2489 running -= 1
2490 running -= 1
2490 if result and result.shouldStop:
2491 if result and result.shouldStop:
2491 stoppedearly = True
2492 stoppedearly = True
2492 break
2493 break
2493 except queue.Empty:
2494 except queue.Empty:
2494 continue
2495 continue
2495 if tests and not running == self._jobs:
2496 if tests and not running == self._jobs:
2496 test = tests.pop(0)
2497 test = tests.pop(0)
2497 if self._loop:
2498 if self._loop:
2498 if getattr(test, 'should_reload', False):
2499 if getattr(test, 'should_reload', False):
2499 num_tests[0] += 1
2500 num_tests[0] += 1
2500 tests.append(self._loadtest(test, num_tests[0]))
2501 tests.append(self._loadtest(test, num_tests[0]))
2501 else:
2502 else:
2502 tests.append(test)
2503 tests.append(test)
2503 if self._jobs == 1:
2504 if self._jobs == 1:
2504 job(test, result)
2505 job(test, result)
2505 else:
2506 else:
2506 t = threading.Thread(
2507 t = threading.Thread(
2507 target=job, name=test.name, args=(test, result)
2508 target=job, name=test.name, args=(test, result)
2508 )
2509 )
2509 t.start()
2510 t.start()
2510 running += 1
2511 running += 1
2511
2512
2512 # If we stop early we still need to wait on started tests to
2513 # If we stop early we still need to wait on started tests to
2513 # finish. Otherwise, there is a race between the test completing
2514 # finish. Otherwise, there is a race between the test completing
2514 # and the test's cleanup code running. This could result in the
2515 # and the test's cleanup code running. This could result in the
2515 # test reporting incorrect.
2516 # test reporting incorrect.
2516 if stoppedearly:
2517 if stoppedearly:
2517 while running:
2518 while running:
2518 try:
2519 try:
2519 done.get(True, 1)
2520 done.get(True, 1)
2520 running -= 1
2521 running -= 1
2521 except queue.Empty:
2522 except queue.Empty:
2522 continue
2523 continue
2523 except KeyboardInterrupt:
2524 except KeyboardInterrupt:
2524 for test in runtests:
2525 for test in runtests:
2525 test.abort()
2526 test.abort()
2526
2527
2527 channels = []
2528 channels = []
2528
2529
2529 return result
2530 return result
2530
2531
2531
2532
2532 # Save the most recent 5 wall-clock runtimes of each test to a
2533 # Save the most recent 5 wall-clock runtimes of each test to a
2533 # human-readable text file named .testtimes. Tests are sorted
2534 # human-readable text file named .testtimes. Tests are sorted
2534 # alphabetically, while times for each test are listed from oldest to
2535 # alphabetically, while times for each test are listed from oldest to
2535 # newest.
2536 # newest.
2536
2537
2537
2538
2538 def loadtimes(outputdir):
2539 def loadtimes(outputdir):
2539 times = []
2540 times = []
2540 try:
2541 try:
2541 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2542 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2542 for line in fp:
2543 for line in fp:
2543 m = re.match('(.*?) ([0-9. ]+)', line)
2544 m = re.match('(.*?) ([0-9. ]+)', line)
2544 times.append(
2545 times.append(
2545 (m.group(1), [float(t) for t in m.group(2).split()])
2546 (m.group(1), [float(t) for t in m.group(2).split()])
2546 )
2547 )
2547 except IOError as err:
2548 except IOError as err:
2548 if err.errno != errno.ENOENT:
2549 if err.errno != errno.ENOENT:
2549 raise
2550 raise
2550 return times
2551 return times
2551
2552
2552
2553
2553 def savetimes(outputdir, result):
2554 def savetimes(outputdir, result):
2554 saved = dict(loadtimes(outputdir))
2555 saved = dict(loadtimes(outputdir))
2555 maxruns = 5
2556 maxruns = 5
2556 skipped = {str(t[0]) for t in result.skipped}
2557 skipped = {str(t[0]) for t in result.skipped}
2557 for tdata in result.times:
2558 for tdata in result.times:
2558 test, real = tdata[0], tdata[3]
2559 test, real = tdata[0], tdata[3]
2559 if test not in skipped:
2560 if test not in skipped:
2560 ts = saved.setdefault(test, [])
2561 ts = saved.setdefault(test, [])
2561 ts.append(real)
2562 ts.append(real)
2562 ts[:] = ts[-maxruns:]
2563 ts[:] = ts[-maxruns:]
2563
2564
2564 fd, tmpname = tempfile.mkstemp(
2565 fd, tmpname = tempfile.mkstemp(
2565 prefix=b'.testtimes', dir=outputdir, text=True
2566 prefix=b'.testtimes', dir=outputdir, text=True
2566 )
2567 )
2567 with os.fdopen(fd, 'w') as fp:
2568 with os.fdopen(fd, 'w') as fp:
2568 for name, ts in sorted(saved.items()):
2569 for name, ts in sorted(saved.items()):
2569 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2570 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2570 timepath = os.path.join(outputdir, b'.testtimes')
2571 timepath = os.path.join(outputdir, b'.testtimes')
2571 try:
2572 try:
2572 os.unlink(timepath)
2573 os.unlink(timepath)
2573 except OSError:
2574 except OSError:
2574 pass
2575 pass
2575 try:
2576 try:
2576 os.rename(tmpname, timepath)
2577 os.rename(tmpname, timepath)
2577 except OSError:
2578 except OSError:
2578 pass
2579 pass
2579
2580
2580
2581
2581 class TextTestRunner(unittest.TextTestRunner):
2582 class TextTestRunner(unittest.TextTestRunner):
2582 """Custom unittest test runner that uses appropriate settings."""
2583 """Custom unittest test runner that uses appropriate settings."""
2583
2584
2584 def __init__(self, runner, *args, **kwargs):
2585 def __init__(self, runner, *args, **kwargs):
2585 super(TextTestRunner, self).__init__(*args, **kwargs)
2586 super(TextTestRunner, self).__init__(*args, **kwargs)
2586
2587
2587 self._runner = runner
2588 self._runner = runner
2588
2589
2589 self._result = getTestResult()(
2590 self._result = getTestResult()(
2590 self._runner.options, self.stream, self.descriptions, self.verbosity
2591 self._runner.options, self.stream, self.descriptions, self.verbosity
2591 )
2592 )
2592
2593
2593 def listtests(self, test):
2594 def listtests(self, test):
2594 test = sorted(test, key=lambda t: t.name)
2595 test = sorted(test, key=lambda t: t.name)
2595
2596
2596 self._result.onStart(test)
2597 self._result.onStart(test)
2597
2598
2598 for t in test:
2599 for t in test:
2599 print(t.name)
2600 print(t.name)
2600 self._result.addSuccess(t)
2601 self._result.addSuccess(t)
2601
2602
2602 if self._runner.options.xunit:
2603 if self._runner.options.xunit:
2603 with open(self._runner.options.xunit, "wb") as xuf:
2604 with open(self._runner.options.xunit, "wb") as xuf:
2604 self._writexunit(self._result, xuf)
2605 self._writexunit(self._result, xuf)
2605
2606
2606 if self._runner.options.json:
2607 if self._runner.options.json:
2607 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2608 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2608 with open(jsonpath, 'w') as fp:
2609 with open(jsonpath, 'w') as fp:
2609 self._writejson(self._result, fp)
2610 self._writejson(self._result, fp)
2610
2611
2611 return self._result
2612 return self._result
2612
2613
2613 def run(self, test):
2614 def run(self, test):
2614 self._result.onStart(test)
2615 self._result.onStart(test)
2615 test(self._result)
2616 test(self._result)
2616
2617
2617 failed = len(self._result.failures)
2618 failed = len(self._result.failures)
2618 skipped = len(self._result.skipped)
2619 skipped = len(self._result.skipped)
2619 ignored = len(self._result.ignored)
2620 ignored = len(self._result.ignored)
2620
2621
2621 with iolock:
2622 with iolock:
2622 self.stream.writeln('')
2623 self.stream.writeln('')
2623
2624
2624 if not self._runner.options.noskips:
2625 if not self._runner.options.noskips:
2625 for test, msg in sorted(
2626 for test, msg in sorted(
2626 self._result.skipped, key=lambda s: s[0].name
2627 self._result.skipped, key=lambda s: s[0].name
2627 ):
2628 ):
2628 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2629 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2629 msg = highlightmsg(formatted, self._result.color)
2630 msg = highlightmsg(formatted, self._result.color)
2630 self.stream.write(msg)
2631 self.stream.write(msg)
2631 for test, msg in sorted(
2632 for test, msg in sorted(
2632 self._result.failures, key=lambda f: f[0].name
2633 self._result.failures, key=lambda f: f[0].name
2633 ):
2634 ):
2634 formatted = 'Failed %s: %s\n' % (test.name, msg)
2635 formatted = 'Failed %s: %s\n' % (test.name, msg)
2635 self.stream.write(highlightmsg(formatted, self._result.color))
2636 self.stream.write(highlightmsg(formatted, self._result.color))
2636 for test, msg in sorted(
2637 for test, msg in sorted(
2637 self._result.errors, key=lambda e: e[0].name
2638 self._result.errors, key=lambda e: e[0].name
2638 ):
2639 ):
2639 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2640 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2640
2641
2641 if self._runner.options.xunit:
2642 if self._runner.options.xunit:
2642 with open(self._runner.options.xunit, "wb") as xuf:
2643 with open(self._runner.options.xunit, "wb") as xuf:
2643 self._writexunit(self._result, xuf)
2644 self._writexunit(self._result, xuf)
2644
2645
2645 if self._runner.options.json:
2646 if self._runner.options.json:
2646 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2647 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2647 with open(jsonpath, 'w') as fp:
2648 with open(jsonpath, 'w') as fp:
2648 self._writejson(self._result, fp)
2649 self._writejson(self._result, fp)
2649
2650
2650 self._runner._checkhglib('Tested')
2651 self._runner._checkhglib('Tested')
2651
2652
2652 savetimes(self._runner._outputdir, self._result)
2653 savetimes(self._runner._outputdir, self._result)
2653
2654
2654 if failed and self._runner.options.known_good_rev:
2655 if failed and self._runner.options.known_good_rev:
2655 self._bisecttests(t for t, m in self._result.failures)
2656 self._bisecttests(t for t, m in self._result.failures)
2656 self.stream.writeln(
2657 self.stream.writeln(
2657 '# Ran %d tests, %d skipped, %d failed.'
2658 '# Ran %d tests, %d skipped, %d failed.'
2658 % (self._result.testsRun, skipped + ignored, failed)
2659 % (self._result.testsRun, skipped + ignored, failed)
2659 )
2660 )
2660 if failed:
2661 if failed:
2661 self.stream.writeln(
2662 self.stream.writeln(
2662 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2663 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2663 )
2664 )
2664 if self._runner.options.time:
2665 if self._runner.options.time:
2665 self.printtimes(self._result.times)
2666 self.printtimes(self._result.times)
2666
2667
2667 if self._runner.options.exceptions:
2668 if self._runner.options.exceptions:
2668 exceptions = aggregateexceptions(
2669 exceptions = aggregateexceptions(
2669 os.path.join(self._runner._outputdir, b'exceptions')
2670 os.path.join(self._runner._outputdir, b'exceptions')
2670 )
2671 )
2671
2672
2672 self.stream.writeln('Exceptions Report:')
2673 self.stream.writeln('Exceptions Report:')
2673 self.stream.writeln(
2674 self.stream.writeln(
2674 '%d total from %d frames'
2675 '%d total from %d frames'
2675 % (exceptions['total'], len(exceptions['exceptioncounts']))
2676 % (exceptions['total'], len(exceptions['exceptioncounts']))
2676 )
2677 )
2677 combined = exceptions['combined']
2678 combined = exceptions['combined']
2678 for key in sorted(combined, key=combined.get, reverse=True):
2679 for key in sorted(combined, key=combined.get, reverse=True):
2679 frame, line, exc = key
2680 frame, line, exc = key
2680 totalcount, testcount, leastcount, leasttest = combined[key]
2681 totalcount, testcount, leastcount, leasttest = combined[key]
2681
2682
2682 self.stream.writeln(
2683 self.stream.writeln(
2683 '%d (%d tests)\t%s: %s (%s - %d total)'
2684 '%d (%d tests)\t%s: %s (%s - %d total)'
2684 % (
2685 % (
2685 totalcount,
2686 totalcount,
2686 testcount,
2687 testcount,
2687 frame,
2688 frame,
2688 exc,
2689 exc,
2689 leasttest,
2690 leasttest,
2690 leastcount,
2691 leastcount,
2691 )
2692 )
2692 )
2693 )
2693
2694
2694 self.stream.flush()
2695 self.stream.flush()
2695
2696
2696 return self._result
2697 return self._result
2697
2698
2698 def _bisecttests(self, tests):
2699 def _bisecttests(self, tests):
2699 bisectcmd = ['hg', 'bisect']
2700 bisectcmd = ['hg', 'bisect']
2700 bisectrepo = self._runner.options.bisect_repo
2701 bisectrepo = self._runner.options.bisect_repo
2701 if bisectrepo:
2702 if bisectrepo:
2702 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2703 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2703
2704
2704 def pread(args):
2705 def pread(args):
2705 env = os.environ.copy()
2706 env = os.environ.copy()
2706 env['HGPLAIN'] = '1'
2707 env['HGPLAIN'] = '1'
2707 p = subprocess.Popen(
2708 p = subprocess.Popen(
2708 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2709 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2709 )
2710 )
2710 data = p.stdout.read()
2711 data = p.stdout.read()
2711 p.wait()
2712 p.wait()
2712 return data
2713 return data
2713
2714
2714 for test in tests:
2715 for test in tests:
2715 pread(bisectcmd + ['--reset']),
2716 pread(bisectcmd + ['--reset']),
2716 pread(bisectcmd + ['--bad', '.'])
2717 pread(bisectcmd + ['--bad', '.'])
2717 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2718 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2718 # TODO: we probably need to forward more options
2719 # TODO: we probably need to forward more options
2719 # that alter hg's behavior inside the tests.
2720 # that alter hg's behavior inside the tests.
2720 opts = ''
2721 opts = ''
2721 withhg = self._runner.options.with_hg
2722 withhg = self._runner.options.with_hg
2722 if withhg:
2723 if withhg:
2723 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2724 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2724 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2725 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2725 data = pread(bisectcmd + ['--command', rtc])
2726 data = pread(bisectcmd + ['--command', rtc])
2726 m = re.search(
2727 m = re.search(
2727 (
2728 (
2728 br'\nThe first (?P<goodbad>bad|good) revision '
2729 br'\nThe first (?P<goodbad>bad|good) revision '
2729 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2730 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2730 br'summary: +(?P<summary>[^\n]+)\n'
2731 br'summary: +(?P<summary>[^\n]+)\n'
2731 ),
2732 ),
2732 data,
2733 data,
2733 (re.MULTILINE | re.DOTALL),
2734 (re.MULTILINE | re.DOTALL),
2734 )
2735 )
2735 if m is None:
2736 if m is None:
2736 self.stream.writeln(
2737 self.stream.writeln(
2737 'Failed to identify failure point for %s' % test
2738 'Failed to identify failure point for %s' % test
2738 )
2739 )
2739 continue
2740 continue
2740 dat = m.groupdict()
2741 dat = m.groupdict()
2741 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2742 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2742 self.stream.writeln(
2743 self.stream.writeln(
2743 '%s %s by %s (%s)'
2744 '%s %s by %s (%s)'
2744 % (
2745 % (
2745 test,
2746 test,
2746 verb,
2747 verb,
2747 dat['node'].decode('ascii'),
2748 dat['node'].decode('ascii'),
2748 dat['summary'].decode('utf8', 'ignore'),
2749 dat['summary'].decode('utf8', 'ignore'),
2749 )
2750 )
2750 )
2751 )
2751
2752
2752 def printtimes(self, times):
2753 def printtimes(self, times):
2753 # iolock held by run
2754 # iolock held by run
2754 self.stream.writeln('# Producing time report')
2755 self.stream.writeln('# Producing time report')
2755 times.sort(key=lambda t: (t[3]))
2756 times.sort(key=lambda t: (t[3]))
2756 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2757 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2757 self.stream.writeln(
2758 self.stream.writeln(
2758 '%-7s %-7s %-7s %-7s %-7s %s'
2759 '%-7s %-7s %-7s %-7s %-7s %s'
2759 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2760 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2760 )
2761 )
2761 for tdata in times:
2762 for tdata in times:
2762 test = tdata[0]
2763 test = tdata[0]
2763 cuser, csys, real, start, end = tdata[1:6]
2764 cuser, csys, real, start, end = tdata[1:6]
2764 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2765 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2765
2766
2766 @staticmethod
2767 @staticmethod
2767 def _writexunit(result, outf):
2768 def _writexunit(result, outf):
2768 # See http://llg.cubic.org/docs/junit/ for a reference.
2769 # See http://llg.cubic.org/docs/junit/ for a reference.
2769 timesd = {t[0]: t[3] for t in result.times}
2770 timesd = {t[0]: t[3] for t in result.times}
2770 doc = minidom.Document()
2771 doc = minidom.Document()
2771 s = doc.createElement('testsuite')
2772 s = doc.createElement('testsuite')
2772 s.setAttribute('errors', "0") # TODO
2773 s.setAttribute('errors', "0") # TODO
2773 s.setAttribute('failures', str(len(result.failures)))
2774 s.setAttribute('failures', str(len(result.failures)))
2774 s.setAttribute('name', 'run-tests')
2775 s.setAttribute('name', 'run-tests')
2775 s.setAttribute(
2776 s.setAttribute(
2776 'skipped', str(len(result.skipped) + len(result.ignored))
2777 'skipped', str(len(result.skipped) + len(result.ignored))
2777 )
2778 )
2778 s.setAttribute('tests', str(result.testsRun))
2779 s.setAttribute('tests', str(result.testsRun))
2779 doc.appendChild(s)
2780 doc.appendChild(s)
2780 for tc in result.successes:
2781 for tc in result.successes:
2781 t = doc.createElement('testcase')
2782 t = doc.createElement('testcase')
2782 t.setAttribute('name', tc.name)
2783 t.setAttribute('name', tc.name)
2783 tctime = timesd.get(tc.name)
2784 tctime = timesd.get(tc.name)
2784 if tctime is not None:
2785 if tctime is not None:
2785 t.setAttribute('time', '%.3f' % tctime)
2786 t.setAttribute('time', '%.3f' % tctime)
2786 s.appendChild(t)
2787 s.appendChild(t)
2787 for tc, err in sorted(result.faildata.items()):
2788 for tc, err in sorted(result.faildata.items()):
2788 t = doc.createElement('testcase')
2789 t = doc.createElement('testcase')
2789 t.setAttribute('name', tc)
2790 t.setAttribute('name', tc)
2790 tctime = timesd.get(tc)
2791 tctime = timesd.get(tc)
2791 if tctime is not None:
2792 if tctime is not None:
2792 t.setAttribute('time', '%.3f' % tctime)
2793 t.setAttribute('time', '%.3f' % tctime)
2793 # createCDATASection expects a unicode or it will
2794 # createCDATASection expects a unicode or it will
2794 # convert using default conversion rules, which will
2795 # convert using default conversion rules, which will
2795 # fail if string isn't ASCII.
2796 # fail if string isn't ASCII.
2796 err = cdatasafe(err).decode('utf-8', 'replace')
2797 err = cdatasafe(err).decode('utf-8', 'replace')
2797 cd = doc.createCDATASection(err)
2798 cd = doc.createCDATASection(err)
2798 # Use 'failure' here instead of 'error' to match errors = 0,
2799 # Use 'failure' here instead of 'error' to match errors = 0,
2799 # failures = len(result.failures) in the testsuite element.
2800 # failures = len(result.failures) in the testsuite element.
2800 failelem = doc.createElement('failure')
2801 failelem = doc.createElement('failure')
2801 failelem.setAttribute('message', 'output changed')
2802 failelem.setAttribute('message', 'output changed')
2802 failelem.setAttribute('type', 'output-mismatch')
2803 failelem.setAttribute('type', 'output-mismatch')
2803 failelem.appendChild(cd)
2804 failelem.appendChild(cd)
2804 t.appendChild(failelem)
2805 t.appendChild(failelem)
2805 s.appendChild(t)
2806 s.appendChild(t)
2806 for tc, message in result.skipped:
2807 for tc, message in result.skipped:
2807 # According to the schema, 'skipped' has no attributes. So store
2808 # According to the schema, 'skipped' has no attributes. So store
2808 # the skip message as a text node instead.
2809 # the skip message as a text node instead.
2809 t = doc.createElement('testcase')
2810 t = doc.createElement('testcase')
2810 t.setAttribute('name', tc.name)
2811 t.setAttribute('name', tc.name)
2811 binmessage = message.encode('utf-8')
2812 binmessage = message.encode('utf-8')
2812 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2813 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2813 cd = doc.createCDATASection(message)
2814 cd = doc.createCDATASection(message)
2814 skipelem = doc.createElement('skipped')
2815 skipelem = doc.createElement('skipped')
2815 skipelem.appendChild(cd)
2816 skipelem.appendChild(cd)
2816 t.appendChild(skipelem)
2817 t.appendChild(skipelem)
2817 s.appendChild(t)
2818 s.appendChild(t)
2818 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2819 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2819
2820
2820 @staticmethod
2821 @staticmethod
2821 def _writejson(result, outf):
2822 def _writejson(result, outf):
2822 timesd = {}
2823 timesd = {}
2823 for tdata in result.times:
2824 for tdata in result.times:
2824 test = tdata[0]
2825 test = tdata[0]
2825 timesd[test] = tdata[1:]
2826 timesd[test] = tdata[1:]
2826
2827
2827 outcome = {}
2828 outcome = {}
2828 groups = [
2829 groups = [
2829 ('success', ((tc, None) for tc in result.successes)),
2830 ('success', ((tc, None) for tc in result.successes)),
2830 ('failure', result.failures),
2831 ('failure', result.failures),
2831 ('skip', result.skipped),
2832 ('skip', result.skipped),
2832 ]
2833 ]
2833 for res, testcases in groups:
2834 for res, testcases in groups:
2834 for tc, __ in testcases:
2835 for tc, __ in testcases:
2835 if tc.name in timesd:
2836 if tc.name in timesd:
2836 diff = result.faildata.get(tc.name, b'')
2837 diff = result.faildata.get(tc.name, b'')
2837 try:
2838 try:
2838 diff = diff.decode('unicode_escape')
2839 diff = diff.decode('unicode_escape')
2839 except UnicodeDecodeError as e:
2840 except UnicodeDecodeError as e:
2840 diff = '%r decoding diff, sorry' % e
2841 diff = '%r decoding diff, sorry' % e
2841 tres = {
2842 tres = {
2842 'result': res,
2843 'result': res,
2843 'time': ('%0.3f' % timesd[tc.name][2]),
2844 'time': ('%0.3f' % timesd[tc.name][2]),
2844 'cuser': ('%0.3f' % timesd[tc.name][0]),
2845 'cuser': ('%0.3f' % timesd[tc.name][0]),
2845 'csys': ('%0.3f' % timesd[tc.name][1]),
2846 'csys': ('%0.3f' % timesd[tc.name][1]),
2846 'start': ('%0.3f' % timesd[tc.name][3]),
2847 'start': ('%0.3f' % timesd[tc.name][3]),
2847 'end': ('%0.3f' % timesd[tc.name][4]),
2848 'end': ('%0.3f' % timesd[tc.name][4]),
2848 'diff': diff,
2849 'diff': diff,
2849 }
2850 }
2850 else:
2851 else:
2851 # blacklisted test
2852 # blacklisted test
2852 tres = {'result': res}
2853 tres = {'result': res}
2853
2854
2854 outcome[tc.name] = tres
2855 outcome[tc.name] = tres
2855 jsonout = json.dumps(
2856 jsonout = json.dumps(
2856 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2857 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2857 )
2858 )
2858 outf.writelines(("testreport =", jsonout))
2859 outf.writelines(("testreport =", jsonout))
2859
2860
2860
2861
2861 def sorttests(testdescs, previoustimes, shuffle=False):
2862 def sorttests(testdescs, previoustimes, shuffle=False):
2862 """Do an in-place sort of tests."""
2863 """Do an in-place sort of tests."""
2863 if shuffle:
2864 if shuffle:
2864 random.shuffle(testdescs)
2865 random.shuffle(testdescs)
2865 return
2866 return
2866
2867
2867 if previoustimes:
2868 if previoustimes:
2868
2869
2869 def sortkey(f):
2870 def sortkey(f):
2870 f = f['path']
2871 f = f['path']
2871 if f in previoustimes:
2872 if f in previoustimes:
2872 # Use most recent time as estimate
2873 # Use most recent time as estimate
2873 return -(previoustimes[f][-1])
2874 return -(previoustimes[f][-1])
2874 else:
2875 else:
2875 # Default to a rather arbitrary value of 1 second for new tests
2876 # Default to a rather arbitrary value of 1 second for new tests
2876 return -1.0
2877 return -1.0
2877
2878
2878 else:
2879 else:
2879 # keywords for slow tests
2880 # keywords for slow tests
2880 slow = {
2881 slow = {
2881 b'svn': 10,
2882 b'svn': 10,
2882 b'cvs': 10,
2883 b'cvs': 10,
2883 b'hghave': 10,
2884 b'hghave': 10,
2884 b'largefiles-update': 10,
2885 b'largefiles-update': 10,
2885 b'run-tests': 10,
2886 b'run-tests': 10,
2886 b'corruption': 10,
2887 b'corruption': 10,
2887 b'race': 10,
2888 b'race': 10,
2888 b'i18n': 10,
2889 b'i18n': 10,
2889 b'check': 100,
2890 b'check': 100,
2890 b'gendoc': 100,
2891 b'gendoc': 100,
2891 b'contrib-perf': 200,
2892 b'contrib-perf': 200,
2892 b'merge-combination': 100,
2893 b'merge-combination': 100,
2893 }
2894 }
2894 perf = {}
2895 perf = {}
2895
2896
2896 def sortkey(f):
2897 def sortkey(f):
2897 # run largest tests first, as they tend to take the longest
2898 # run largest tests first, as they tend to take the longest
2898 f = f['path']
2899 f = f['path']
2899 try:
2900 try:
2900 return perf[f]
2901 return perf[f]
2901 except KeyError:
2902 except KeyError:
2902 try:
2903 try:
2903 val = -os.stat(f).st_size
2904 val = -os.stat(f).st_size
2904 except OSError as e:
2905 except OSError as e:
2905 if e.errno != errno.ENOENT:
2906 if e.errno != errno.ENOENT:
2906 raise
2907 raise
2907 perf[f] = -1e9 # file does not exist, tell early
2908 perf[f] = -1e9 # file does not exist, tell early
2908 return -1e9
2909 return -1e9
2909 for kw, mul in slow.items():
2910 for kw, mul in slow.items():
2910 if kw in f:
2911 if kw in f:
2911 val *= mul
2912 val *= mul
2912 if f.endswith(b'.py'):
2913 if f.endswith(b'.py'):
2913 val /= 10.0
2914 val /= 10.0
2914 perf[f] = val / 1000.0
2915 perf[f] = val / 1000.0
2915 return perf[f]
2916 return perf[f]
2916
2917
2917 testdescs.sort(key=sortkey)
2918 testdescs.sort(key=sortkey)
2918
2919
2919
2920
2920 class TestRunner(object):
2921 class TestRunner(object):
2921 """Holds context for executing tests.
2922 """Holds context for executing tests.
2922
2923
2923 Tests rely on a lot of state. This object holds it for them.
2924 Tests rely on a lot of state. This object holds it for them.
2924 """
2925 """
2925
2926
2926 # Programs required to run tests.
2927 # Programs required to run tests.
2927 REQUIREDTOOLS = [
2928 REQUIREDTOOLS = [
2928 b'diff',
2929 b'diff',
2929 b'grep',
2930 b'grep',
2930 b'unzip',
2931 b'unzip',
2931 b'gunzip',
2932 b'gunzip',
2932 b'bunzip2',
2933 b'bunzip2',
2933 b'sed',
2934 b'sed',
2934 ]
2935 ]
2935
2936
2936 # Maps file extensions to test class.
2937 # Maps file extensions to test class.
2937 TESTTYPES = [
2938 TESTTYPES = [
2938 (b'.py', PythonTest),
2939 (b'.py', PythonTest),
2939 (b'.t', TTest),
2940 (b'.t', TTest),
2940 ]
2941 ]
2941
2942
2942 def __init__(self):
2943 def __init__(self):
2943 self.options = None
2944 self.options = None
2944 self._hgroot = None
2945 self._hgroot = None
2945 self._testdir = None
2946 self._testdir = None
2946 self._outputdir = None
2947 self._outputdir = None
2947 self._hgtmp = None
2948 self._hgtmp = None
2948 self._installdir = None
2949 self._installdir = None
2949 self._bindir = None
2950 self._bindir = None
2950 self._tmpbinddir = None
2951 self._tmpbinddir = None
2951 self._pythondir = None
2952 self._pythondir = None
2952 self._coveragefile = None
2953 self._coveragefile = None
2953 self._createdfiles = []
2954 self._createdfiles = []
2954 self._hgcommand = None
2955 self._hgcommand = None
2955 self._hgpath = None
2956 self._hgpath = None
2956 self._portoffset = 0
2957 self._portoffset = 0
2957 self._ports = {}
2958 self._ports = {}
2958
2959
2959 def run(self, args, parser=None):
2960 def run(self, args, parser=None):
2960 """Run the test suite."""
2961 """Run the test suite."""
2961 oldmask = os.umask(0o22)
2962 oldmask = os.umask(0o22)
2962 try:
2963 try:
2963 parser = parser or getparser()
2964 parser = parser or getparser()
2964 options = parseargs(args, parser)
2965 options = parseargs(args, parser)
2965 tests = [_sys2bytes(a) for a in options.tests]
2966 tests = [_sys2bytes(a) for a in options.tests]
2966 if options.test_list is not None:
2967 if options.test_list is not None:
2967 for listfile in options.test_list:
2968 for listfile in options.test_list:
2968 with open(listfile, 'rb') as f:
2969 with open(listfile, 'rb') as f:
2969 tests.extend(t for t in f.read().splitlines() if t)
2970 tests.extend(t for t in f.read().splitlines() if t)
2970 self.options = options
2971 self.options = options
2971
2972
2972 self._checktools()
2973 self._checktools()
2973 testdescs = self.findtests(tests)
2974 testdescs = self.findtests(tests)
2974 if options.profile_runner:
2975 if options.profile_runner:
2975 import statprof
2976 import statprof
2976
2977
2977 statprof.start()
2978 statprof.start()
2978 result = self._run(testdescs)
2979 result = self._run(testdescs)
2979 if options.profile_runner:
2980 if options.profile_runner:
2980 statprof.stop()
2981 statprof.stop()
2981 statprof.display()
2982 statprof.display()
2982 return result
2983 return result
2983
2984
2984 finally:
2985 finally:
2985 os.umask(oldmask)
2986 os.umask(oldmask)
2986
2987
2987 def _run(self, testdescs):
2988 def _run(self, testdescs):
2988 testdir = getcwdb()
2989 testdir = getcwdb()
2989 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2990 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2990 # assume all tests in same folder for now
2991 # assume all tests in same folder for now
2991 if testdescs:
2992 if testdescs:
2992 pathname = os.path.dirname(testdescs[0]['path'])
2993 pathname = os.path.dirname(testdescs[0]['path'])
2993 if pathname:
2994 if pathname:
2994 testdir = os.path.join(testdir, pathname)
2995 testdir = os.path.join(testdir, pathname)
2995 self._testdir = osenvironb[b'TESTDIR'] = testdir
2996 self._testdir = osenvironb[b'TESTDIR'] = testdir
2996 if self.options.outputdir:
2997 if self.options.outputdir:
2997 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
2998 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
2998 else:
2999 else:
2999 self._outputdir = getcwdb()
3000 self._outputdir = getcwdb()
3000 if testdescs and pathname:
3001 if testdescs and pathname:
3001 self._outputdir = os.path.join(self._outputdir, pathname)
3002 self._outputdir = os.path.join(self._outputdir, pathname)
3002 previoustimes = {}
3003 previoustimes = {}
3003 if self.options.order_by_runtime:
3004 if self.options.order_by_runtime:
3004 previoustimes = dict(loadtimes(self._outputdir))
3005 previoustimes = dict(loadtimes(self._outputdir))
3005 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3006 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3006
3007
3007 if 'PYTHONHASHSEED' not in os.environ:
3008 if 'PYTHONHASHSEED' not in os.environ:
3008 # use a random python hash seed all the time
3009 # use a random python hash seed all the time
3009 # we do the randomness ourself to know what seed is used
3010 # we do the randomness ourself to know what seed is used
3010 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3011 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3011
3012
3012 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3013 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3013 # by default, causing thrashing on high-cpu-count systems.
3014 # by default, causing thrashing on high-cpu-count systems.
3014 # Setting its limit to 3 during tests should still let us uncover
3015 # Setting its limit to 3 during tests should still let us uncover
3015 # multi-threading bugs while keeping the thrashing reasonable.
3016 # multi-threading bugs while keeping the thrashing reasonable.
3016 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3017 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3017
3018
3018 if self.options.tmpdir:
3019 if self.options.tmpdir:
3019 self.options.keep_tmpdir = True
3020 self.options.keep_tmpdir = True
3020 tmpdir = _sys2bytes(self.options.tmpdir)
3021 tmpdir = _sys2bytes(self.options.tmpdir)
3021 if os.path.exists(tmpdir):
3022 if os.path.exists(tmpdir):
3022 # Meaning of tmpdir has changed since 1.3: we used to create
3023 # Meaning of tmpdir has changed since 1.3: we used to create
3023 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3024 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3024 # tmpdir already exists.
3025 # tmpdir already exists.
3025 print("error: temp dir %r already exists" % tmpdir)
3026 print("error: temp dir %r already exists" % tmpdir)
3026 return 1
3027 return 1
3027
3028
3028 os.makedirs(tmpdir)
3029 os.makedirs(tmpdir)
3029 else:
3030 else:
3030 d = None
3031 d = None
3031 if os.name == 'nt':
3032 if os.name == 'nt':
3032 # without this, we get the default temp dir location, but
3033 # without this, we get the default temp dir location, but
3033 # in all lowercase, which causes troubles with paths (issue3490)
3034 # in all lowercase, which causes troubles with paths (issue3490)
3034 d = osenvironb.get(b'TMP', None)
3035 d = osenvironb.get(b'TMP', None)
3035 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3036 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3036
3037
3037 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3038 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3038
3039
3039 if self.options.with_hg:
3040 if self.options.with_hg:
3040 self._installdir = None
3041 self._installdir = None
3041 whg = self.options.with_hg
3042 whg = self.options.with_hg
3042 self._bindir = os.path.dirname(os.path.realpath(whg))
3043 self._bindir = os.path.dirname(os.path.realpath(whg))
3043 assert isinstance(self._bindir, bytes)
3044 assert isinstance(self._bindir, bytes)
3044 self._hgcommand = os.path.basename(whg)
3045 self._hgcommand = os.path.basename(whg)
3045 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3046 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3046 os.makedirs(self._tmpbindir)
3047 os.makedirs(self._tmpbindir)
3047
3048
3048 normbin = os.path.normpath(os.path.abspath(whg))
3049 normbin = os.path.normpath(os.path.abspath(whg))
3049 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3050 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3050
3051
3051 # Other Python scripts in the test harness need to
3052 # Other Python scripts in the test harness need to
3052 # `import mercurial`. If `hg` is a Python script, we assume
3053 # `import mercurial`. If `hg` is a Python script, we assume
3053 # the Mercurial modules are relative to its path and tell the tests
3054 # the Mercurial modules are relative to its path and tell the tests
3054 # to load Python modules from its directory.
3055 # to load Python modules from its directory.
3055 with open(whg, 'rb') as fh:
3056 with open(whg, 'rb') as fh:
3056 initial = fh.read(1024)
3057 initial = fh.read(1024)
3057
3058
3058 if re.match(b'#!.*python', initial):
3059 if re.match(b'#!.*python', initial):
3059 self._pythondir = self._bindir
3060 self._pythondir = self._bindir
3060 # If it looks like our in-repo Rust binary, use the source root.
3061 # If it looks like our in-repo Rust binary, use the source root.
3061 # This is a bit hacky. But rhg is still not supported outside the
3062 # This is a bit hacky. But rhg is still not supported outside the
3062 # source directory. So until it is, do the simple thing.
3063 # source directory. So until it is, do the simple thing.
3063 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3064 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3064 self._pythondir = os.path.dirname(self._testdir)
3065 self._pythondir = os.path.dirname(self._testdir)
3065 # Fall back to the legacy behavior.
3066 # Fall back to the legacy behavior.
3066 else:
3067 else:
3067 self._pythondir = self._bindir
3068 self._pythondir = self._bindir
3068
3069
3069 else:
3070 else:
3070 self._installdir = os.path.join(self._hgtmp, b"install")
3071 self._installdir = os.path.join(self._hgtmp, b"install")
3071 self._bindir = os.path.join(self._installdir, b"bin")
3072 self._bindir = os.path.join(self._installdir, b"bin")
3072 self._hgcommand = b'hg'
3073 self._hgcommand = b'hg'
3073 self._tmpbindir = self._bindir
3074 self._tmpbindir = self._bindir
3074 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3075 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3075
3076
3076 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3077 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3077 # a python script and feed it to python.exe. Legacy stdio is force
3078 # a python script and feed it to python.exe. Legacy stdio is force
3078 # enabled by hg.exe, and this is a more realistic way to launch hg
3079 # enabled by hg.exe, and this is a more realistic way to launch hg
3079 # anyway.
3080 # anyway.
3080 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3081 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3081 self._hgcommand += b'.exe'
3082 self._hgcommand += b'.exe'
3082
3083
3083 # set CHGHG, then replace "hg" command by "chg"
3084 # set CHGHG, then replace "hg" command by "chg"
3084 chgbindir = self._bindir
3085 chgbindir = self._bindir
3085 if self.options.chg or self.options.with_chg:
3086 if self.options.chg or self.options.with_chg:
3086 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3087 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3087 else:
3088 else:
3088 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3089 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3089 if self.options.chg:
3090 if self.options.chg:
3090 self._hgcommand = b'chg'
3091 self._hgcommand = b'chg'
3091 elif self.options.with_chg:
3092 elif self.options.with_chg:
3092 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3093 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3093 self._hgcommand = os.path.basename(self.options.with_chg)
3094 self._hgcommand = os.path.basename(self.options.with_chg)
3094
3095
3095 osenvironb[b"BINDIR"] = self._bindir
3096 osenvironb[b"BINDIR"] = self._bindir
3096 osenvironb[b"PYTHON"] = PYTHON
3097 osenvironb[b"PYTHON"] = PYTHON
3097
3098
3098 fileb = _sys2bytes(__file__)
3099 fileb = _sys2bytes(__file__)
3099 runtestdir = os.path.abspath(os.path.dirname(fileb))
3100 runtestdir = os.path.abspath(os.path.dirname(fileb))
3100 osenvironb[b'RUNTESTDIR'] = runtestdir
3101 osenvironb[b'RUNTESTDIR'] = runtestdir
3101 if PYTHON3:
3102 if PYTHON3:
3102 sepb = _sys2bytes(os.pathsep)
3103 sepb = _sys2bytes(os.pathsep)
3103 else:
3104 else:
3104 sepb = os.pathsep
3105 sepb = os.pathsep
3105 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3106 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3106 if os.path.islink(__file__):
3107 if os.path.islink(__file__):
3107 # test helper will likely be at the end of the symlink
3108 # test helper will likely be at the end of the symlink
3108 realfile = os.path.realpath(fileb)
3109 realfile = os.path.realpath(fileb)
3109 realdir = os.path.abspath(os.path.dirname(realfile))
3110 realdir = os.path.abspath(os.path.dirname(realfile))
3110 path.insert(2, realdir)
3111 path.insert(2, realdir)
3111 if chgbindir != self._bindir:
3112 if chgbindir != self._bindir:
3112 path.insert(1, chgbindir)
3113 path.insert(1, chgbindir)
3113 if self._testdir != runtestdir:
3114 if self._testdir != runtestdir:
3114 path = [self._testdir] + path
3115 path = [self._testdir] + path
3115 if self._tmpbindir != self._bindir:
3116 if self._tmpbindir != self._bindir:
3116 path = [self._tmpbindir] + path
3117 path = [self._tmpbindir] + path
3117 osenvironb[b"PATH"] = sepb.join(path)
3118 osenvironb[b"PATH"] = sepb.join(path)
3118
3119
3119 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3120 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3120 # can run .../tests/run-tests.py test-foo where test-foo
3121 # can run .../tests/run-tests.py test-foo where test-foo
3121 # adds an extension to HGRC. Also include run-test.py directory to
3122 # adds an extension to HGRC. Also include run-test.py directory to
3122 # import modules like heredoctest.
3123 # import modules like heredoctest.
3123 pypath = [self._pythondir, self._testdir, runtestdir]
3124 pypath = [self._pythondir, self._testdir, runtestdir]
3124 # We have to augment PYTHONPATH, rather than simply replacing
3125 # We have to augment PYTHONPATH, rather than simply replacing
3125 # it, in case external libraries are only available via current
3126 # it, in case external libraries are only available via current
3126 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3127 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3127 # are in /opt/subversion.)
3128 # are in /opt/subversion.)
3128 oldpypath = osenvironb.get(IMPL_PATH)
3129 oldpypath = osenvironb.get(IMPL_PATH)
3129 if oldpypath:
3130 if oldpypath:
3130 pypath.append(oldpypath)
3131 pypath.append(oldpypath)
3131 osenvironb[IMPL_PATH] = sepb.join(pypath)
3132 osenvironb[IMPL_PATH] = sepb.join(pypath)
3132
3133
3133 if self.options.pure:
3134 if self.options.pure:
3134 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3135 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3135 os.environ["HGMODULEPOLICY"] = "py"
3136 os.environ["HGMODULEPOLICY"] = "py"
3136 if self.options.rust:
3137 if self.options.rust:
3137 os.environ["HGMODULEPOLICY"] = "rust+c"
3138 os.environ["HGMODULEPOLICY"] = "rust+c"
3138 if self.options.no_rust:
3139 if self.options.no_rust:
3139 current_policy = os.environ.get("HGMODULEPOLICY", "")
3140 current_policy = os.environ.get("HGMODULEPOLICY", "")
3140 if current_policy.startswith("rust+"):
3141 if current_policy.startswith("rust+"):
3141 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3142 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3142 os.environ.pop("HGWITHRUSTEXT", None)
3143 os.environ.pop("HGWITHRUSTEXT", None)
3143
3144
3144 if self.options.allow_slow_tests:
3145 if self.options.allow_slow_tests:
3145 os.environ["HGTEST_SLOW"] = "slow"
3146 os.environ["HGTEST_SLOW"] = "slow"
3146 elif 'HGTEST_SLOW' in os.environ:
3147 elif 'HGTEST_SLOW' in os.environ:
3147 del os.environ['HGTEST_SLOW']
3148 del os.environ['HGTEST_SLOW']
3148
3149
3149 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3150 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3150
3151
3151 if self.options.exceptions:
3152 if self.options.exceptions:
3152 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3153 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3153 try:
3154 try:
3154 os.makedirs(exceptionsdir)
3155 os.makedirs(exceptionsdir)
3155 except OSError as e:
3156 except OSError as e:
3156 if e.errno != errno.EEXIST:
3157 if e.errno != errno.EEXIST:
3157 raise
3158 raise
3158
3159
3159 # Remove all existing exception reports.
3160 # Remove all existing exception reports.
3160 for f in os.listdir(exceptionsdir):
3161 for f in os.listdir(exceptionsdir):
3161 os.unlink(os.path.join(exceptionsdir, f))
3162 os.unlink(os.path.join(exceptionsdir, f))
3162
3163
3163 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3164 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3164 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3165 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3165 self.options.extra_config_opt.append(
3166 self.options.extra_config_opt.append(
3166 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3167 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3167 )
3168 )
3168
3169
3169 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3170 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3170 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3171 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3171 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3172 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3172 vlog("# Using PATH", os.environ["PATH"])
3173 vlog("# Using PATH", os.environ["PATH"])
3173 vlog(
3174 vlog(
3174 "# Using",
3175 "# Using",
3175 _bytes2sys(IMPL_PATH),
3176 _bytes2sys(IMPL_PATH),
3176 _bytes2sys(osenvironb[IMPL_PATH]),
3177 _bytes2sys(osenvironb[IMPL_PATH]),
3177 )
3178 )
3178 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3179 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3179
3180
3180 try:
3181 try:
3181 return self._runtests(testdescs) or 0
3182 return self._runtests(testdescs) or 0
3182 finally:
3183 finally:
3183 time.sleep(0.1)
3184 time.sleep(0.1)
3184 self._cleanup()
3185 self._cleanup()
3185
3186
3186 def findtests(self, args):
3187 def findtests(self, args):
3187 """Finds possible test files from arguments.
3188 """Finds possible test files from arguments.
3188
3189
3189 If you wish to inject custom tests into the test harness, this would
3190 If you wish to inject custom tests into the test harness, this would
3190 be a good function to monkeypatch or override in a derived class.
3191 be a good function to monkeypatch or override in a derived class.
3191 """
3192 """
3192 if not args:
3193 if not args:
3193 if self.options.changed:
3194 if self.options.changed:
3194 proc = Popen4(
3195 proc = Popen4(
3195 b'hg st --rev "%s" -man0 .'
3196 b'hg st --rev "%s" -man0 .'
3196 % _sys2bytes(self.options.changed),
3197 % _sys2bytes(self.options.changed),
3197 None,
3198 None,
3198 0,
3199 0,
3199 )
3200 )
3200 stdout, stderr = proc.communicate()
3201 stdout, stderr = proc.communicate()
3201 args = stdout.strip(b'\0').split(b'\0')
3202 args = stdout.strip(b'\0').split(b'\0')
3202 else:
3203 else:
3203 args = os.listdir(b'.')
3204 args = os.listdir(b'.')
3204
3205
3205 expanded_args = []
3206 expanded_args = []
3206 for arg in args:
3207 for arg in args:
3207 if os.path.isdir(arg):
3208 if os.path.isdir(arg):
3208 if not arg.endswith(b'/'):
3209 if not arg.endswith(b'/'):
3209 arg += b'/'
3210 arg += b'/'
3210 expanded_args.extend([arg + a for a in os.listdir(arg)])
3211 expanded_args.extend([arg + a for a in os.listdir(arg)])
3211 else:
3212 else:
3212 expanded_args.append(arg)
3213 expanded_args.append(arg)
3213 args = expanded_args
3214 args = expanded_args
3214
3215
3215 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3216 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3216 tests = []
3217 tests = []
3217 for t in args:
3218 for t in args:
3218 case = []
3219 case = []
3219
3220
3220 if not (
3221 if not (
3221 os.path.basename(t).startswith(b'test-')
3222 os.path.basename(t).startswith(b'test-')
3222 and (t.endswith(b'.py') or t.endswith(b'.t'))
3223 and (t.endswith(b'.py') or t.endswith(b'.t'))
3223 ):
3224 ):
3224
3225
3225 m = testcasepattern.match(os.path.basename(t))
3226 m = testcasepattern.match(os.path.basename(t))
3226 if m is not None:
3227 if m is not None:
3227 t_basename, casestr = m.groups()
3228 t_basename, casestr = m.groups()
3228 t = os.path.join(os.path.dirname(t), t_basename)
3229 t = os.path.join(os.path.dirname(t), t_basename)
3229 if casestr:
3230 if casestr:
3230 case = casestr.split(b'#')
3231 case = casestr.split(b'#')
3231 else:
3232 else:
3232 continue
3233 continue
3233
3234
3234 if t.endswith(b'.t'):
3235 if t.endswith(b'.t'):
3235 # .t file may contain multiple test cases
3236 # .t file may contain multiple test cases
3236 casedimensions = parsettestcases(t)
3237 casedimensions = parsettestcases(t)
3237 if casedimensions:
3238 if casedimensions:
3238 cases = []
3239 cases = []
3239
3240
3240 def addcases(case, casedimensions):
3241 def addcases(case, casedimensions):
3241 if not casedimensions:
3242 if not casedimensions:
3242 cases.append(case)
3243 cases.append(case)
3243 else:
3244 else:
3244 for c in casedimensions[0]:
3245 for c in casedimensions[0]:
3245 addcases(case + [c], casedimensions[1:])
3246 addcases(case + [c], casedimensions[1:])
3246
3247
3247 addcases([], casedimensions)
3248 addcases([], casedimensions)
3248 if case and case in cases:
3249 if case and case in cases:
3249 cases = [case]
3250 cases = [case]
3250 elif case:
3251 elif case:
3251 # Ignore invalid cases
3252 # Ignore invalid cases
3252 cases = []
3253 cases = []
3253 else:
3254 else:
3254 pass
3255 pass
3255 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3256 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3256 else:
3257 else:
3257 tests.append({'path': t})
3258 tests.append({'path': t})
3258 else:
3259 else:
3259 tests.append({'path': t})
3260 tests.append({'path': t})
3260
3261
3261 if self.options.retest:
3262 if self.options.retest:
3262 retest_args = []
3263 retest_args = []
3263 for test in tests:
3264 for test in tests:
3264 errpath = self._geterrpath(test)
3265 errpath = self._geterrpath(test)
3265 if os.path.exists(errpath):
3266 if os.path.exists(errpath):
3266 retest_args.append(test)
3267 retest_args.append(test)
3267 tests = retest_args
3268 tests = retest_args
3268 return tests
3269 return tests
3269
3270
3270 def _runtests(self, testdescs):
3271 def _runtests(self, testdescs):
3271 def _reloadtest(test, i):
3272 def _reloadtest(test, i):
3272 # convert a test back to its description dict
3273 # convert a test back to its description dict
3273 desc = {'path': test.path}
3274 desc = {'path': test.path}
3274 case = getattr(test, '_case', [])
3275 case = getattr(test, '_case', [])
3275 if case:
3276 if case:
3276 desc['case'] = case
3277 desc['case'] = case
3277 return self._gettest(desc, i)
3278 return self._gettest(desc, i)
3278
3279
3279 try:
3280 try:
3280 if self.options.restart:
3281 if self.options.restart:
3281 orig = list(testdescs)
3282 orig = list(testdescs)
3282 while testdescs:
3283 while testdescs:
3283 desc = testdescs[0]
3284 desc = testdescs[0]
3284 errpath = self._geterrpath(desc)
3285 errpath = self._geterrpath(desc)
3285 if os.path.exists(errpath):
3286 if os.path.exists(errpath):
3286 break
3287 break
3287 testdescs.pop(0)
3288 testdescs.pop(0)
3288 if not testdescs:
3289 if not testdescs:
3289 print("running all tests")
3290 print("running all tests")
3290 testdescs = orig
3291 testdescs = orig
3291
3292
3292 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3293 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3293 num_tests = len(tests) * self.options.runs_per_test
3294 num_tests = len(tests) * self.options.runs_per_test
3294
3295
3295 jobs = min(num_tests, self.options.jobs)
3296 jobs = min(num_tests, self.options.jobs)
3296
3297
3297 failed = False
3298 failed = False
3298 kws = self.options.keywords
3299 kws = self.options.keywords
3299 if kws is not None and PYTHON3:
3300 if kws is not None and PYTHON3:
3300 kws = kws.encode('utf-8')
3301 kws = kws.encode('utf-8')
3301
3302
3302 suite = TestSuite(
3303 suite = TestSuite(
3303 self._testdir,
3304 self._testdir,
3304 jobs=jobs,
3305 jobs=jobs,
3305 whitelist=self.options.whitelisted,
3306 whitelist=self.options.whitelisted,
3306 blacklist=self.options.blacklist,
3307 blacklist=self.options.blacklist,
3307 keywords=kws,
3308 keywords=kws,
3308 loop=self.options.loop,
3309 loop=self.options.loop,
3309 runs_per_test=self.options.runs_per_test,
3310 runs_per_test=self.options.runs_per_test,
3310 showchannels=self.options.showchannels,
3311 showchannels=self.options.showchannels,
3311 tests=tests,
3312 tests=tests,
3312 loadtest=_reloadtest,
3313 loadtest=_reloadtest,
3313 )
3314 )
3314 verbosity = 1
3315 verbosity = 1
3315 if self.options.list_tests:
3316 if self.options.list_tests:
3316 verbosity = 0
3317 verbosity = 0
3317 elif self.options.verbose:
3318 elif self.options.verbose:
3318 verbosity = 2
3319 verbosity = 2
3319 runner = TextTestRunner(self, verbosity=verbosity)
3320 runner = TextTestRunner(self, verbosity=verbosity)
3320
3321
3321 if self.options.list_tests:
3322 if self.options.list_tests:
3322 result = runner.listtests(suite)
3323 result = runner.listtests(suite)
3323 else:
3324 else:
3324 if self._installdir:
3325 if self._installdir:
3325 self._installhg()
3326 self._installhg()
3326 self._checkhglib("Testing")
3327 self._checkhglib("Testing")
3327 else:
3328 else:
3328 self._usecorrectpython()
3329 self._usecorrectpython()
3329 if self.options.chg:
3330 if self.options.chg:
3330 assert self._installdir
3331 assert self._installdir
3331 self._installchg()
3332 self._installchg()
3332
3333
3333 log(
3334 log(
3334 'running %d tests using %d parallel processes'
3335 'running %d tests using %d parallel processes'
3335 % (num_tests, jobs)
3336 % (num_tests, jobs)
3336 )
3337 )
3337
3338
3338 result = runner.run(suite)
3339 result = runner.run(suite)
3339
3340
3340 if result.failures or result.errors:
3341 if result.failures or result.errors:
3341 failed = True
3342 failed = True
3342
3343
3343 result.onEnd()
3344 result.onEnd()
3344
3345
3345 if self.options.anycoverage:
3346 if self.options.anycoverage:
3346 self._outputcoverage()
3347 self._outputcoverage()
3347 except KeyboardInterrupt:
3348 except KeyboardInterrupt:
3348 failed = True
3349 failed = True
3349 print("\ninterrupted!")
3350 print("\ninterrupted!")
3350
3351
3351 if failed:
3352 if failed:
3352 return 1
3353 return 1
3353
3354
3354 def _geterrpath(self, test):
3355 def _geterrpath(self, test):
3355 # test['path'] is a relative path
3356 # test['path'] is a relative path
3356 if 'case' in test:
3357 if 'case' in test:
3357 # for multiple dimensions test cases
3358 # for multiple dimensions test cases
3358 casestr = b'#'.join(test['case'])
3359 casestr = b'#'.join(test['case'])
3359 errpath = b'%s#%s.err' % (test['path'], casestr)
3360 errpath = b'%s#%s.err' % (test['path'], casestr)
3360 else:
3361 else:
3361 errpath = b'%s.err' % test['path']
3362 errpath = b'%s.err' % test['path']
3362 if self.options.outputdir:
3363 if self.options.outputdir:
3363 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3364 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3364 errpath = os.path.join(self._outputdir, errpath)
3365 errpath = os.path.join(self._outputdir, errpath)
3365 return errpath
3366 return errpath
3366
3367
3367 def _getport(self, count):
3368 def _getport(self, count):
3368 port = self._ports.get(count) # do we have a cached entry?
3369 port = self._ports.get(count) # do we have a cached entry?
3369 if port is None:
3370 if port is None:
3370 portneeded = 3
3371 portneeded = 3
3371 # above 100 tries we just give up and let test reports failure
3372 # above 100 tries we just give up and let test reports failure
3372 for tries in xrange(100):
3373 for tries in xrange(100):
3373 allfree = True
3374 allfree = True
3374 port = self.options.port + self._portoffset
3375 port = self.options.port + self._portoffset
3375 for idx in xrange(portneeded):
3376 for idx in xrange(portneeded):
3376 if not checkportisavailable(port + idx):
3377 if not checkportisavailable(port + idx):
3377 allfree = False
3378 allfree = False
3378 break
3379 break
3379 self._portoffset += portneeded
3380 self._portoffset += portneeded
3380 if allfree:
3381 if allfree:
3381 break
3382 break
3382 self._ports[count] = port
3383 self._ports[count] = port
3383 return port
3384 return port
3384
3385
3385 def _gettest(self, testdesc, count):
3386 def _gettest(self, testdesc, count):
3386 """Obtain a Test by looking at its filename.
3387 """Obtain a Test by looking at its filename.
3387
3388
3388 Returns a Test instance. The Test may not be runnable if it doesn't
3389 Returns a Test instance. The Test may not be runnable if it doesn't
3389 map to a known type.
3390 map to a known type.
3390 """
3391 """
3391 path = testdesc['path']
3392 path = testdesc['path']
3392 lctest = path.lower()
3393 lctest = path.lower()
3393 testcls = Test
3394 testcls = Test
3394
3395
3395 for ext, cls in self.TESTTYPES:
3396 for ext, cls in self.TESTTYPES:
3396 if lctest.endswith(ext):
3397 if lctest.endswith(ext):
3397 testcls = cls
3398 testcls = cls
3398 break
3399 break
3399
3400
3400 refpath = os.path.join(getcwdb(), path)
3401 refpath = os.path.join(getcwdb(), path)
3401 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3402 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3402
3403
3403 # extra keyword parameters. 'case' is used by .t tests
3404 # extra keyword parameters. 'case' is used by .t tests
3404 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3405 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3405
3406
3406 t = testcls(
3407 t = testcls(
3407 refpath,
3408 refpath,
3408 self._outputdir,
3409 self._outputdir,
3409 tmpdir,
3410 tmpdir,
3410 keeptmpdir=self.options.keep_tmpdir,
3411 keeptmpdir=self.options.keep_tmpdir,
3411 debug=self.options.debug,
3412 debug=self.options.debug,
3412 first=self.options.first,
3413 first=self.options.first,
3413 timeout=self.options.timeout,
3414 timeout=self.options.timeout,
3414 startport=self._getport(count),
3415 startport=self._getport(count),
3415 extraconfigopts=self.options.extra_config_opt,
3416 extraconfigopts=self.options.extra_config_opt,
3416 shell=self.options.shell,
3417 shell=self.options.shell,
3417 hgcommand=self._hgcommand,
3418 hgcommand=self._hgcommand,
3418 usechg=bool(self.options.with_chg or self.options.chg),
3419 usechg=bool(self.options.with_chg or self.options.chg),
3419 chgdebug=self.options.chg_debug,
3420 chgdebug=self.options.chg_debug,
3420 useipv6=useipv6,
3421 useipv6=useipv6,
3421 **kwds
3422 **kwds
3422 )
3423 )
3423 t.should_reload = True
3424 t.should_reload = True
3424 return t
3425 return t
3425
3426
3426 def _cleanup(self):
3427 def _cleanup(self):
3427 """Clean up state from this test invocation."""
3428 """Clean up state from this test invocation."""
3428 if self.options.keep_tmpdir:
3429 if self.options.keep_tmpdir:
3429 return
3430 return
3430
3431
3431 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3432 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3432 shutil.rmtree(self._hgtmp, True)
3433 shutil.rmtree(self._hgtmp, True)
3433 for f in self._createdfiles:
3434 for f in self._createdfiles:
3434 try:
3435 try:
3435 os.remove(f)
3436 os.remove(f)
3436 except OSError:
3437 except OSError:
3437 pass
3438 pass
3438
3439
3439 def _usecorrectpython(self):
3440 def _usecorrectpython(self):
3440 """Configure the environment to use the appropriate Python in tests."""
3441 """Configure the environment to use the appropriate Python in tests."""
3441 # Tests must use the same interpreter as us or bad things will happen.
3442 # Tests must use the same interpreter as us or bad things will happen.
3442 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3443 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3443
3444
3444 # os.symlink() is a thing with py3 on Windows, but it requires
3445 # os.symlink() is a thing with py3 on Windows, but it requires
3445 # Administrator rights.
3446 # Administrator rights.
3446 if getattr(os, 'symlink', None) and os.name != 'nt':
3447 if getattr(os, 'symlink', None) and os.name != 'nt':
3447 vlog(
3448 vlog(
3448 "# Making python executable in test path a symlink to '%s'"
3449 "# Making python executable in test path a symlink to '%s'"
3449 % sysexecutable
3450 % sysexecutable
3450 )
3451 )
3451 mypython = os.path.join(self._tmpbindir, pyexename)
3452 mypython = os.path.join(self._tmpbindir, pyexename)
3452 try:
3453 try:
3453 if os.readlink(mypython) == sysexecutable:
3454 if os.readlink(mypython) == sysexecutable:
3454 return
3455 return
3455 os.unlink(mypython)
3456 os.unlink(mypython)
3456 except OSError as err:
3457 except OSError as err:
3457 if err.errno != errno.ENOENT:
3458 if err.errno != errno.ENOENT:
3458 raise
3459 raise
3459 if self._findprogram(pyexename) != sysexecutable:
3460 if self._findprogram(pyexename) != sysexecutable:
3460 try:
3461 try:
3461 os.symlink(sysexecutable, mypython)
3462 os.symlink(sysexecutable, mypython)
3462 self._createdfiles.append(mypython)
3463 self._createdfiles.append(mypython)
3463 except OSError as err:
3464 except OSError as err:
3464 # child processes may race, which is harmless
3465 # child processes may race, which is harmless
3465 if err.errno != errno.EEXIST:
3466 if err.errno != errno.EEXIST:
3466 raise
3467 raise
3467 else:
3468 else:
3468 exedir, exename = os.path.split(sysexecutable)
3469 exedir, exename = os.path.split(sysexecutable)
3469 vlog(
3470 vlog(
3470 "# Modifying search path to find %s as %s in '%s'"
3471 "# Modifying search path to find %s as %s in '%s'"
3471 % (exename, pyexename, exedir)
3472 % (exename, pyexename, exedir)
3472 )
3473 )
3473 path = os.environ['PATH'].split(os.pathsep)
3474 path = os.environ['PATH'].split(os.pathsep)
3474 while exedir in path:
3475 while exedir in path:
3475 path.remove(exedir)
3476 path.remove(exedir)
3476 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3477 os.environ['PATH'] = os.pathsep.join([exedir] + path)
3477 if not self._findprogram(pyexename):
3478 if not self._findprogram(pyexename):
3478 print("WARNING: Cannot find %s in search path" % pyexename)
3479 print("WARNING: Cannot find %s in search path" % pyexename)
3479
3480
3480 def _installhg(self):
3481 def _installhg(self):
3481 """Install hg into the test environment.
3482 """Install hg into the test environment.
3482
3483
3483 This will also configure hg with the appropriate testing settings.
3484 This will also configure hg with the appropriate testing settings.
3484 """
3485 """
3485 vlog("# Performing temporary installation of HG")
3486 vlog("# Performing temporary installation of HG")
3486 installerrs = os.path.join(self._hgtmp, b"install.err")
3487 installerrs = os.path.join(self._hgtmp, b"install.err")
3487 compiler = ''
3488 compiler = ''
3488 if self.options.compiler:
3489 if self.options.compiler:
3489 compiler = '--compiler ' + self.options.compiler
3490 compiler = '--compiler ' + self.options.compiler
3490 setup_opts = b""
3491 setup_opts = b""
3491 if self.options.pure:
3492 if self.options.pure:
3492 setup_opts = b"--pure"
3493 setup_opts = b"--pure"
3493 elif self.options.rust:
3494 elif self.options.rust:
3494 setup_opts = b"--rust"
3495 setup_opts = b"--rust"
3495 elif self.options.no_rust:
3496 elif self.options.no_rust:
3496 setup_opts = b"--no-rust"
3497 setup_opts = b"--no-rust"
3497
3498
3498 # Run installer in hg root
3499 # Run installer in hg root
3499 script = os.path.realpath(sys.argv[0])
3500 script = os.path.realpath(sys.argv[0])
3500 exe = sysexecutable
3501 exe = sysexecutable
3501 if PYTHON3:
3502 if PYTHON3:
3502 compiler = _sys2bytes(compiler)
3503 compiler = _sys2bytes(compiler)
3503 script = _sys2bytes(script)
3504 script = _sys2bytes(script)
3504 exe = _sys2bytes(exe)
3505 exe = _sys2bytes(exe)
3505 hgroot = os.path.dirname(os.path.dirname(script))
3506 hgroot = os.path.dirname(os.path.dirname(script))
3506 self._hgroot = hgroot
3507 self._hgroot = hgroot
3507 os.chdir(hgroot)
3508 os.chdir(hgroot)
3508 nohome = b'--home=""'
3509 nohome = b'--home=""'
3509 if os.name == 'nt':
3510 if os.name == 'nt':
3510 # The --home="" trick works only on OS where os.sep == '/'
3511 # The --home="" trick works only on OS where os.sep == '/'
3511 # because of a distutils convert_path() fast-path. Avoid it at
3512 # because of a distutils convert_path() fast-path. Avoid it at
3512 # least on Windows for now, deal with .pydistutils.cfg bugs
3513 # least on Windows for now, deal with .pydistutils.cfg bugs
3513 # when they happen.
3514 # when they happen.
3514 nohome = b''
3515 nohome = b''
3515 cmd = (
3516 cmd = (
3516 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3517 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3517 b' build %(compiler)s --build-base="%(base)s"'
3518 b' build %(compiler)s --build-base="%(base)s"'
3518 b' install --force --prefix="%(prefix)s"'
3519 b' install --force --prefix="%(prefix)s"'
3519 b' --install-lib="%(libdir)s"'
3520 b' --install-lib="%(libdir)s"'
3520 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3521 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3521 % {
3522 % {
3522 b'exe': exe,
3523 b'exe': exe,
3523 b'setup_opts': setup_opts,
3524 b'setup_opts': setup_opts,
3524 b'compiler': compiler,
3525 b'compiler': compiler,
3525 b'base': os.path.join(self._hgtmp, b"build"),
3526 b'base': os.path.join(self._hgtmp, b"build"),
3526 b'prefix': self._installdir,
3527 b'prefix': self._installdir,
3527 b'libdir': self._pythondir,
3528 b'libdir': self._pythondir,
3528 b'bindir': self._bindir,
3529 b'bindir': self._bindir,
3529 b'nohome': nohome,
3530 b'nohome': nohome,
3530 b'logfile': installerrs,
3531 b'logfile': installerrs,
3531 }
3532 }
3532 )
3533 )
3533
3534
3534 # setuptools requires install directories to exist.
3535 # setuptools requires install directories to exist.
3535 def makedirs(p):
3536 def makedirs(p):
3536 try:
3537 try:
3537 os.makedirs(p)
3538 os.makedirs(p)
3538 except OSError as e:
3539 except OSError as e:
3539 if e.errno != errno.EEXIST:
3540 if e.errno != errno.EEXIST:
3540 raise
3541 raise
3541
3542
3542 makedirs(self._pythondir)
3543 makedirs(self._pythondir)
3543 makedirs(self._bindir)
3544 makedirs(self._bindir)
3544
3545
3545 vlog("# Running", cmd.decode("utf-8"))
3546 vlog("# Running", cmd.decode("utf-8"))
3546 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3547 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3547 if not self.options.verbose:
3548 if not self.options.verbose:
3548 try:
3549 try:
3549 os.remove(installerrs)
3550 os.remove(installerrs)
3550 except OSError as e:
3551 except OSError as e:
3551 if e.errno != errno.ENOENT:
3552 if e.errno != errno.ENOENT:
3552 raise
3553 raise
3553 else:
3554 else:
3554 with open(installerrs, 'rb') as f:
3555 with open(installerrs, 'rb') as f:
3555 for line in f:
3556 for line in f:
3556 if PYTHON3:
3557 if PYTHON3:
3557 sys.stdout.buffer.write(line)
3558 sys.stdout.buffer.write(line)
3558 else:
3559 else:
3559 sys.stdout.write(line)
3560 sys.stdout.write(line)
3560 sys.exit(1)
3561 sys.exit(1)
3561 os.chdir(self._testdir)
3562 os.chdir(self._testdir)
3562
3563
3563 self._usecorrectpython()
3564 self._usecorrectpython()
3564
3565
3565 hgbat = os.path.join(self._bindir, b'hg.bat')
3566 hgbat = os.path.join(self._bindir, b'hg.bat')
3566 if os.path.isfile(hgbat):
3567 if os.path.isfile(hgbat):
3567 # hg.bat expects to be put in bin/scripts while run-tests.py
3568 # hg.bat expects to be put in bin/scripts while run-tests.py
3568 # installation layout put it in bin/ directly. Fix it
3569 # installation layout put it in bin/ directly. Fix it
3569 with open(hgbat, 'rb') as f:
3570 with open(hgbat, 'rb') as f:
3570 data = f.read()
3571 data = f.read()
3571 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3572 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3572 data = data.replace(
3573 data = data.replace(
3573 br'"%~dp0..\python" "%~dp0hg" %*',
3574 br'"%~dp0..\python" "%~dp0hg" %*',
3574 b'"%~dp0python" "%~dp0hg" %*',
3575 b'"%~dp0python" "%~dp0hg" %*',
3575 )
3576 )
3576 with open(hgbat, 'wb') as f:
3577 with open(hgbat, 'wb') as f:
3577 f.write(data)
3578 f.write(data)
3578 else:
3579 else:
3579 print('WARNING: cannot fix hg.bat reference to python.exe')
3580 print('WARNING: cannot fix hg.bat reference to python.exe')
3580
3581
3581 if self.options.anycoverage:
3582 if self.options.anycoverage:
3582 custom = os.path.join(
3583 custom = os.path.join(
3583 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3584 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3584 )
3585 )
3585 target = os.path.join(self._pythondir, b'sitecustomize.py')
3586 target = os.path.join(self._pythondir, b'sitecustomize.py')
3586 vlog('# Installing coverage trigger to %s' % target)
3587 vlog('# Installing coverage trigger to %s' % target)
3587 shutil.copyfile(custom, target)
3588 shutil.copyfile(custom, target)
3588 rc = os.path.join(self._testdir, b'.coveragerc')
3589 rc = os.path.join(self._testdir, b'.coveragerc')
3589 vlog('# Installing coverage rc to %s' % rc)
3590 vlog('# Installing coverage rc to %s' % rc)
3590 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3591 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3591 covdir = os.path.join(self._installdir, b'..', b'coverage')
3592 covdir = os.path.join(self._installdir, b'..', b'coverage')
3592 try:
3593 try:
3593 os.mkdir(covdir)
3594 os.mkdir(covdir)
3594 except OSError as e:
3595 except OSError as e:
3595 if e.errno != errno.EEXIST:
3596 if e.errno != errno.EEXIST:
3596 raise
3597 raise
3597
3598
3598 osenvironb[b'COVERAGE_DIR'] = covdir
3599 osenvironb[b'COVERAGE_DIR'] = covdir
3599
3600
3600 def _checkhglib(self, verb):
3601 def _checkhglib(self, verb):
3601 """Ensure that the 'mercurial' package imported by python is
3602 """Ensure that the 'mercurial' package imported by python is
3602 the one we expect it to be. If not, print a warning to stderr."""
3603 the one we expect it to be. If not, print a warning to stderr."""
3603 if (self._bindir == self._pythondir) and (
3604 if (self._bindir == self._pythondir) and (
3604 self._bindir != self._tmpbindir
3605 self._bindir != self._tmpbindir
3605 ):
3606 ):
3606 # The pythondir has been inferred from --with-hg flag.
3607 # The pythondir has been inferred from --with-hg flag.
3607 # We cannot expect anything sensible here.
3608 # We cannot expect anything sensible here.
3608 return
3609 return
3609 expecthg = os.path.join(self._pythondir, b'mercurial')
3610 expecthg = os.path.join(self._pythondir, b'mercurial')
3610 actualhg = self._gethgpath()
3611 actualhg = self._gethgpath()
3611 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3612 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3612 sys.stderr.write(
3613 sys.stderr.write(
3613 'warning: %s with unexpected mercurial lib: %s\n'
3614 'warning: %s with unexpected mercurial lib: %s\n'
3614 ' (expected %s)\n' % (verb, actualhg, expecthg)
3615 ' (expected %s)\n' % (verb, actualhg, expecthg)
3615 )
3616 )
3616
3617
3617 def _gethgpath(self):
3618 def _gethgpath(self):
3618 """Return the path to the mercurial package that is actually found by
3619 """Return the path to the mercurial package that is actually found by
3619 the current Python interpreter."""
3620 the current Python interpreter."""
3620 if self._hgpath is not None:
3621 if self._hgpath is not None:
3621 return self._hgpath
3622 return self._hgpath
3622
3623
3623 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3624 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3624 cmd = cmd % PYTHON
3625 cmd = cmd % PYTHON
3625 if PYTHON3:
3626 if PYTHON3:
3626 cmd = _bytes2sys(cmd)
3627 cmd = _bytes2sys(cmd)
3627
3628
3628 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3629 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3629 out, err = p.communicate()
3630 out, err = p.communicate()
3630
3631
3631 self._hgpath = out.strip()
3632 self._hgpath = out.strip()
3632
3633
3633 return self._hgpath
3634 return self._hgpath
3634
3635
3635 def _installchg(self):
3636 def _installchg(self):
3636 """Install chg into the test environment"""
3637 """Install chg into the test environment"""
3637 vlog('# Performing temporary installation of CHG')
3638 vlog('# Performing temporary installation of CHG')
3638 assert os.path.dirname(self._bindir) == self._installdir
3639 assert os.path.dirname(self._bindir) == self._installdir
3639 assert self._hgroot, 'must be called after _installhg()'
3640 assert self._hgroot, 'must be called after _installhg()'
3640 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3641 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3641 b'make': b'make', # TODO: switch by option or environment?
3642 b'make': b'make', # TODO: switch by option or environment?
3642 b'prefix': self._installdir,
3643 b'prefix': self._installdir,
3643 }
3644 }
3644 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3645 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3645 vlog("# Running", cmd)
3646 vlog("# Running", cmd)
3646 proc = subprocess.Popen(
3647 proc = subprocess.Popen(
3647 cmd,
3648 cmd,
3648 shell=True,
3649 shell=True,
3649 cwd=cwd,
3650 cwd=cwd,
3650 stdin=subprocess.PIPE,
3651 stdin=subprocess.PIPE,
3651 stdout=subprocess.PIPE,
3652 stdout=subprocess.PIPE,
3652 stderr=subprocess.STDOUT,
3653 stderr=subprocess.STDOUT,
3653 )
3654 )
3654 out, _err = proc.communicate()
3655 out, _err = proc.communicate()
3655 if proc.returncode != 0:
3656 if proc.returncode != 0:
3656 if PYTHON3:
3657 if PYTHON3:
3657 sys.stdout.buffer.write(out)
3658 sys.stdout.buffer.write(out)
3658 else:
3659 else:
3659 sys.stdout.write(out)
3660 sys.stdout.write(out)
3660 sys.exit(1)
3661 sys.exit(1)
3661
3662
3662 def _outputcoverage(self):
3663 def _outputcoverage(self):
3663 """Produce code coverage output."""
3664 """Produce code coverage output."""
3664 import coverage
3665 import coverage
3665
3666
3666 coverage = coverage.coverage
3667 coverage = coverage.coverage
3667
3668
3668 vlog('# Producing coverage report')
3669 vlog('# Producing coverage report')
3669 # chdir is the easiest way to get short, relative paths in the
3670 # chdir is the easiest way to get short, relative paths in the
3670 # output.
3671 # output.
3671 os.chdir(self._hgroot)
3672 os.chdir(self._hgroot)
3672 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3673 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3673 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3674 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3674
3675
3675 # Map install directory paths back to source directory.
3676 # Map install directory paths back to source directory.
3676 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3677 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3677
3678
3678 cov.combine()
3679 cov.combine()
3679
3680
3680 omit = [
3681 omit = [
3681 _bytes2sys(os.path.join(x, b'*'))
3682 _bytes2sys(os.path.join(x, b'*'))
3682 for x in [self._bindir, self._testdir]
3683 for x in [self._bindir, self._testdir]
3683 ]
3684 ]
3684 cov.report(ignore_errors=True, omit=omit)
3685 cov.report(ignore_errors=True, omit=omit)
3685
3686
3686 if self.options.htmlcov:
3687 if self.options.htmlcov:
3687 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3688 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3688 cov.html_report(directory=htmldir, omit=omit)
3689 cov.html_report(directory=htmldir, omit=omit)
3689 if self.options.annotate:
3690 if self.options.annotate:
3690 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3691 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3691 if not os.path.isdir(adir):
3692 if not os.path.isdir(adir):
3692 os.mkdir(adir)
3693 os.mkdir(adir)
3693 cov.annotate(directory=adir, omit=omit)
3694 cov.annotate(directory=adir, omit=omit)
3694
3695
3695 def _findprogram(self, program):
3696 def _findprogram(self, program):
3696 """Search PATH for a executable program"""
3697 """Search PATH for a executable program"""
3697 dpb = _sys2bytes(os.defpath)
3698 dpb = _sys2bytes(os.defpath)
3698 sepb = _sys2bytes(os.pathsep)
3699 sepb = _sys2bytes(os.pathsep)
3699 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3700 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3700 name = os.path.join(p, program)
3701 name = os.path.join(p, program)
3701 if os.name == 'nt' or os.access(name, os.X_OK):
3702 if os.name == 'nt' or os.access(name, os.X_OK):
3702 return _bytes2sys(name)
3703 return _bytes2sys(name)
3703 return None
3704 return None
3704
3705
3705 def _checktools(self):
3706 def _checktools(self):
3706 """Ensure tools required to run tests are present."""
3707 """Ensure tools required to run tests are present."""
3707 for p in self.REQUIREDTOOLS:
3708 for p in self.REQUIREDTOOLS:
3708 if os.name == 'nt' and not p.endswith(b'.exe'):
3709 if os.name == 'nt' and not p.endswith(b'.exe'):
3709 p += b'.exe'
3710 p += b'.exe'
3710 found = self._findprogram(p)
3711 found = self._findprogram(p)
3711 p = p.decode("utf-8")
3712 p = p.decode("utf-8")
3712 if found:
3713 if found:
3713 vlog("# Found prerequisite", p, "at", found)
3714 vlog("# Found prerequisite", p, "at", found)
3714 else:
3715 else:
3715 print("WARNING: Did not find prerequisite tool: %s " % p)
3716 print("WARNING: Did not find prerequisite tool: %s " % p)
3716
3717
3717
3718
3718 def aggregateexceptions(path):
3719 def aggregateexceptions(path):
3719 exceptioncounts = collections.Counter()
3720 exceptioncounts = collections.Counter()
3720 testsbyfailure = collections.defaultdict(set)
3721 testsbyfailure = collections.defaultdict(set)
3721 failuresbytest = collections.defaultdict(set)
3722 failuresbytest = collections.defaultdict(set)
3722
3723
3723 for f in os.listdir(path):
3724 for f in os.listdir(path):
3724 with open(os.path.join(path, f), 'rb') as fh:
3725 with open(os.path.join(path, f), 'rb') as fh:
3725 data = fh.read().split(b'\0')
3726 data = fh.read().split(b'\0')
3726 if len(data) != 5:
3727 if len(data) != 5:
3727 continue
3728 continue
3728
3729
3729 exc, mainframe, hgframe, hgline, testname = data
3730 exc, mainframe, hgframe, hgline, testname = data
3730 exc = exc.decode('utf-8')
3731 exc = exc.decode('utf-8')
3731 mainframe = mainframe.decode('utf-8')
3732 mainframe = mainframe.decode('utf-8')
3732 hgframe = hgframe.decode('utf-8')
3733 hgframe = hgframe.decode('utf-8')
3733 hgline = hgline.decode('utf-8')
3734 hgline = hgline.decode('utf-8')
3734 testname = testname.decode('utf-8')
3735 testname = testname.decode('utf-8')
3735
3736
3736 key = (hgframe, hgline, exc)
3737 key = (hgframe, hgline, exc)
3737 exceptioncounts[key] += 1
3738 exceptioncounts[key] += 1
3738 testsbyfailure[key].add(testname)
3739 testsbyfailure[key].add(testname)
3739 failuresbytest[testname].add(key)
3740 failuresbytest[testname].add(key)
3740
3741
3741 # Find test having fewest failures for each failure.
3742 # Find test having fewest failures for each failure.
3742 leastfailing = {}
3743 leastfailing = {}
3743 for key, tests in testsbyfailure.items():
3744 for key, tests in testsbyfailure.items():
3744 fewesttest = None
3745 fewesttest = None
3745 fewestcount = 99999999
3746 fewestcount = 99999999
3746 for test in sorted(tests):
3747 for test in sorted(tests):
3747 if len(failuresbytest[test]) < fewestcount:
3748 if len(failuresbytest[test]) < fewestcount:
3748 fewesttest = test
3749 fewesttest = test
3749 fewestcount = len(failuresbytest[test])
3750 fewestcount = len(failuresbytest[test])
3750
3751
3751 leastfailing[key] = (fewestcount, fewesttest)
3752 leastfailing[key] = (fewestcount, fewesttest)
3752
3753
3753 # Create a combined counter so we can sort by total occurrences and
3754 # Create a combined counter so we can sort by total occurrences and
3754 # impacted tests.
3755 # impacted tests.
3755 combined = {}
3756 combined = {}
3756 for key in exceptioncounts:
3757 for key in exceptioncounts:
3757 combined[key] = (
3758 combined[key] = (
3758 exceptioncounts[key],
3759 exceptioncounts[key],
3759 len(testsbyfailure[key]),
3760 len(testsbyfailure[key]),
3760 leastfailing[key][0],
3761 leastfailing[key][0],
3761 leastfailing[key][1],
3762 leastfailing[key][1],
3762 )
3763 )
3763
3764
3764 return {
3765 return {
3765 'exceptioncounts': exceptioncounts,
3766 'exceptioncounts': exceptioncounts,
3766 'total': sum(exceptioncounts.values()),
3767 'total': sum(exceptioncounts.values()),
3767 'combined': combined,
3768 'combined': combined,
3768 'leastfailing': leastfailing,
3769 'leastfailing': leastfailing,
3769 'byfailure': testsbyfailure,
3770 'byfailure': testsbyfailure,
3770 'bytest': failuresbytest,
3771 'bytest': failuresbytest,
3771 }
3772 }
3772
3773
3773
3774
3774 if __name__ == '__main__':
3775 if __name__ == '__main__':
3775 runner = TestRunner()
3776 runner = TestRunner()
3776
3777
3777 try:
3778 try:
3778 import msvcrt
3779 import msvcrt
3779
3780
3780 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3781 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3781 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3782 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3782 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3783 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3783 except ImportError:
3784 except ImportError:
3784 pass
3785 pass
3785
3786
3786 sys.exit(runner.run(sys.argv[1:]))
3787 sys.exit(runner.run(sys.argv[1:]))
@@ -1,228 +1,229 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 devel.all-warnings=true
5 devel.all-warnings=true
6 devel.default-date=0 0
6 devel.default-date=0 0
7 extensions.fsmonitor= (fsmonitor !)
7 extensions.fsmonitor= (fsmonitor !)
8 largefiles.usercache=$TESTTMP/.cache/largefiles
8 largefiles.usercache=$TESTTMP/.cache/largefiles
9 lfs.usercache=$TESTTMP/.cache/lfs
9 lfs.usercache=$TESTTMP/.cache/lfs
10 ui.slash=True
10 ui.slash=True
11 ui.interactive=False
11 ui.interactive=False
12 ui.detailed-exit-code=True
12 ui.detailed-exit-code=True
13 ui.merge=internal:merge
13 ui.merge=internal:merge
14 ui.mergemarkers=detailed
14 ui.mergemarkers=detailed
15 ui.promptecho=True
15 ui.promptecho=True
16 ui.timeout.warn=15
16 web.address=localhost
17 web.address=localhost
17 web\.ipv6=(?:True|False) (re)
18 web\.ipv6=(?:True|False) (re)
18 web.server-header=testing stub value
19 web.server-header=testing stub value
19 #endif
20 #endif
20
21
21 $ hg init t
22 $ hg init t
22 $ cd t
23 $ cd t
23
24
24 Prepare a changeset:
25 Prepare a changeset:
25
26
26 $ echo a > a
27 $ echo a > a
27 $ hg add a
28 $ hg add a
28
29
29 $ hg status
30 $ hg status
30 A a
31 A a
31
32
32 Writes to stdio succeed and fail appropriately
33 Writes to stdio succeed and fail appropriately
33
34
34 #if devfull
35 #if devfull
35 $ hg status 2>/dev/full
36 $ hg status 2>/dev/full
36 A a
37 A a
37
38
38 $ hg status >/dev/full
39 $ hg status >/dev/full
39 abort: No space left on device
40 abort: No space left on device
40 [255]
41 [255]
41 #endif
42 #endif
42
43
43 #if devfull
44 #if devfull
44 $ hg status >/dev/full 2>&1
45 $ hg status >/dev/full 2>&1
45 [255]
46 [255]
46
47
47 $ hg status ENOENT 2>/dev/full
48 $ hg status ENOENT 2>/dev/full
48 [255]
49 [255]
49 #endif
50 #endif
50
51
51 $ hg commit -m test
52 $ hg commit -m test
52
53
53 This command is ancient:
54 This command is ancient:
54
55
55 $ hg history
56 $ hg history
56 changeset: 0:acb14030fe0a
57 changeset: 0:acb14030fe0a
57 tag: tip
58 tag: tip
58 user: test
59 user: test
59 date: Thu Jan 01 00:00:00 1970 +0000
60 date: Thu Jan 01 00:00:00 1970 +0000
60 summary: test
61 summary: test
61
62
62
63
63 Verify that updating to revision 0 via commands.update() works properly
64 Verify that updating to revision 0 via commands.update() works properly
64
65
65 $ cat <<EOF > update_to_rev0.py
66 $ cat <<EOF > update_to_rev0.py
66 > from mercurial import commands, hg, ui as uimod
67 > from mercurial import commands, hg, ui as uimod
67 > myui = uimod.ui.load()
68 > myui = uimod.ui.load()
68 > repo = hg.repository(myui, path=b'.')
69 > repo = hg.repository(myui, path=b'.')
69 > commands.update(myui, repo, rev=b"0")
70 > commands.update(myui, repo, rev=b"0")
70 > EOF
71 > EOF
71 $ hg up null
72 $ hg up null
72 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 $ "$PYTHON" ./update_to_rev0.py
74 $ "$PYTHON" ./update_to_rev0.py
74 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 $ hg identify -n
76 $ hg identify -n
76 0
77 0
77
78
78
79
79 Poke around at hashes:
80 Poke around at hashes:
80
81
81 $ hg manifest --debug
82 $ hg manifest --debug
82 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
83 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
83
84
84 $ hg cat a
85 $ hg cat a
85 a
86 a
86
87
87 Verify should succeed:
88 Verify should succeed:
88
89
89 $ hg verify
90 $ hg verify
90 checking changesets
91 checking changesets
91 checking manifests
92 checking manifests
92 crosschecking files in changesets and manifests
93 crosschecking files in changesets and manifests
93 checking files
94 checking files
94 checked 1 changesets with 1 changes to 1 files
95 checked 1 changesets with 1 changes to 1 files
95
96
96 Repository root:
97 Repository root:
97
98
98 $ hg root
99 $ hg root
99 $TESTTMP/t
100 $TESTTMP/t
100 $ hg log -l1 -T '{reporoot}\n'
101 $ hg log -l1 -T '{reporoot}\n'
101 $TESTTMP/t
102 $TESTTMP/t
102 $ hg root -Tjson | sed 's|\\\\|\\|g'
103 $ hg root -Tjson | sed 's|\\\\|\\|g'
103 [
104 [
104 {
105 {
105 "hgpath": "$TESTTMP/t/.hg",
106 "hgpath": "$TESTTMP/t/.hg",
106 "reporoot": "$TESTTMP/t",
107 "reporoot": "$TESTTMP/t",
107 "storepath": "$TESTTMP/t/.hg/store"
108 "storepath": "$TESTTMP/t/.hg/store"
108 }
109 }
109 ]
110 ]
110
111
111 At the end...
112 At the end...
112
113
113 $ cd ..
114 $ cd ..
114
115
115 Status message redirection:
116 Status message redirection:
116
117
117 $ hg init empty
118 $ hg init empty
118
119
119 status messages are sent to stdout by default:
120 status messages are sent to stdout by default:
120
121
121 $ hg outgoing -R t empty -Tjson 2>/dev/null
122 $ hg outgoing -R t empty -Tjson 2>/dev/null
122 comparing with empty
123 comparing with empty
123 searching for changes
124 searching for changes
124 [
125 [
125 {
126 {
126 "bookmarks": [],
127 "bookmarks": [],
127 "branch": "default",
128 "branch": "default",
128 "date": [0, 0],
129 "date": [0, 0],
129 "desc": "test",
130 "desc": "test",
130 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
131 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
131 "parents": ["0000000000000000000000000000000000000000"],
132 "parents": ["0000000000000000000000000000000000000000"],
132 "phase": "draft",
133 "phase": "draft",
133 "rev": 0,
134 "rev": 0,
134 "tags": ["tip"],
135 "tags": ["tip"],
135 "user": "test"
136 "user": "test"
136 }
137 }
137 ]
138 ]
138
139
139 which can be configured to send to stderr, so the output wouldn't be
140 which can be configured to send to stderr, so the output wouldn't be
140 interleaved:
141 interleaved:
141
142
142 $ cat <<'EOF' >> "$HGRCPATH"
143 $ cat <<'EOF' >> "$HGRCPATH"
143 > [ui]
144 > [ui]
144 > message-output = stderr
145 > message-output = stderr
145 > EOF
146 > EOF
146 $ hg outgoing -R t empty -Tjson 2>/dev/null
147 $ hg outgoing -R t empty -Tjson 2>/dev/null
147 [
148 [
148 {
149 {
149 "bookmarks": [],
150 "bookmarks": [],
150 "branch": "default",
151 "branch": "default",
151 "date": [0, 0],
152 "date": [0, 0],
152 "desc": "test",
153 "desc": "test",
153 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
154 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
154 "parents": ["0000000000000000000000000000000000000000"],
155 "parents": ["0000000000000000000000000000000000000000"],
155 "phase": "draft",
156 "phase": "draft",
156 "rev": 0,
157 "rev": 0,
157 "tags": ["tip"],
158 "tags": ["tip"],
158 "user": "test"
159 "user": "test"
159 }
160 }
160 ]
161 ]
161 $ hg outgoing -R t empty -Tjson >/dev/null
162 $ hg outgoing -R t empty -Tjson >/dev/null
162 comparing with empty
163 comparing with empty
163 searching for changes
164 searching for changes
164
165
165 this option should be turned off by HGPLAIN= since it may break scripting use:
166 this option should be turned off by HGPLAIN= since it may break scripting use:
166
167
167 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
168 $ HGPLAIN= hg outgoing -R t empty -Tjson 2>/dev/null
168 comparing with empty
169 comparing with empty
169 searching for changes
170 searching for changes
170 [
171 [
171 {
172 {
172 "bookmarks": [],
173 "bookmarks": [],
173 "branch": "default",
174 "branch": "default",
174 "date": [0, 0],
175 "date": [0, 0],
175 "desc": "test",
176 "desc": "test",
176 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
177 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
177 "parents": ["0000000000000000000000000000000000000000"],
178 "parents": ["0000000000000000000000000000000000000000"],
178 "phase": "draft",
179 "phase": "draft",
179 "rev": 0,
180 "rev": 0,
180 "tags": ["tip"],
181 "tags": ["tip"],
181 "user": "test"
182 "user": "test"
182 }
183 }
183 ]
184 ]
184
185
185 but still overridden by --config:
186 but still overridden by --config:
186
187
187 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
188 $ HGPLAIN= hg outgoing -R t empty -Tjson --config ui.message-output=stderr \
188 > 2>/dev/null
189 > 2>/dev/null
189 [
190 [
190 {
191 {
191 "bookmarks": [],
192 "bookmarks": [],
192 "branch": "default",
193 "branch": "default",
193 "date": [0, 0],
194 "date": [0, 0],
194 "desc": "test",
195 "desc": "test",
195 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
196 "node": "acb14030fe0a21b60322c440ad2d20cf7685a376",
196 "parents": ["0000000000000000000000000000000000000000"],
197 "parents": ["0000000000000000000000000000000000000000"],
197 "phase": "draft",
198 "phase": "draft",
198 "rev": 0,
199 "rev": 0,
199 "tags": ["tip"],
200 "tags": ["tip"],
200 "user": "test"
201 "user": "test"
201 }
202 }
202 ]
203 ]
203
204
204 Invalid ui.message-output option:
205 Invalid ui.message-output option:
205
206
206 $ hg log -R t --config ui.message-output=bad
207 $ hg log -R t --config ui.message-output=bad
207 abort: invalid ui.message-output destination: bad
208 abort: invalid ui.message-output destination: bad
208 [255]
209 [255]
209
210
210 Underlying message streams should be updated when ui.fout/ferr are set:
211 Underlying message streams should be updated when ui.fout/ferr are set:
211
212
212 $ cat <<'EOF' > capui.py
213 $ cat <<'EOF' > capui.py
213 > from mercurial import pycompat, registrar
214 > from mercurial import pycompat, registrar
214 > cmdtable = {}
215 > cmdtable = {}
215 > command = registrar.command(cmdtable)
216 > command = registrar.command(cmdtable)
216 > @command(b'capui', norepo=True)
217 > @command(b'capui', norepo=True)
217 > def capui(ui):
218 > def capui(ui):
218 > out = ui.fout
219 > out = ui.fout
219 > ui.fout = pycompat.bytesio()
220 > ui.fout = pycompat.bytesio()
220 > ui.status(b'status\n')
221 > ui.status(b'status\n')
221 > ui.ferr = pycompat.bytesio()
222 > ui.ferr = pycompat.bytesio()
222 > ui.warn(b'warn\n')
223 > ui.warn(b'warn\n')
223 > out.write(b'stdout: %s' % ui.fout.getvalue())
224 > out.write(b'stdout: %s' % ui.fout.getvalue())
224 > out.write(b'stderr: %s' % ui.ferr.getvalue())
225 > out.write(b'stderr: %s' % ui.ferr.getvalue())
225 > EOF
226 > EOF
226 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
227 $ hg --config extensions.capui=capui.py --config ui.message-output=stdio capui
227 stdout: status
228 stdout: status
228 stderr: warn
229 stderr: warn
@@ -1,1158 +1,1160 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import absolute_import
16 >>> from __future__ import absolute_import
17 >>> import os
17 >>> import os
18 >>> import sys
18 >>> import sys
19 >>> from hgclient import bprint, check, readchannel, runcommand
19 >>> from hgclient import bprint, check, readchannel, runcommand
20 >>> @check
20 >>> @check
21 ... def hellomessage(server):
21 ... def hellomessage(server):
22 ... ch, data = readchannel(server)
22 ... ch, data = readchannel(server)
23 ... bprint(b'%c, %r' % (ch, data))
23 ... bprint(b'%c, %r' % (ch, data))
24 ... # run an arbitrary command to make sure the next thing the server
24 ... # run an arbitrary command to make sure the next thing the server
25 ... # sends isn't part of the hello message
25 ... # sends isn't part of the hello message
26 ... runcommand(server, [b'id'])
26 ... runcommand(server, [b'id'])
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 *** runcommand id
28 *** runcommand id
29 000000000000 tip
29 000000000000 tip
30
30
31 >>> from hgclient import check
31 >>> from hgclient import check
32 >>> @check
32 >>> @check
33 ... def unknowncommand(server):
33 ... def unknowncommand(server):
34 ... server.stdin.write(b'unknowncommand\n')
34 ... server.stdin.write(b'unknowncommand\n')
35 abort: unknown command unknowncommand
35 abort: unknown command unknowncommand
36
36
37 >>> from hgclient import check, readchannel, runcommand
37 >>> from hgclient import check, readchannel, runcommand
38 >>> @check
38 >>> @check
39 ... def checkruncommand(server):
39 ... def checkruncommand(server):
40 ... # hello block
40 ... # hello block
41 ... readchannel(server)
41 ... readchannel(server)
42 ...
42 ...
43 ... # no args
43 ... # no args
44 ... runcommand(server, [])
44 ... runcommand(server, [])
45 ...
45 ...
46 ... # global options
46 ... # global options
47 ... runcommand(server, [b'id', b'--quiet'])
47 ... runcommand(server, [b'id', b'--quiet'])
48 ...
48 ...
49 ... # make sure global options don't stick through requests
49 ... # make sure global options don't stick through requests
50 ... runcommand(server, [b'id'])
50 ... runcommand(server, [b'id'])
51 ...
51 ...
52 ... # --config
52 ... # --config
53 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
53 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
54 ...
54 ...
55 ... # make sure --config doesn't stick
55 ... # make sure --config doesn't stick
56 ... runcommand(server, [b'id'])
56 ... runcommand(server, [b'id'])
57 ...
57 ...
58 ... # negative return code should be masked
58 ... # negative return code should be masked
59 ... runcommand(server, [b'id', b'-runknown'])
59 ... runcommand(server, [b'id', b'-runknown'])
60 *** runcommand
60 *** runcommand
61 Mercurial Distributed SCM
61 Mercurial Distributed SCM
62
62
63 basic commands:
63 basic commands:
64
64
65 add add the specified files on the next commit
65 add add the specified files on the next commit
66 annotate show changeset information by line for each file
66 annotate show changeset information by line for each file
67 clone make a copy of an existing repository
67 clone make a copy of an existing repository
68 commit commit the specified files or all outstanding changes
68 commit commit the specified files or all outstanding changes
69 diff diff repository (or selected files)
69 diff diff repository (or selected files)
70 export dump the header and diffs for one or more changesets
70 export dump the header and diffs for one or more changesets
71 forget forget the specified files on the next commit
71 forget forget the specified files on the next commit
72 init create a new repository in the given directory
72 init create a new repository in the given directory
73 log show revision history of entire repository or files
73 log show revision history of entire repository or files
74 merge merge another revision into working directory
74 merge merge another revision into working directory
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 remove remove the specified files on the next commit
77 remove remove the specified files on the next commit
78 serve start stand-alone webserver
78 serve start stand-alone webserver
79 status show changed files in the working directory
79 status show changed files in the working directory
80 summary summarize working directory state
80 summary summarize working directory state
81 update update working directory (or switch revisions)
81 update update working directory (or switch revisions)
82
82
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 *** runcommand id --quiet
84 *** runcommand id --quiet
85 000000000000
85 000000000000
86 *** runcommand id
86 *** runcommand id
87 000000000000 tip
87 000000000000 tip
88 *** runcommand id --config ui.quiet=True
88 *** runcommand id --config ui.quiet=True
89 000000000000
89 000000000000
90 *** runcommand id
90 *** runcommand id
91 000000000000 tip
91 000000000000 tip
92 *** runcommand id -runknown
92 *** runcommand id -runknown
93 abort: unknown revision 'unknown'
93 abort: unknown revision 'unknown'
94 [255]
94 [255]
95
95
96 >>> from hgclient import bprint, check, readchannel
96 >>> from hgclient import bprint, check, readchannel
97 >>> @check
97 >>> @check
98 ... def inputeof(server):
98 ... def inputeof(server):
99 ... readchannel(server)
99 ... readchannel(server)
100 ... server.stdin.write(b'runcommand\n')
100 ... server.stdin.write(b'runcommand\n')
101 ... # close stdin while server is waiting for input
101 ... # close stdin while server is waiting for input
102 ... server.stdin.close()
102 ... server.stdin.close()
103 ...
103 ...
104 ... # server exits with 1 if the pipe closed while reading the command
104 ... # server exits with 1 if the pipe closed while reading the command
105 ... bprint(b'server exit code =', b'%d' % server.wait())
105 ... bprint(b'server exit code =', b'%d' % server.wait())
106 server exit code = 1
106 server exit code = 1
107
107
108 >>> from hgclient import check, readchannel, runcommand, stringio
108 >>> from hgclient import check, readchannel, runcommand, stringio
109 >>> @check
109 >>> @check
110 ... def serverinput(server):
110 ... def serverinput(server):
111 ... readchannel(server)
111 ... readchannel(server)
112 ...
112 ...
113 ... patch = b"""
113 ... patch = b"""
114 ... # HG changeset patch
114 ... # HG changeset patch
115 ... # User test
115 ... # User test
116 ... # Date 0 0
116 ... # Date 0 0
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 ... # Parent 0000000000000000000000000000000000000000
118 ... # Parent 0000000000000000000000000000000000000000
119 ... 1
119 ... 1
120 ...
120 ...
121 ... diff -r 000000000000 -r c103a3dec114 a
121 ... diff -r 000000000000 -r c103a3dec114 a
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 ... @@ -0,0 +1,1 @@
124 ... @@ -0,0 +1,1 @@
125 ... +1
125 ... +1
126 ... """
126 ... """
127 ...
127 ...
128 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
128 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
129 ... runcommand(server, [b'log'])
129 ... runcommand(server, [b'log'])
130 *** runcommand import -
130 *** runcommand import -
131 applying patch from stdin
131 applying patch from stdin
132 *** runcommand log
132 *** runcommand log
133 changeset: 0:eff892de26ec
133 changeset: 0:eff892de26ec
134 tag: tip
134 tag: tip
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
136 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: 1
137 summary: 1
138
138
139
139
140 check strict parsing of early options:
140 check strict parsing of early options:
141
141
142 >>> import os
142 >>> import os
143 >>> from hgclient import check, readchannel, runcommand
143 >>> from hgclient import check, readchannel, runcommand
144 >>> os.environ['HGPLAIN'] = '+strictflags'
144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 >>> @check
145 >>> @check
146 ... def cwd(server):
146 ... def cwd(server):
147 ... readchannel(server)
147 ... readchannel(server)
148 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
148 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
149 ... b'default'])
149 ... b'default'])
150 *** runcommand log -b --config=alias.log=!echo pwned default
150 *** runcommand log -b --config=alias.log=!echo pwned default
151 abort: unknown revision '--config=alias.log=!echo pwned'
151 abort: unknown revision '--config=alias.log=!echo pwned'
152 [255]
152 [255]
153
153
154 check that "histedit --commands=-" can read rules from the input channel:
154 check that "histedit --commands=-" can read rules from the input channel:
155
155
156 >>> from hgclient import check, readchannel, runcommand, stringio
156 >>> from hgclient import check, readchannel, runcommand, stringio
157 >>> @check
157 >>> @check
158 ... def serverinput(server):
158 ... def serverinput(server):
159 ... readchannel(server)
159 ... readchannel(server)
160 ... rules = b'pick eff892de26ec\n'
160 ... rules = b'pick eff892de26ec\n'
161 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
161 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
162 ... b'--config', b'extensions.histedit='],
162 ... b'--config', b'extensions.histedit='],
163 ... input=stringio(rules))
163 ... input=stringio(rules))
164 *** runcommand histedit 0 --commands=- --config extensions.histedit=
164 *** runcommand histedit 0 --commands=- --config extensions.histedit=
165
165
166 check that --cwd doesn't persist between requests:
166 check that --cwd doesn't persist between requests:
167
167
168 $ mkdir foo
168 $ mkdir foo
169 $ touch foo/bar
169 $ touch foo/bar
170 >>> from hgclient import check, readchannel, runcommand
170 >>> from hgclient import check, readchannel, runcommand
171 >>> @check
171 >>> @check
172 ... def cwd(server):
172 ... def cwd(server):
173 ... readchannel(server)
173 ... readchannel(server)
174 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
174 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
175 ... runcommand(server, [b'st', b'foo/bar'])
175 ... runcommand(server, [b'st', b'foo/bar'])
176 *** runcommand --cwd foo st bar
176 *** runcommand --cwd foo st bar
177 ? bar
177 ? bar
178 *** runcommand st foo/bar
178 *** runcommand st foo/bar
179 ? foo/bar
179 ? foo/bar
180
180
181 $ rm foo/bar
181 $ rm foo/bar
182
182
183
183
184 check that local configs for the cached repo aren't inherited when -R is used:
184 check that local configs for the cached repo aren't inherited when -R is used:
185
185
186 $ cat <<EOF >> .hg/hgrc
186 $ cat <<EOF >> .hg/hgrc
187 > [ui]
187 > [ui]
188 > foo = bar
188 > foo = bar
189 > EOF
189 > EOF
190
190
191 #if no-extraextensions
191 #if no-extraextensions
192
192
193 >>> from hgclient import check, readchannel, runcommand, sep
193 >>> from hgclient import check, readchannel, runcommand, sep
194 >>> @check
194 >>> @check
195 ... def localhgrc(server):
195 ... def localhgrc(server):
196 ... readchannel(server)
196 ... readchannel(server)
197 ...
197 ...
198 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
198 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
199 ... # show it
199 ... # show it
200 ... runcommand(server, [b'showconfig'], outfilter=sep)
200 ... runcommand(server, [b'showconfig'], outfilter=sep)
201 ...
201 ...
202 ... # but not for this repo
202 ... # but not for this repo
203 ... runcommand(server, [b'init', b'foo'])
203 ... runcommand(server, [b'init', b'foo'])
204 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
204 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
205 *** runcommand showconfig
205 *** runcommand showconfig
206 bundle.mainreporoot=$TESTTMP/repo
206 bundle.mainreporoot=$TESTTMP/repo
207 devel.all-warnings=true
207 devel.all-warnings=true
208 devel.default-date=0 0
208 devel.default-date=0 0
209 extensions.fsmonitor= (fsmonitor !)
209 extensions.fsmonitor= (fsmonitor !)
210 largefiles.usercache=$TESTTMP/.cache/largefiles
210 largefiles.usercache=$TESTTMP/.cache/largefiles
211 lfs.usercache=$TESTTMP/.cache/lfs
211 lfs.usercache=$TESTTMP/.cache/lfs
212 ui.slash=True
212 ui.slash=True
213 ui.interactive=False
213 ui.interactive=False
214 ui.detailed-exit-code=True
214 ui.detailed-exit-code=True
215 ui.merge=internal:merge
215 ui.merge=internal:merge
216 ui.mergemarkers=detailed
216 ui.mergemarkers=detailed
217 ui.timeout.warn=15
217 ui.foo=bar
218 ui.foo=bar
218 ui.nontty=true
219 ui.nontty=true
219 web.address=localhost
220 web.address=localhost
220 web\.ipv6=(?:True|False) (re)
221 web\.ipv6=(?:True|False) (re)
221 web.server-header=testing stub value
222 web.server-header=testing stub value
222 *** runcommand init foo
223 *** runcommand init foo
223 *** runcommand -R foo showconfig ui defaults
224 *** runcommand -R foo showconfig ui defaults
224 ui.slash=True
225 ui.slash=True
225 ui.interactive=False
226 ui.interactive=False
226 ui.detailed-exit-code=True
227 ui.detailed-exit-code=True
227 ui.merge=internal:merge
228 ui.merge=internal:merge
228 ui.mergemarkers=detailed
229 ui.mergemarkers=detailed
230 ui.timeout.warn=15
229 ui.nontty=true
231 ui.nontty=true
230 #endif
232 #endif
231
233
232 $ rm -R foo
234 $ rm -R foo
233
235
234 #if windows
236 #if windows
235 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
237 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
236 #else
238 #else
237 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
239 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
238 #endif
240 #endif
239
241
240 $ cat <<EOF > hook.py
242 $ cat <<EOF > hook.py
241 > import sys
243 > import sys
242 > from hgclient import bprint
244 > from hgclient import bprint
243 > def hook(**args):
245 > def hook(**args):
244 > bprint(b'hook talking')
246 > bprint(b'hook talking')
245 > bprint(b'now try to read something: %r' % sys.stdin.read())
247 > bprint(b'now try to read something: %r' % sys.stdin.read())
246 > EOF
248 > EOF
247
249
248 >>> from hgclient import check, readchannel, runcommand, stringio
250 >>> from hgclient import check, readchannel, runcommand, stringio
249 >>> @check
251 >>> @check
250 ... def hookoutput(server):
252 ... def hookoutput(server):
251 ... readchannel(server)
253 ... readchannel(server)
252 ... runcommand(server, [b'--config',
254 ... runcommand(server, [b'--config',
253 ... b'hooks.pre-identify=python:hook.hook',
255 ... b'hooks.pre-identify=python:hook.hook',
254 ... b'id'],
256 ... b'id'],
255 ... input=stringio(b'some input'))
257 ... input=stringio(b'some input'))
256 *** runcommand --config hooks.pre-identify=python:hook.hook id
258 *** runcommand --config hooks.pre-identify=python:hook.hook id
257 eff892de26ec tip
259 eff892de26ec tip
258 hook talking
260 hook talking
259 now try to read something: ''
261 now try to read something: ''
260
262
261 Clean hook cached version
263 Clean hook cached version
262 $ rm hook.py*
264 $ rm hook.py*
263 $ rm -Rf __pycache__
265 $ rm -Rf __pycache__
264
266
265 $ echo a >> a
267 $ echo a >> a
266 >>> import os
268 >>> import os
267 >>> from hgclient import check, readchannel, runcommand
269 >>> from hgclient import check, readchannel, runcommand
268 >>> @check
270 >>> @check
269 ... def outsidechanges(server):
271 ... def outsidechanges(server):
270 ... readchannel(server)
272 ... readchannel(server)
271 ... runcommand(server, [b'status'])
273 ... runcommand(server, [b'status'])
272 ... os.system('hg ci -Am2')
274 ... os.system('hg ci -Am2')
273 ... runcommand(server, [b'tip'])
275 ... runcommand(server, [b'tip'])
274 ... runcommand(server, [b'status'])
276 ... runcommand(server, [b'status'])
275 *** runcommand status
277 *** runcommand status
276 M a
278 M a
277 *** runcommand tip
279 *** runcommand tip
278 changeset: 1:d3a0a68be6de
280 changeset: 1:d3a0a68be6de
279 tag: tip
281 tag: tip
280 user: test
282 user: test
281 date: Thu Jan 01 00:00:00 1970 +0000
283 date: Thu Jan 01 00:00:00 1970 +0000
282 summary: 2
284 summary: 2
283
285
284 *** runcommand status
286 *** runcommand status
285
287
286 >>> import os
288 >>> import os
287 >>> from hgclient import bprint, check, readchannel, runcommand
289 >>> from hgclient import bprint, check, readchannel, runcommand
288 >>> @check
290 >>> @check
289 ... def bookmarks(server):
291 ... def bookmarks(server):
290 ... readchannel(server)
292 ... readchannel(server)
291 ... runcommand(server, [b'bookmarks'])
293 ... runcommand(server, [b'bookmarks'])
292 ...
294 ...
293 ... # changes .hg/bookmarks
295 ... # changes .hg/bookmarks
294 ... os.system('hg bookmark -i bm1')
296 ... os.system('hg bookmark -i bm1')
295 ... os.system('hg bookmark -i bm2')
297 ... os.system('hg bookmark -i bm2')
296 ... runcommand(server, [b'bookmarks'])
298 ... runcommand(server, [b'bookmarks'])
297 ...
299 ...
298 ... # changes .hg/bookmarks.current
300 ... # changes .hg/bookmarks.current
299 ... os.system('hg upd bm1 -q')
301 ... os.system('hg upd bm1 -q')
300 ... runcommand(server, [b'bookmarks'])
302 ... runcommand(server, [b'bookmarks'])
301 ...
303 ...
302 ... runcommand(server, [b'bookmarks', b'bm3'])
304 ... runcommand(server, [b'bookmarks', b'bm3'])
303 ... f = open('a', 'ab')
305 ... f = open('a', 'ab')
304 ... f.write(b'a\n') and None
306 ... f.write(b'a\n') and None
305 ... f.close()
307 ... f.close()
306 ... runcommand(server, [b'commit', b'-Amm'])
308 ... runcommand(server, [b'commit', b'-Amm'])
307 ... runcommand(server, [b'bookmarks'])
309 ... runcommand(server, [b'bookmarks'])
308 ... bprint(b'')
310 ... bprint(b'')
309 *** runcommand bookmarks
311 *** runcommand bookmarks
310 no bookmarks set
312 no bookmarks set
311 *** runcommand bookmarks
313 *** runcommand bookmarks
312 bm1 1:d3a0a68be6de
314 bm1 1:d3a0a68be6de
313 bm2 1:d3a0a68be6de
315 bm2 1:d3a0a68be6de
314 *** runcommand bookmarks
316 *** runcommand bookmarks
315 * bm1 1:d3a0a68be6de
317 * bm1 1:d3a0a68be6de
316 bm2 1:d3a0a68be6de
318 bm2 1:d3a0a68be6de
317 *** runcommand bookmarks bm3
319 *** runcommand bookmarks bm3
318 *** runcommand commit -Amm
320 *** runcommand commit -Amm
319 *** runcommand bookmarks
321 *** runcommand bookmarks
320 bm1 1:d3a0a68be6de
322 bm1 1:d3a0a68be6de
321 bm2 1:d3a0a68be6de
323 bm2 1:d3a0a68be6de
322 * bm3 2:aef17e88f5f0
324 * bm3 2:aef17e88f5f0
323
325
324
326
325 >>> import os
327 >>> import os
326 >>> from hgclient import check, readchannel, runcommand
328 >>> from hgclient import check, readchannel, runcommand
327 >>> @check
329 >>> @check
328 ... def tagscache(server):
330 ... def tagscache(server):
329 ... readchannel(server)
331 ... readchannel(server)
330 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
332 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
331 ... os.system('hg tag -r 0 foo')
333 ... os.system('hg tag -r 0 foo')
332 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
334 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
333 *** runcommand id -t -r 0
335 *** runcommand id -t -r 0
334
336
335 *** runcommand id -t -r 0
337 *** runcommand id -t -r 0
336 foo
338 foo
337
339
338 >>> import os
340 >>> import os
339 >>> from hgclient import check, readchannel, runcommand
341 >>> from hgclient import check, readchannel, runcommand
340 >>> @check
342 >>> @check
341 ... def setphase(server):
343 ... def setphase(server):
342 ... readchannel(server)
344 ... readchannel(server)
343 ... runcommand(server, [b'phase', b'-r', b'.'])
345 ... runcommand(server, [b'phase', b'-r', b'.'])
344 ... os.system('hg phase -r . -p')
346 ... os.system('hg phase -r . -p')
345 ... runcommand(server, [b'phase', b'-r', b'.'])
347 ... runcommand(server, [b'phase', b'-r', b'.'])
346 *** runcommand phase -r .
348 *** runcommand phase -r .
347 3: draft
349 3: draft
348 *** runcommand phase -r .
350 *** runcommand phase -r .
349 3: public
351 3: public
350
352
351 $ echo a >> a
353 $ echo a >> a
352 >>> from hgclient import bprint, check, readchannel, runcommand
354 >>> from hgclient import bprint, check, readchannel, runcommand
353 >>> @check
355 >>> @check
354 ... def rollback(server):
356 ... def rollback(server):
355 ... readchannel(server)
357 ... readchannel(server)
356 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
358 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
357 ... runcommand(server, [b'commit', b'-Am.'])
359 ... runcommand(server, [b'commit', b'-Am.'])
358 ... runcommand(server, [b'rollback'])
360 ... runcommand(server, [b'rollback'])
359 ... runcommand(server, [b'phase', b'-r', b'.'])
361 ... runcommand(server, [b'phase', b'-r', b'.'])
360 ... bprint(b'')
362 ... bprint(b'')
361 *** runcommand phase -r . -p
363 *** runcommand phase -r . -p
362 no phases changed
364 no phases changed
363 *** runcommand commit -Am.
365 *** runcommand commit -Am.
364 *** runcommand rollback
366 *** runcommand rollback
365 repository tip rolled back to revision 3 (undo commit)
367 repository tip rolled back to revision 3 (undo commit)
366 working directory now based on revision 3
368 working directory now based on revision 3
367 *** runcommand phase -r .
369 *** runcommand phase -r .
368 3: public
370 3: public
369
371
370
372
371 >>> import os
373 >>> import os
372 >>> from hgclient import check, readchannel, runcommand
374 >>> from hgclient import check, readchannel, runcommand
373 >>> @check
375 >>> @check
374 ... def branch(server):
376 ... def branch(server):
375 ... readchannel(server)
377 ... readchannel(server)
376 ... runcommand(server, [b'branch'])
378 ... runcommand(server, [b'branch'])
377 ... os.system('hg branch foo')
379 ... os.system('hg branch foo')
378 ... runcommand(server, [b'branch'])
380 ... runcommand(server, [b'branch'])
379 ... os.system('hg branch default')
381 ... os.system('hg branch default')
380 *** runcommand branch
382 *** runcommand branch
381 default
383 default
382 marked working directory as branch foo
384 marked working directory as branch foo
383 (branches are permanent and global, did you want a bookmark?)
385 (branches are permanent and global, did you want a bookmark?)
384 *** runcommand branch
386 *** runcommand branch
385 foo
387 foo
386 marked working directory as branch default
388 marked working directory as branch default
387 (branches are permanent and global, did you want a bookmark?)
389 (branches are permanent and global, did you want a bookmark?)
388
390
389 $ touch .hgignore
391 $ touch .hgignore
390 >>> import os
392 >>> import os
391 >>> from hgclient import bprint, check, readchannel, runcommand
393 >>> from hgclient import bprint, check, readchannel, runcommand
392 >>> @check
394 >>> @check
393 ... def hgignore(server):
395 ... def hgignore(server):
394 ... readchannel(server)
396 ... readchannel(server)
395 ... runcommand(server, [b'commit', b'-Am.'])
397 ... runcommand(server, [b'commit', b'-Am.'])
396 ... f = open('ignored-file', 'ab')
398 ... f = open('ignored-file', 'ab')
397 ... f.write(b'') and None
399 ... f.write(b'') and None
398 ... f.close()
400 ... f.close()
399 ... f = open('.hgignore', 'ab')
401 ... f = open('.hgignore', 'ab')
400 ... f.write(b'ignored-file')
402 ... f.write(b'ignored-file')
401 ... f.close()
403 ... f.close()
402 ... runcommand(server, [b'status', b'-i', b'-u'])
404 ... runcommand(server, [b'status', b'-i', b'-u'])
403 ... bprint(b'')
405 ... bprint(b'')
404 *** runcommand commit -Am.
406 *** runcommand commit -Am.
405 adding .hgignore
407 adding .hgignore
406 *** runcommand status -i -u
408 *** runcommand status -i -u
407 I ignored-file
409 I ignored-file
408
410
409
411
410 cache of non-public revisions should be invalidated on repository change
412 cache of non-public revisions should be invalidated on repository change
411 (issue4855):
413 (issue4855):
412
414
413 >>> import os
415 >>> import os
414 >>> from hgclient import bprint, check, readchannel, runcommand
416 >>> from hgclient import bprint, check, readchannel, runcommand
415 >>> @check
417 >>> @check
416 ... def phasesetscacheaftercommit(server):
418 ... def phasesetscacheaftercommit(server):
417 ... readchannel(server)
419 ... readchannel(server)
418 ... # load _phasecache._phaserevs and _phasesets
420 ... # load _phasecache._phaserevs and _phasesets
419 ... runcommand(server, [b'log', b'-qr', b'draft()'])
421 ... runcommand(server, [b'log', b'-qr', b'draft()'])
420 ... # create draft commits by another process
422 ... # create draft commits by another process
421 ... for i in range(5, 7):
423 ... for i in range(5, 7):
422 ... f = open('a', 'ab')
424 ... f = open('a', 'ab')
423 ... f.seek(0, os.SEEK_END)
425 ... f.seek(0, os.SEEK_END)
424 ... f.write(b'a\n') and None
426 ... f.write(b'a\n') and None
425 ... f.close()
427 ... f.close()
426 ... os.system('hg commit -Aqm%d' % i)
428 ... os.system('hg commit -Aqm%d' % i)
427 ... # new commits should be listed as draft revisions
429 ... # new commits should be listed as draft revisions
428 ... runcommand(server, [b'log', b'-qr', b'draft()'])
430 ... runcommand(server, [b'log', b'-qr', b'draft()'])
429 ... bprint(b'')
431 ... bprint(b'')
430 *** runcommand log -qr draft()
432 *** runcommand log -qr draft()
431 4:7966c8e3734d
433 4:7966c8e3734d
432 *** runcommand log -qr draft()
434 *** runcommand log -qr draft()
433 4:7966c8e3734d
435 4:7966c8e3734d
434 5:41f6602d1c4f
436 5:41f6602d1c4f
435 6:10501e202c35
437 6:10501e202c35
436
438
437
439
438 >>> import os
440 >>> import os
439 >>> from hgclient import bprint, check, readchannel, runcommand
441 >>> from hgclient import bprint, check, readchannel, runcommand
440 >>> @check
442 >>> @check
441 ... def phasesetscacheafterstrip(server):
443 ... def phasesetscacheafterstrip(server):
442 ... readchannel(server)
444 ... readchannel(server)
443 ... # load _phasecache._phaserevs and _phasesets
445 ... # load _phasecache._phaserevs and _phasesets
444 ... runcommand(server, [b'log', b'-qr', b'draft()'])
446 ... runcommand(server, [b'log', b'-qr', b'draft()'])
445 ... # strip cached revisions by another process
447 ... # strip cached revisions by another process
446 ... os.system('hg --config extensions.strip= strip -q 5')
448 ... os.system('hg --config extensions.strip= strip -q 5')
447 ... # shouldn't abort by "unknown revision '6'"
449 ... # shouldn't abort by "unknown revision '6'"
448 ... runcommand(server, [b'log', b'-qr', b'draft()'])
450 ... runcommand(server, [b'log', b'-qr', b'draft()'])
449 ... bprint(b'')
451 ... bprint(b'')
450 *** runcommand log -qr draft()
452 *** runcommand log -qr draft()
451 4:7966c8e3734d
453 4:7966c8e3734d
452 5:41f6602d1c4f
454 5:41f6602d1c4f
453 6:10501e202c35
455 6:10501e202c35
454 *** runcommand log -qr draft()
456 *** runcommand log -qr draft()
455 4:7966c8e3734d
457 4:7966c8e3734d
456
458
457
459
458 cache of phase roots should be invalidated on strip (issue3827):
460 cache of phase roots should be invalidated on strip (issue3827):
459
461
460 >>> import os
462 >>> import os
461 >>> from hgclient import check, readchannel, runcommand, sep
463 >>> from hgclient import check, readchannel, runcommand, sep
462 >>> @check
464 >>> @check
463 ... def phasecacheafterstrip(server):
465 ... def phasecacheafterstrip(server):
464 ... readchannel(server)
466 ... readchannel(server)
465 ...
467 ...
466 ... # create new head, 5:731265503d86
468 ... # create new head, 5:731265503d86
467 ... runcommand(server, [b'update', b'-C', b'0'])
469 ... runcommand(server, [b'update', b'-C', b'0'])
468 ... f = open('a', 'ab')
470 ... f = open('a', 'ab')
469 ... f.write(b'a\n') and None
471 ... f.write(b'a\n') and None
470 ... f.close()
472 ... f.close()
471 ... runcommand(server, [b'commit', b'-Am.', b'a'])
473 ... runcommand(server, [b'commit', b'-Am.', b'a'])
472 ... runcommand(server, [b'log', b'-Gq'])
474 ... runcommand(server, [b'log', b'-Gq'])
473 ...
475 ...
474 ... # make it public; draft marker moves to 4:7966c8e3734d
476 ... # make it public; draft marker moves to 4:7966c8e3734d
475 ... runcommand(server, [b'phase', b'-p', b'.'])
477 ... runcommand(server, [b'phase', b'-p', b'.'])
476 ... # load _phasecache.phaseroots
478 ... # load _phasecache.phaseroots
477 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
479 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
478 ...
480 ...
479 ... # strip 1::4 outside server
481 ... # strip 1::4 outside server
480 ... os.system('hg -q --config extensions.mq= strip 1')
482 ... os.system('hg -q --config extensions.mq= strip 1')
481 ...
483 ...
482 ... # shouldn't raise "7966c8e3734d: no node!"
484 ... # shouldn't raise "7966c8e3734d: no node!"
483 ... runcommand(server, [b'branches'])
485 ... runcommand(server, [b'branches'])
484 *** runcommand update -C 0
486 *** runcommand update -C 0
485 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
487 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
486 (leaving bookmark bm3)
488 (leaving bookmark bm3)
487 *** runcommand commit -Am. a
489 *** runcommand commit -Am. a
488 created new head
490 created new head
489 *** runcommand log -Gq
491 *** runcommand log -Gq
490 @ 5:731265503d86
492 @ 5:731265503d86
491 |
493 |
492 | o 4:7966c8e3734d
494 | o 4:7966c8e3734d
493 | |
495 | |
494 | o 3:b9b85890c400
496 | o 3:b9b85890c400
495 | |
497 | |
496 | o 2:aef17e88f5f0
498 | o 2:aef17e88f5f0
497 | |
499 | |
498 | o 1:d3a0a68be6de
500 | o 1:d3a0a68be6de
499 |/
501 |/
500 o 0:eff892de26ec
502 o 0:eff892de26ec
501
503
502 *** runcommand phase -p .
504 *** runcommand phase -p .
503 *** runcommand phase .
505 *** runcommand phase .
504 5: public
506 5: public
505 *** runcommand branches
507 *** runcommand branches
506 default 1:731265503d86
508 default 1:731265503d86
507
509
508 in-memory cache must be reloaded if transaction is aborted. otherwise
510 in-memory cache must be reloaded if transaction is aborted. otherwise
509 changelog and manifest would have invalid node:
511 changelog and manifest would have invalid node:
510
512
511 $ echo a >> a
513 $ echo a >> a
512 >>> from hgclient import check, readchannel, runcommand
514 >>> from hgclient import check, readchannel, runcommand
513 >>> @check
515 >>> @check
514 ... def txabort(server):
516 ... def txabort(server):
515 ... readchannel(server)
517 ... readchannel(server)
516 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
518 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
517 ... b'-mfoo'])
519 ... b'-mfoo'])
518 ... runcommand(server, [b'verify'])
520 ... runcommand(server, [b'verify'])
519 *** runcommand commit --config hooks.pretxncommit=false -mfoo
521 *** runcommand commit --config hooks.pretxncommit=false -mfoo
520 transaction abort!
522 transaction abort!
521 rollback completed
523 rollback completed
522 abort: pretxncommit hook exited with status 1
524 abort: pretxncommit hook exited with status 1
523 [255]
525 [255]
524 *** runcommand verify
526 *** runcommand verify
525 checking changesets
527 checking changesets
526 checking manifests
528 checking manifests
527 crosschecking files in changesets and manifests
529 crosschecking files in changesets and manifests
528 checking files
530 checking files
529 checked 2 changesets with 2 changes to 1 files
531 checked 2 changesets with 2 changes to 1 files
530 $ hg revert --no-backup -aq
532 $ hg revert --no-backup -aq
531
533
532 $ cat >> .hg/hgrc << EOF
534 $ cat >> .hg/hgrc << EOF
533 > [experimental]
535 > [experimental]
534 > evolution.createmarkers=True
536 > evolution.createmarkers=True
535 > EOF
537 > EOF
536
538
537 >>> import os
539 >>> import os
538 >>> from hgclient import check, readchannel, runcommand
540 >>> from hgclient import check, readchannel, runcommand
539 >>> @check
541 >>> @check
540 ... def obsolete(server):
542 ... def obsolete(server):
541 ... readchannel(server)
543 ... readchannel(server)
542 ...
544 ...
543 ... runcommand(server, [b'up', b'null'])
545 ... runcommand(server, [b'up', b'null'])
544 ... runcommand(server, [b'phase', b'-df', b'tip'])
546 ... runcommand(server, [b'phase', b'-df', b'tip'])
545 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
547 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
546 ... if os.name == 'nt':
548 ... if os.name == 'nt':
547 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
549 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
548 ... os.system(cmd)
550 ... os.system(cmd)
549 ... runcommand(server, [b'log', b'--hidden'])
551 ... runcommand(server, [b'log', b'--hidden'])
550 ... runcommand(server, [b'log'])
552 ... runcommand(server, [b'log'])
551 *** runcommand up null
553 *** runcommand up null
552 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
554 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
553 *** runcommand phase -df tip
555 *** runcommand phase -df tip
554 1 new obsolescence markers
556 1 new obsolescence markers
555 obsoleted 1 changesets
557 obsoleted 1 changesets
556 *** runcommand log --hidden
558 *** runcommand log --hidden
557 changeset: 1:731265503d86
559 changeset: 1:731265503d86
558 tag: tip
560 tag: tip
559 user: test
561 user: test
560 date: Thu Jan 01 00:00:00 1970 +0000
562 date: Thu Jan 01 00:00:00 1970 +0000
561 obsolete: pruned
563 obsolete: pruned
562 summary: .
564 summary: .
563
565
564 changeset: 0:eff892de26ec
566 changeset: 0:eff892de26ec
565 bookmark: bm1
567 bookmark: bm1
566 bookmark: bm2
568 bookmark: bm2
567 bookmark: bm3
569 bookmark: bm3
568 user: test
570 user: test
569 date: Thu Jan 01 00:00:00 1970 +0000
571 date: Thu Jan 01 00:00:00 1970 +0000
570 summary: 1
572 summary: 1
571
573
572 *** runcommand log
574 *** runcommand log
573 changeset: 0:eff892de26ec
575 changeset: 0:eff892de26ec
574 bookmark: bm1
576 bookmark: bm1
575 bookmark: bm2
577 bookmark: bm2
576 bookmark: bm3
578 bookmark: bm3
577 tag: tip
579 tag: tip
578 user: test
580 user: test
579 date: Thu Jan 01 00:00:00 1970 +0000
581 date: Thu Jan 01 00:00:00 1970 +0000
580 summary: 1
582 summary: 1
581
583
582
584
583 $ cat <<EOF >> .hg/hgrc
585 $ cat <<EOF >> .hg/hgrc
584 > [extensions]
586 > [extensions]
585 > mq =
587 > mq =
586 > EOF
588 > EOF
587
589
588 >>> import os
590 >>> import os
589 >>> from hgclient import check, readchannel, runcommand
591 >>> from hgclient import check, readchannel, runcommand
590 >>> @check
592 >>> @check
591 ... def mqoutsidechanges(server):
593 ... def mqoutsidechanges(server):
592 ... readchannel(server)
594 ... readchannel(server)
593 ...
595 ...
594 ... # load repo.mq
596 ... # load repo.mq
595 ... runcommand(server, [b'qapplied'])
597 ... runcommand(server, [b'qapplied'])
596 ... os.system('hg qnew 0.diff')
598 ... os.system('hg qnew 0.diff')
597 ... # repo.mq should be invalidated
599 ... # repo.mq should be invalidated
598 ... runcommand(server, [b'qapplied'])
600 ... runcommand(server, [b'qapplied'])
599 ...
601 ...
600 ... runcommand(server, [b'qpop', b'--all'])
602 ... runcommand(server, [b'qpop', b'--all'])
601 ... os.system('hg qqueue --create foo')
603 ... os.system('hg qqueue --create foo')
602 ... # repo.mq should be recreated to point to new queue
604 ... # repo.mq should be recreated to point to new queue
603 ... runcommand(server, [b'qqueue', b'--active'])
605 ... runcommand(server, [b'qqueue', b'--active'])
604 *** runcommand qapplied
606 *** runcommand qapplied
605 *** runcommand qapplied
607 *** runcommand qapplied
606 0.diff
608 0.diff
607 *** runcommand qpop --all
609 *** runcommand qpop --all
608 popping 0.diff
610 popping 0.diff
609 patch queue now empty
611 patch queue now empty
610 *** runcommand qqueue --active
612 *** runcommand qqueue --active
611 foo
613 foo
612
614
613 $ cat <<'EOF' > ../dbgui.py
615 $ cat <<'EOF' > ../dbgui.py
614 > import os
616 > import os
615 > import sys
617 > import sys
616 > from mercurial import commands, registrar
618 > from mercurial import commands, registrar
617 > cmdtable = {}
619 > cmdtable = {}
618 > command = registrar.command(cmdtable)
620 > command = registrar.command(cmdtable)
619 > @command(b"debuggetpass", norepo=True)
621 > @command(b"debuggetpass", norepo=True)
620 > def debuggetpass(ui):
622 > def debuggetpass(ui):
621 > ui.write(b"%s\n" % ui.getpass())
623 > ui.write(b"%s\n" % ui.getpass())
622 > @command(b"debugprompt", norepo=True)
624 > @command(b"debugprompt", norepo=True)
623 > def debugprompt(ui):
625 > def debugprompt(ui):
624 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
626 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
625 > @command(b"debugpromptchoice", norepo=True)
627 > @command(b"debugpromptchoice", norepo=True)
626 > def debugpromptchoice(ui):
628 > def debugpromptchoice(ui):
627 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
629 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
628 > ui.write(b"%d\n" % ui.promptchoice(msg))
630 > ui.write(b"%d\n" % ui.promptchoice(msg))
629 > @command(b"debugreadstdin", norepo=True)
631 > @command(b"debugreadstdin", norepo=True)
630 > def debugreadstdin(ui):
632 > def debugreadstdin(ui):
631 > ui.write(b"read: %r\n" % sys.stdin.read(1))
633 > ui.write(b"read: %r\n" % sys.stdin.read(1))
632 > @command(b"debugwritestdout", norepo=True)
634 > @command(b"debugwritestdout", norepo=True)
633 > def debugwritestdout(ui):
635 > def debugwritestdout(ui):
634 > os.write(1, b"low-level stdout fd and\n")
636 > os.write(1, b"low-level stdout fd and\n")
635 > sys.stdout.write("stdout should be redirected to stderr\n")
637 > sys.stdout.write("stdout should be redirected to stderr\n")
636 > sys.stdout.flush()
638 > sys.stdout.flush()
637 > EOF
639 > EOF
638 $ cat <<EOF >> .hg/hgrc
640 $ cat <<EOF >> .hg/hgrc
639 > [extensions]
641 > [extensions]
640 > dbgui = ../dbgui.py
642 > dbgui = ../dbgui.py
641 > EOF
643 > EOF
642
644
643 >>> from hgclient import check, readchannel, runcommand, stringio
645 >>> from hgclient import check, readchannel, runcommand, stringio
644 >>> @check
646 >>> @check
645 ... def getpass(server):
647 ... def getpass(server):
646 ... readchannel(server)
648 ... readchannel(server)
647 ... runcommand(server, [b'debuggetpass', b'--config',
649 ... runcommand(server, [b'debuggetpass', b'--config',
648 ... b'ui.interactive=True'],
650 ... b'ui.interactive=True'],
649 ... input=stringio(b'1234\n'))
651 ... input=stringio(b'1234\n'))
650 ... runcommand(server, [b'debuggetpass', b'--config',
652 ... runcommand(server, [b'debuggetpass', b'--config',
651 ... b'ui.interactive=True'],
653 ... b'ui.interactive=True'],
652 ... input=stringio(b'\n'))
654 ... input=stringio(b'\n'))
653 ... runcommand(server, [b'debuggetpass', b'--config',
655 ... runcommand(server, [b'debuggetpass', b'--config',
654 ... b'ui.interactive=True'],
656 ... b'ui.interactive=True'],
655 ... input=stringio(b''))
657 ... input=stringio(b''))
656 ... runcommand(server, [b'debugprompt', b'--config',
658 ... runcommand(server, [b'debugprompt', b'--config',
657 ... b'ui.interactive=True'],
659 ... b'ui.interactive=True'],
658 ... input=stringio(b'5678\n'))
660 ... input=stringio(b'5678\n'))
659 ... runcommand(server, [b'debugprompt', b'--config',
661 ... runcommand(server, [b'debugprompt', b'--config',
660 ... b'ui.interactive=True'],
662 ... b'ui.interactive=True'],
661 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
663 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
662 ... runcommand(server, [b'debugreadstdin'])
664 ... runcommand(server, [b'debugreadstdin'])
663 ... runcommand(server, [b'debugwritestdout'])
665 ... runcommand(server, [b'debugwritestdout'])
664 *** runcommand debuggetpass --config ui.interactive=True
666 *** runcommand debuggetpass --config ui.interactive=True
665 password: 1234
667 password: 1234
666 *** runcommand debuggetpass --config ui.interactive=True
668 *** runcommand debuggetpass --config ui.interactive=True
667 password:
669 password:
668 *** runcommand debuggetpass --config ui.interactive=True
670 *** runcommand debuggetpass --config ui.interactive=True
669 password: abort: response expected
671 password: abort: response expected
670 [255]
672 [255]
671 *** runcommand debugprompt --config ui.interactive=True
673 *** runcommand debugprompt --config ui.interactive=True
672 prompt: 5678
674 prompt: 5678
673 *** runcommand debugprompt --config ui.interactive=True
675 *** runcommand debugprompt --config ui.interactive=True
674 prompt: y
676 prompt: y
675 *** runcommand debugreadstdin
677 *** runcommand debugreadstdin
676 read: ''
678 read: ''
677 *** runcommand debugwritestdout
679 *** runcommand debugwritestdout
678 low-level stdout fd and
680 low-level stdout fd and
679 stdout should be redirected to stderr
681 stdout should be redirected to stderr
680
682
681
683
682 run commandserver in commandserver, which is silly but should work:
684 run commandserver in commandserver, which is silly but should work:
683
685
684 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
686 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
685 >>> @check
687 >>> @check
686 ... def nested(server):
688 ... def nested(server):
687 ... bprint(b'%c, %r' % readchannel(server))
689 ... bprint(b'%c, %r' % readchannel(server))
688 ... class nestedserver(object):
690 ... class nestedserver(object):
689 ... stdin = stringio(b'getencoding\n')
691 ... stdin = stringio(b'getencoding\n')
690 ... stdout = stringio()
692 ... stdout = stringio()
691 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
693 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
692 ... output=nestedserver.stdout, input=nestedserver.stdin)
694 ... output=nestedserver.stdout, input=nestedserver.stdin)
693 ... nestedserver.stdout.seek(0)
695 ... nestedserver.stdout.seek(0)
694 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
696 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
695 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
697 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
696 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
698 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
697 *** runcommand serve --cmdserver pipe
699 *** runcommand serve --cmdserver pipe
698 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
700 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
699 r, '*' (glob)
701 r, '*' (glob)
700
702
701
703
702 start without repository:
704 start without repository:
703
705
704 $ cd ..
706 $ cd ..
705
707
706 >>> from hgclient import bprint, check, readchannel, runcommand
708 >>> from hgclient import bprint, check, readchannel, runcommand
707 >>> @check
709 >>> @check
708 ... def hellomessage(server):
710 ... def hellomessage(server):
709 ... ch, data = readchannel(server)
711 ... ch, data = readchannel(server)
710 ... bprint(b'%c, %r' % (ch, data))
712 ... bprint(b'%c, %r' % (ch, data))
711 ... # run an arbitrary command to make sure the next thing the server
713 ... # run an arbitrary command to make sure the next thing the server
712 ... # sends isn't part of the hello message
714 ... # sends isn't part of the hello message
713 ... runcommand(server, [b'id'])
715 ... runcommand(server, [b'id'])
714 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
716 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
715 *** runcommand id
717 *** runcommand id
716 abort: there is no Mercurial repository here (.hg not found)
718 abort: there is no Mercurial repository here (.hg not found)
717 [10]
719 [10]
718
720
719 >>> from hgclient import check, readchannel, runcommand
721 >>> from hgclient import check, readchannel, runcommand
720 >>> @check
722 >>> @check
721 ... def startwithoutrepo(server):
723 ... def startwithoutrepo(server):
722 ... readchannel(server)
724 ... readchannel(server)
723 ... runcommand(server, [b'init', b'repo2'])
725 ... runcommand(server, [b'init', b'repo2'])
724 ... runcommand(server, [b'id', b'-R', b'repo2'])
726 ... runcommand(server, [b'id', b'-R', b'repo2'])
725 *** runcommand init repo2
727 *** runcommand init repo2
726 *** runcommand id -R repo2
728 *** runcommand id -R repo2
727 000000000000 tip
729 000000000000 tip
728
730
729
731
730 don't fall back to cwd if invalid -R path is specified (issue4805):
732 don't fall back to cwd if invalid -R path is specified (issue4805):
731
733
732 $ cd repo
734 $ cd repo
733 $ hg serve --cmdserver pipe -R ../nonexistent
735 $ hg serve --cmdserver pipe -R ../nonexistent
734 abort: repository ../nonexistent not found
736 abort: repository ../nonexistent not found
735 [255]
737 [255]
736 $ cd ..
738 $ cd ..
737
739
738
740
739 #if no-windows
741 #if no-windows
740
742
741 option to not shutdown on SIGINT:
743 option to not shutdown on SIGINT:
742
744
743 $ cat <<'EOF' > dbgint.py
745 $ cat <<'EOF' > dbgint.py
744 > import os
746 > import os
745 > import signal
747 > import signal
746 > import time
748 > import time
747 > from mercurial import commands, registrar
749 > from mercurial import commands, registrar
748 > cmdtable = {}
750 > cmdtable = {}
749 > command = registrar.command(cmdtable)
751 > command = registrar.command(cmdtable)
750 > @command(b"debugsleep", norepo=True)
752 > @command(b"debugsleep", norepo=True)
751 > def debugsleep(ui):
753 > def debugsleep(ui):
752 > time.sleep(1)
754 > time.sleep(1)
753 > @command(b"debugsuicide", norepo=True)
755 > @command(b"debugsuicide", norepo=True)
754 > def debugsuicide(ui):
756 > def debugsuicide(ui):
755 > os.kill(os.getpid(), signal.SIGINT)
757 > os.kill(os.getpid(), signal.SIGINT)
756 > time.sleep(1)
758 > time.sleep(1)
757 > EOF
759 > EOF
758
760
759 >>> import signal
761 >>> import signal
760 >>> import time
762 >>> import time
761 >>> from hgclient import checkwith, readchannel, runcommand
763 >>> from hgclient import checkwith, readchannel, runcommand
762 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
764 >>> @checkwith(extraargs=[b'--config', b'cmdserver.shutdown-on-interrupt=False',
763 ... b'--config', b'extensions.dbgint=dbgint.py'])
765 ... b'--config', b'extensions.dbgint=dbgint.py'])
764 ... def nointr(server):
766 ... def nointr(server):
765 ... readchannel(server)
767 ... readchannel(server)
766 ... server.send_signal(signal.SIGINT) # server won't be terminated
768 ... server.send_signal(signal.SIGINT) # server won't be terminated
767 ... time.sleep(1)
769 ... time.sleep(1)
768 ... runcommand(server, [b'debugsleep'])
770 ... runcommand(server, [b'debugsleep'])
769 ... server.send_signal(signal.SIGINT) # server won't be terminated
771 ... server.send_signal(signal.SIGINT) # server won't be terminated
770 ... runcommand(server, [b'debugsleep'])
772 ... runcommand(server, [b'debugsleep'])
771 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
773 ... runcommand(server, [b'debugsuicide']) # command can be interrupted
772 ... server.send_signal(signal.SIGTERM) # server will be terminated
774 ... server.send_signal(signal.SIGTERM) # server will be terminated
773 ... time.sleep(1)
775 ... time.sleep(1)
774 *** runcommand debugsleep
776 *** runcommand debugsleep
775 *** runcommand debugsleep
777 *** runcommand debugsleep
776 *** runcommand debugsuicide
778 *** runcommand debugsuicide
777 interrupted!
779 interrupted!
778 killed!
780 killed!
779 [255]
781 [255]
780
782
781 #endif
783 #endif
782
784
783
785
784 structured message channel:
786 structured message channel:
785
787
786 $ cat <<'EOF' >> repo2/.hg/hgrc
788 $ cat <<'EOF' >> repo2/.hg/hgrc
787 > [ui]
789 > [ui]
788 > # server --config should precede repository option
790 > # server --config should precede repository option
789 > message-output = stdio
791 > message-output = stdio
790 > EOF
792 > EOF
791
793
792 >>> from hgclient import bprint, checkwith, readchannel, runcommand
794 >>> from hgclient import bprint, checkwith, readchannel, runcommand
793 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
795 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
794 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
796 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
795 ... def verify(server):
797 ... def verify(server):
796 ... _ch, data = readchannel(server)
798 ... _ch, data = readchannel(server)
797 ... bprint(data)
799 ... bprint(data)
798 ... runcommand(server, [b'-R', b'repo2', b'verify'])
800 ... runcommand(server, [b'-R', b'repo2', b'verify'])
799 capabilities: getencoding runcommand
801 capabilities: getencoding runcommand
800 encoding: ascii
802 encoding: ascii
801 message-encoding: cbor
803 message-encoding: cbor
802 pid: * (glob)
804 pid: * (glob)
803 pgid: * (glob) (no-windows !)
805 pgid: * (glob) (no-windows !)
804 *** runcommand -R repo2 verify
806 *** runcommand -R repo2 verify
805 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
807 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
806 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
808 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
807 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
809 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
808 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
810 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
809 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
811 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
810 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
812 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
811 message: '\xa2DdataOchecking files\nDtypeFstatus'
813 message: '\xa2DdataOchecking files\nDtypeFstatus'
812 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
814 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
813 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
815 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
814
816
815 >>> from hgclient import checkwith, readchannel, runcommand, stringio
817 >>> from hgclient import checkwith, readchannel, runcommand, stringio
816 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
818 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
817 ... b'--config', b'cmdserver.message-encodings=cbor',
819 ... b'--config', b'cmdserver.message-encodings=cbor',
818 ... b'--config', b'extensions.dbgui=dbgui.py'])
820 ... b'--config', b'extensions.dbgui=dbgui.py'])
819 ... def prompt(server):
821 ... def prompt(server):
820 ... readchannel(server)
822 ... readchannel(server)
821 ... interactive = [b'--config', b'ui.interactive=True']
823 ... interactive = [b'--config', b'ui.interactive=True']
822 ... runcommand(server, [b'debuggetpass'] + interactive,
824 ... runcommand(server, [b'debuggetpass'] + interactive,
823 ... input=stringio(b'1234\n'))
825 ... input=stringio(b'1234\n'))
824 ... runcommand(server, [b'debugprompt'] + interactive,
826 ... runcommand(server, [b'debugprompt'] + interactive,
825 ... input=stringio(b'5678\n'))
827 ... input=stringio(b'5678\n'))
826 ... runcommand(server, [b'debugpromptchoice'] + interactive,
828 ... runcommand(server, [b'debugpromptchoice'] + interactive,
827 ... input=stringio(b'n\n'))
829 ... input=stringio(b'n\n'))
828 *** runcommand debuggetpass --config ui.interactive=True
830 *** runcommand debuggetpass --config ui.interactive=True
829 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
831 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
830 1234
832 1234
831 *** runcommand debugprompt --config ui.interactive=True
833 *** runcommand debugprompt --config ui.interactive=True
832 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
834 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
833 5678
835 5678
834 *** runcommand debugpromptchoice --config ui.interactive=True
836 *** runcommand debugpromptchoice --config ui.interactive=True
835 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
837 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
836 1
838 1
837
839
838 bad message encoding:
840 bad message encoding:
839
841
840 $ hg serve --cmdserver pipe --config ui.message-output=channel
842 $ hg serve --cmdserver pipe --config ui.message-output=channel
841 abort: no supported message encodings:
843 abort: no supported message encodings:
842 [255]
844 [255]
843 $ hg serve --cmdserver pipe --config ui.message-output=channel \
845 $ hg serve --cmdserver pipe --config ui.message-output=channel \
844 > --config cmdserver.message-encodings='foo bar'
846 > --config cmdserver.message-encodings='foo bar'
845 abort: no supported message encodings: foo bar
847 abort: no supported message encodings: foo bar
846 [255]
848 [255]
847
849
848 unix domain socket:
850 unix domain socket:
849
851
850 $ cd repo
852 $ cd repo
851 $ hg update -q
853 $ hg update -q
852
854
853 #if unix-socket unix-permissions
855 #if unix-socket unix-permissions
854
856
855 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
857 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
856 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
858 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
857 >>> def hellomessage(conn):
859 >>> def hellomessage(conn):
858 ... ch, data = readchannel(conn)
860 ... ch, data = readchannel(conn)
859 ... bprint(b'%c, %r' % (ch, data))
861 ... bprint(b'%c, %r' % (ch, data))
860 ... runcommand(conn, [b'id'])
862 ... runcommand(conn, [b'id'])
861 >>> check(hellomessage, server.connect)
863 >>> check(hellomessage, server.connect)
862 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
864 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
863 *** runcommand id
865 *** runcommand id
864 eff892de26ec tip bm1/bm2/bm3
866 eff892de26ec tip bm1/bm2/bm3
865 >>> def unknowncommand(conn):
867 >>> def unknowncommand(conn):
866 ... readchannel(conn)
868 ... readchannel(conn)
867 ... conn.stdin.write(b'unknowncommand\n')
869 ... conn.stdin.write(b'unknowncommand\n')
868 >>> check(unknowncommand, server.connect) # error sent to server.log
870 >>> check(unknowncommand, server.connect) # error sent to server.log
869 >>> def serverinput(conn):
871 >>> def serverinput(conn):
870 ... readchannel(conn)
872 ... readchannel(conn)
871 ... patch = b"""
873 ... patch = b"""
872 ... # HG changeset patch
874 ... # HG changeset patch
873 ... # User test
875 ... # User test
874 ... # Date 0 0
876 ... # Date 0 0
875 ... 2
877 ... 2
876 ...
878 ...
877 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
879 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
878 ... --- a/a
880 ... --- a/a
879 ... +++ b/a
881 ... +++ b/a
880 ... @@ -1,1 +1,2 @@
882 ... @@ -1,1 +1,2 @@
881 ... 1
883 ... 1
882 ... +2
884 ... +2
883 ... """
885 ... """
884 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
886 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
885 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
887 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
886 >>> check(serverinput, server.connect)
888 >>> check(serverinput, server.connect)
887 *** runcommand import -
889 *** runcommand import -
888 applying patch from stdin
890 applying patch from stdin
889 *** runcommand log -rtip -q
891 *** runcommand log -rtip -q
890 2:1ed24be7e7a0
892 2:1ed24be7e7a0
891 >>> server.shutdown()
893 >>> server.shutdown()
892
894
893 $ cat .hg/server.log
895 $ cat .hg/server.log
894 listening at .hg/server.sock
896 listening at .hg/server.sock
895 abort: unknown command unknowncommand
897 abort: unknown command unknowncommand
896 killed!
898 killed!
897 $ rm .hg/server.log
899 $ rm .hg/server.log
898
900
899 if server crashed before hello, traceback will be sent to 'e' channel as
901 if server crashed before hello, traceback will be sent to 'e' channel as
900 last ditch:
902 last ditch:
901
903
902 $ cat <<'EOF' > ../earlycrasher.py
904 $ cat <<'EOF' > ../earlycrasher.py
903 > from mercurial import commandserver, extensions
905 > from mercurial import commandserver, extensions
904 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
906 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
905 > def createcmdserver(*args, **kwargs):
907 > def createcmdserver(*args, **kwargs):
906 > raise Exception('crash')
908 > raise Exception('crash')
907 > return orig(ui, repo, conn, createcmdserver, prereposetups)
909 > return orig(ui, repo, conn, createcmdserver, prereposetups)
908 > def extsetup(ui):
910 > def extsetup(ui):
909 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
911 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
910 > EOF
912 > EOF
911 $ cat <<EOF >> .hg/hgrc
913 $ cat <<EOF >> .hg/hgrc
912 > [extensions]
914 > [extensions]
913 > earlycrasher = ../earlycrasher.py
915 > earlycrasher = ../earlycrasher.py
914 > EOF
916 > EOF
915 >>> from hgclient import bprint, check, readchannel, unixserver
917 >>> from hgclient import bprint, check, readchannel, unixserver
916 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
918 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
917 >>> def earlycrash(conn):
919 >>> def earlycrash(conn):
918 ... while True:
920 ... while True:
919 ... try:
921 ... try:
920 ... ch, data = readchannel(conn)
922 ... ch, data = readchannel(conn)
921 ... for l in data.splitlines(True):
923 ... for l in data.splitlines(True):
922 ... if not l.startswith(b' '):
924 ... if not l.startswith(b' '):
923 ... bprint(b'%c, %r' % (ch, l))
925 ... bprint(b'%c, %r' % (ch, l))
924 ... except EOFError:
926 ... except EOFError:
925 ... break
927 ... break
926 >>> check(earlycrash, server.connect)
928 >>> check(earlycrash, server.connect)
927 e, 'Traceback (most recent call last):\n'
929 e, 'Traceback (most recent call last):\n'
928 e, 'Exception: crash\n'
930 e, 'Exception: crash\n'
929 >>> server.shutdown()
931 >>> server.shutdown()
930
932
931 $ cat .hg/server.log | grep -v '^ '
933 $ cat .hg/server.log | grep -v '^ '
932 listening at .hg/server.sock
934 listening at .hg/server.sock
933 Traceback (most recent call last):
935 Traceback (most recent call last):
934 Exception: crash
936 Exception: crash
935 killed!
937 killed!
936 #endif
938 #endif
937 #if no-unix-socket
939 #if no-unix-socket
938
940
939 $ hg serve --cmdserver unix -a .hg/server.sock
941 $ hg serve --cmdserver unix -a .hg/server.sock
940 abort: unsupported platform
942 abort: unsupported platform
941 [255]
943 [255]
942
944
943 #endif
945 #endif
944
946
945 $ cd ..
947 $ cd ..
946
948
947 Test that accessing to invalid changelog cache is avoided at
949 Test that accessing to invalid changelog cache is avoided at
948 subsequent operations even if repo object is reused even after failure
950 subsequent operations even if repo object is reused even after failure
949 of transaction (see 0a7610758c42 also)
951 of transaction (see 0a7610758c42 also)
950
952
951 "hg log" after failure of transaction is needed to detect invalid
953 "hg log" after failure of transaction is needed to detect invalid
952 cache in repoview: this can't detect by "hg verify" only.
954 cache in repoview: this can't detect by "hg verify" only.
953
955
954 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
956 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
955 4) are tested, because '00changelog.i' are differently changed in each
957 4) are tested, because '00changelog.i' are differently changed in each
956 cases.
958 cases.
957
959
958 $ cat > $TESTTMP/failafterfinalize.py <<EOF
960 $ cat > $TESTTMP/failafterfinalize.py <<EOF
959 > # extension to abort transaction after finalization forcibly
961 > # extension to abort transaction after finalization forcibly
960 > from mercurial import commands, error, extensions, lock as lockmod
962 > from mercurial import commands, error, extensions, lock as lockmod
961 > from mercurial import registrar
963 > from mercurial import registrar
962 > cmdtable = {}
964 > cmdtable = {}
963 > command = registrar.command(cmdtable)
965 > command = registrar.command(cmdtable)
964 > configtable = {}
966 > configtable = {}
965 > configitem = registrar.configitem(configtable)
967 > configitem = registrar.configitem(configtable)
966 > configitem(b'failafterfinalize', b'fail',
968 > configitem(b'failafterfinalize', b'fail',
967 > default=None,
969 > default=None,
968 > )
970 > )
969 > def fail(tr):
971 > def fail(tr):
970 > raise error.Abort(b'fail after finalization')
972 > raise error.Abort(b'fail after finalization')
971 > def reposetup(ui, repo):
973 > def reposetup(ui, repo):
972 > class failrepo(repo.__class__):
974 > class failrepo(repo.__class__):
973 > def commitctx(self, ctx, error=False, origctx=None):
975 > def commitctx(self, ctx, error=False, origctx=None):
974 > if self.ui.configbool(b'failafterfinalize', b'fail'):
976 > if self.ui.configbool(b'failafterfinalize', b'fail'):
975 > # 'sorted()' by ASCII code on category names causes
977 > # 'sorted()' by ASCII code on category names causes
976 > # invoking 'fail' after finalization of changelog
978 > # invoking 'fail' after finalization of changelog
977 > # using "'cl-%i' % id(self)" as category name
979 > # using "'cl-%i' % id(self)" as category name
978 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
980 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
979 > return super(failrepo, self).commitctx(ctx, error, origctx)
981 > return super(failrepo, self).commitctx(ctx, error, origctx)
980 > repo.__class__ = failrepo
982 > repo.__class__ = failrepo
981 > EOF
983 > EOF
982
984
983 $ hg init repo3
985 $ hg init repo3
984 $ cd repo3
986 $ cd repo3
985
987
986 $ cat <<EOF >> $HGRCPATH
988 $ cat <<EOF >> $HGRCPATH
987 > [command-templates]
989 > [command-templates]
988 > log = {rev} {desc|firstline} ({files})\n
990 > log = {rev} {desc|firstline} ({files})\n
989 >
991 >
990 > [extensions]
992 > [extensions]
991 > failafterfinalize = $TESTTMP/failafterfinalize.py
993 > failafterfinalize = $TESTTMP/failafterfinalize.py
992 > EOF
994 > EOF
993
995
994 - test failure with "empty changelog"
996 - test failure with "empty changelog"
995
997
996 $ echo foo > foo
998 $ echo foo > foo
997 $ hg add foo
999 $ hg add foo
998
1000
999 (failure before finalization)
1001 (failure before finalization)
1000
1002
1001 >>> from hgclient import check, readchannel, runcommand
1003 >>> from hgclient import check, readchannel, runcommand
1002 >>> @check
1004 >>> @check
1003 ... def abort(server):
1005 ... def abort(server):
1004 ... readchannel(server)
1006 ... readchannel(server)
1005 ... runcommand(server, [b'commit',
1007 ... runcommand(server, [b'commit',
1006 ... b'--config', b'hooks.pretxncommit=false',
1008 ... b'--config', b'hooks.pretxncommit=false',
1007 ... b'-mfoo'])
1009 ... b'-mfoo'])
1008 ... runcommand(server, [b'log'])
1010 ... runcommand(server, [b'log'])
1009 ... runcommand(server, [b'verify', b'-q'])
1011 ... runcommand(server, [b'verify', b'-q'])
1010 *** runcommand commit --config hooks.pretxncommit=false -mfoo
1012 *** runcommand commit --config hooks.pretxncommit=false -mfoo
1011 transaction abort!
1013 transaction abort!
1012 rollback completed
1014 rollback completed
1013 abort: pretxncommit hook exited with status 1
1015 abort: pretxncommit hook exited with status 1
1014 [255]
1016 [255]
1015 *** runcommand log
1017 *** runcommand log
1016 *** runcommand verify -q
1018 *** runcommand verify -q
1017
1019
1018 (failure after finalization)
1020 (failure after finalization)
1019
1021
1020 >>> from hgclient import check, readchannel, runcommand
1022 >>> from hgclient import check, readchannel, runcommand
1021 >>> @check
1023 >>> @check
1022 ... def abort(server):
1024 ... def abort(server):
1023 ... readchannel(server)
1025 ... readchannel(server)
1024 ... runcommand(server, [b'commit',
1026 ... runcommand(server, [b'commit',
1025 ... b'--config', b'failafterfinalize.fail=true',
1027 ... b'--config', b'failafterfinalize.fail=true',
1026 ... b'-mfoo'])
1028 ... b'-mfoo'])
1027 ... runcommand(server, [b'log'])
1029 ... runcommand(server, [b'log'])
1028 ... runcommand(server, [b'verify', b'-q'])
1030 ... runcommand(server, [b'verify', b'-q'])
1029 *** runcommand commit --config failafterfinalize.fail=true -mfoo
1031 *** runcommand commit --config failafterfinalize.fail=true -mfoo
1030 transaction abort!
1032 transaction abort!
1031 rollback completed
1033 rollback completed
1032 abort: fail after finalization
1034 abort: fail after finalization
1033 [255]
1035 [255]
1034 *** runcommand log
1036 *** runcommand log
1035 *** runcommand verify -q
1037 *** runcommand verify -q
1036
1038
1037 - test failure with "not-empty changelog"
1039 - test failure with "not-empty changelog"
1038
1040
1039 $ echo bar > bar
1041 $ echo bar > bar
1040 $ hg add bar
1042 $ hg add bar
1041 $ hg commit -mbar bar
1043 $ hg commit -mbar bar
1042
1044
1043 (failure before finalization)
1045 (failure before finalization)
1044
1046
1045 >>> from hgclient import check, readchannel, runcommand
1047 >>> from hgclient import check, readchannel, runcommand
1046 >>> @check
1048 >>> @check
1047 ... def abort(server):
1049 ... def abort(server):
1048 ... readchannel(server)
1050 ... readchannel(server)
1049 ... runcommand(server, [b'commit',
1051 ... runcommand(server, [b'commit',
1050 ... b'--config', b'hooks.pretxncommit=false',
1052 ... b'--config', b'hooks.pretxncommit=false',
1051 ... b'-mfoo', b'foo'])
1053 ... b'-mfoo', b'foo'])
1052 ... runcommand(server, [b'log'])
1054 ... runcommand(server, [b'log'])
1053 ... runcommand(server, [b'verify', b'-q'])
1055 ... runcommand(server, [b'verify', b'-q'])
1054 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1056 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1055 transaction abort!
1057 transaction abort!
1056 rollback completed
1058 rollback completed
1057 abort: pretxncommit hook exited with status 1
1059 abort: pretxncommit hook exited with status 1
1058 [255]
1060 [255]
1059 *** runcommand log
1061 *** runcommand log
1060 0 bar (bar)
1062 0 bar (bar)
1061 *** runcommand verify -q
1063 *** runcommand verify -q
1062
1064
1063 (failure after finalization)
1065 (failure after finalization)
1064
1066
1065 >>> from hgclient import check, readchannel, runcommand
1067 >>> from hgclient import check, readchannel, runcommand
1066 >>> @check
1068 >>> @check
1067 ... def abort(server):
1069 ... def abort(server):
1068 ... readchannel(server)
1070 ... readchannel(server)
1069 ... runcommand(server, [b'commit',
1071 ... runcommand(server, [b'commit',
1070 ... b'--config', b'failafterfinalize.fail=true',
1072 ... b'--config', b'failafterfinalize.fail=true',
1071 ... b'-mfoo', b'foo'])
1073 ... b'-mfoo', b'foo'])
1072 ... runcommand(server, [b'log'])
1074 ... runcommand(server, [b'log'])
1073 ... runcommand(server, [b'verify', b'-q'])
1075 ... runcommand(server, [b'verify', b'-q'])
1074 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1076 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1075 transaction abort!
1077 transaction abort!
1076 rollback completed
1078 rollback completed
1077 abort: fail after finalization
1079 abort: fail after finalization
1078 [255]
1080 [255]
1079 *** runcommand log
1081 *** runcommand log
1080 0 bar (bar)
1082 0 bar (bar)
1081 *** runcommand verify -q
1083 *** runcommand verify -q
1082
1084
1083 $ cd ..
1085 $ cd ..
1084
1086
1085 Test symlink traversal over cached audited paths:
1087 Test symlink traversal over cached audited paths:
1086 -------------------------------------------------
1088 -------------------------------------------------
1087
1089
1088 #if symlink
1090 #if symlink
1089
1091
1090 set up symlink hell
1092 set up symlink hell
1091
1093
1092 $ mkdir merge-symlink-out
1094 $ mkdir merge-symlink-out
1093 $ hg init merge-symlink
1095 $ hg init merge-symlink
1094 $ cd merge-symlink
1096 $ cd merge-symlink
1095 $ touch base
1097 $ touch base
1096 $ hg commit -qAm base
1098 $ hg commit -qAm base
1097 $ ln -s ../merge-symlink-out a
1099 $ ln -s ../merge-symlink-out a
1098 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1100 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1099 $ hg up -q 0
1101 $ hg up -q 0
1100 $ mkdir a
1102 $ mkdir a
1101 $ touch a/poisoned
1103 $ touch a/poisoned
1102 $ hg commit -qAm 'file a/poisoned'
1104 $ hg commit -qAm 'file a/poisoned'
1103 $ hg log -G -T '{rev}: {desc}\n'
1105 $ hg log -G -T '{rev}: {desc}\n'
1104 @ 2: file a/poisoned
1106 @ 2: file a/poisoned
1105 |
1107 |
1106 | o 1: symlink a -> ../merge-symlink-out
1108 | o 1: symlink a -> ../merge-symlink-out
1107 |/
1109 |/
1108 o 0: base
1110 o 0: base
1109
1111
1110
1112
1111 try trivial merge after update: cache of audited paths should be discarded,
1113 try trivial merge after update: cache of audited paths should be discarded,
1112 and the merge should fail (issue5628)
1114 and the merge should fail (issue5628)
1113
1115
1114 $ hg up -q null
1116 $ hg up -q null
1115 >>> from hgclient import check, readchannel, runcommand
1117 >>> from hgclient import check, readchannel, runcommand
1116 >>> @check
1118 >>> @check
1117 ... def merge(server):
1119 ... def merge(server):
1118 ... readchannel(server)
1120 ... readchannel(server)
1119 ... # audit a/poisoned as a good path
1121 ... # audit a/poisoned as a good path
1120 ... runcommand(server, [b'up', b'-qC', b'2'])
1122 ... runcommand(server, [b'up', b'-qC', b'2'])
1121 ... runcommand(server, [b'up', b'-qC', b'1'])
1123 ... runcommand(server, [b'up', b'-qC', b'1'])
1122 ... # here a is a symlink, so a/poisoned is bad
1124 ... # here a is a symlink, so a/poisoned is bad
1123 ... runcommand(server, [b'merge', b'2'])
1125 ... runcommand(server, [b'merge', b'2'])
1124 *** runcommand up -qC 2
1126 *** runcommand up -qC 2
1125 *** runcommand up -qC 1
1127 *** runcommand up -qC 1
1126 *** runcommand merge 2
1128 *** runcommand merge 2
1127 abort: path 'a/poisoned' traverses symbolic link 'a'
1129 abort: path 'a/poisoned' traverses symbolic link 'a'
1128 [255]
1130 [255]
1129 $ ls ../merge-symlink-out
1131 $ ls ../merge-symlink-out
1130
1132
1131 cache of repo.auditor should be discarded, so matcher would never traverse
1133 cache of repo.auditor should be discarded, so matcher would never traverse
1132 symlinks:
1134 symlinks:
1133
1135
1134 $ hg up -qC 0
1136 $ hg up -qC 0
1135 $ touch ../merge-symlink-out/poisoned
1137 $ touch ../merge-symlink-out/poisoned
1136 >>> from hgclient import check, readchannel, runcommand
1138 >>> from hgclient import check, readchannel, runcommand
1137 >>> @check
1139 >>> @check
1138 ... def files(server):
1140 ... def files(server):
1139 ... readchannel(server)
1141 ... readchannel(server)
1140 ... runcommand(server, [b'up', b'-qC', b'2'])
1142 ... runcommand(server, [b'up', b'-qC', b'2'])
1141 ... # audit a/poisoned as a good path
1143 ... # audit a/poisoned as a good path
1142 ... runcommand(server, [b'files', b'a/poisoned'])
1144 ... runcommand(server, [b'files', b'a/poisoned'])
1143 ... runcommand(server, [b'up', b'-qC', b'0'])
1145 ... runcommand(server, [b'up', b'-qC', b'0'])
1144 ... runcommand(server, [b'up', b'-qC', b'1'])
1146 ... runcommand(server, [b'up', b'-qC', b'1'])
1145 ... # here 'a' is a symlink, so a/poisoned should be warned
1147 ... # here 'a' is a symlink, so a/poisoned should be warned
1146 ... runcommand(server, [b'files', b'a/poisoned'])
1148 ... runcommand(server, [b'files', b'a/poisoned'])
1147 *** runcommand up -qC 2
1149 *** runcommand up -qC 2
1148 *** runcommand files a/poisoned
1150 *** runcommand files a/poisoned
1149 a/poisoned
1151 a/poisoned
1150 *** runcommand up -qC 0
1152 *** runcommand up -qC 0
1151 *** runcommand up -qC 1
1153 *** runcommand up -qC 1
1152 *** runcommand files a/poisoned
1154 *** runcommand files a/poisoned
1153 abort: path 'a/poisoned' traverses symbolic link 'a'
1155 abort: path 'a/poisoned' traverses symbolic link 'a'
1154 [255]
1156 [255]
1155
1157
1156 $ cd ..
1158 $ cd ..
1157
1159
1158 #endif
1160 #endif
@@ -1,141 +1,141 b''
1 #require unix-permissions no-root no-windows
1 #require unix-permissions no-root no-windows
2
2
3 Prepare
3 Prepare
4
4
5 $ hg init a
5 $ hg init a
6 $ echo a > a/a
6 $ echo a > a/a
7 $ hg -R a ci -A -m a
7 $ hg -R a ci -A -m a
8 adding a
8 adding a
9
9
10 $ hg clone a b
10 $ hg clone a b
11 updating to branch default
11 updating to branch default
12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
13
13
14 Test that raising an exception in the release function doesn't cause the lock to choke
14 Test that raising an exception in the release function doesn't cause the lock to choke
15
15
16 $ cat > testlock.py << EOF
16 $ cat > testlock.py << EOF
17 > from mercurial import error, registrar
17 > from mercurial import error, registrar
18 >
18 >
19 > cmdtable = {}
19 > cmdtable = {}
20 > command = registrar.command(cmdtable)
20 > command = registrar.command(cmdtable)
21 >
21 >
22 > def acquiretestlock(repo, releaseexc):
22 > def acquiretestlock(repo, releaseexc):
23 > def unlock():
23 > def unlock():
24 > if releaseexc:
24 > if releaseexc:
25 > raise error.Abort(b'expected release exception')
25 > raise error.Abort(b'expected release exception')
26 > l = repo._lock(repo.vfs, b'testlock', False, unlock, None, b'test lock')
26 > l = repo._lock(repo.vfs, b'testlock', False, unlock, None, b'test lock')
27 > return l
27 > return l
28 >
28 >
29 > @command(b'testlockexc')
29 > @command(b'testlockexc')
30 > def testlockexc(ui, repo):
30 > def testlockexc(ui, repo):
31 > testlock = acquiretestlock(repo, True)
31 > testlock = acquiretestlock(repo, True)
32 > try:
32 > try:
33 > testlock.release()
33 > testlock.release()
34 > finally:
34 > finally:
35 > try:
35 > try:
36 > testlock = acquiretestlock(repo, False)
36 > testlock = acquiretestlock(repo, False)
37 > except error.LockHeld:
37 > except error.LockHeld:
38 > raise error.Abort(b'lockfile on disk even after releasing!')
38 > raise error.Abort(b'lockfile on disk even after releasing!')
39 > testlock.release()
39 > testlock.release()
40 > EOF
40 > EOF
41 $ cat >> $HGRCPATH << EOF
41 $ cat >> $HGRCPATH << EOF
42 > [extensions]
42 > [extensions]
43 > testlock=$TESTTMP/testlock.py
43 > testlock=$TESTTMP/testlock.py
44 > EOF
44 > EOF
45
45
46 $ hg -R b testlockexc
46 $ hg -R b testlockexc
47 abort: expected release exception
47 abort: expected release exception
48 [255]
48 [255]
49
49
50 One process waiting for another
50 One process waiting for another
51
51
52 $ cat > hooks.py << EOF
52 $ cat > hooks.py << EOF
53 > import time
53 > import time
54 > def sleepone(**x): time.sleep(1)
54 > def sleepone(**x): time.sleep(1)
55 > def sleephalf(**x): time.sleep(0.5)
55 > def sleephalf(**x): time.sleep(0.5)
56 > EOF
56 > EOF
57 $ echo b > b/b
57 $ echo b > b/b
58 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
58 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
59 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
59 $ hg -R b up -q --config ui.timeout.warn=0 --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
60 > > preup-stdout 2>preup-stderr
60 > > preup-stdout 2>preup-stderr
61 $ wait
61 $ wait
62 $ cat preup-stdout
62 $ cat preup-stdout
63 $ cat preup-stderr
63 $ cat preup-stderr
64 waiting for lock on working directory of b held by process '*' on host '*' (glob)
64 waiting for lock on working directory of b held by process '*' on host '*' (glob)
65 got lock after * seconds (glob)
65 got lock after * seconds (glob)
66 $ cat stdout
66 $ cat stdout
67 adding b
67 adding b
68
68
69 On processs waiting on another, warning after a long time.
69 On processs waiting on another, warning after a long time.
70
70
71 $ echo b > b/c
71 $ echo b > b/c
72 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
72 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
73 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
73 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
74 > --config ui.timeout.warn=250 \
74 > --config ui.timeout.warn=250 \
75 > > preup-stdout 2>preup-stderr
75 > > preup-stdout 2>preup-stderr
76 $ wait
76 $ wait
77 $ cat preup-stdout
77 $ cat preup-stdout
78 $ cat preup-stderr
78 $ cat preup-stderr
79 $ cat stdout
79 $ cat stdout
80 adding c
80 adding c
81
81
82 On processs waiting on another, warning disabled.
82 On processs waiting on another, warning disabled.
83
83
84 $ echo b > b/d
84 $ echo b > b/d
85 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
85 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
86 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
86 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
87 > --config ui.timeout.warn=-1 \
87 > --config ui.timeout.warn=-1 \
88 > > preup-stdout 2>preup-stderr
88 > > preup-stdout 2>preup-stderr
89 $ wait
89 $ wait
90 $ cat preup-stdout
90 $ cat preup-stdout
91 $ cat preup-stderr
91 $ cat preup-stderr
92 $ cat stdout
92 $ cat stdout
93 adding d
93 adding d
94
94
95 check we still print debug output
95 check we still print debug output
96
96
97 On processs waiting on another, warning after a long time (debug output on)
97 On processs waiting on another, warning after a long time (debug output on)
98
98
99 $ echo b > b/e
99 $ echo b > b/e
100 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
100 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
101 $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
101 $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
102 > --config ui.timeout.warn=250 --debug\
102 > --config ui.timeout.warn=250 --debug\
103 > > preup-stdout 2>preup-stderr
103 > > preup-stdout 2>preup-stderr
104 $ wait
104 $ wait
105 $ cat preup-stdout
105 $ cat preup-stdout
106 calling hook pre-update: hghook_pre-update.sleephalf
106 calling hook pre-update: hghook_pre-update.sleephalf
107 waiting for lock on working directory of b held by process '*' on host '*' (glob)
107 waiting for lock on working directory of b held by process '*' on host '*' (glob)
108 got lock after * seconds (glob)
108 got lock after * seconds (glob)
109 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 $ cat preup-stderr
110 $ cat preup-stderr
111 $ cat stdout
111 $ cat stdout
112 adding e
112 adding e
113
113
114 On processs waiting on another, warning disabled, (debug output on)
114 On processs waiting on another, warning disabled, (debug output on)
115
115
116 $ echo b > b/f
116 $ echo b > b/f
117 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
117 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
118 $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
118 $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
119 > --config ui.timeout.warn=-1 --debug\
119 > --config ui.timeout.warn=-1 --debug\
120 > > preup-stdout 2>preup-stderr
120 > > preup-stdout 2>preup-stderr
121 $ wait
121 $ wait
122 $ cat preup-stdout
122 $ cat preup-stdout
123 calling hook pre-update: hghook_pre-update.sleephalf
123 calling hook pre-update: hghook_pre-update.sleephalf
124 waiting for lock on working directory of b held by process '*' on host '*' (glob)
124 waiting for lock on working directory of b held by process '*' on host '*' (glob)
125 got lock after * seconds (glob)
125 got lock after * seconds (glob)
126 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 $ cat preup-stderr
127 $ cat preup-stderr
128 $ cat stdout
128 $ cat stdout
129 adding f
129 adding f
130
130
131 Pushing to a local read-only repo that can't be locked
131 Pushing to a local read-only repo that can't be locked
132
132
133 $ chmod 100 a/.hg/store
133 $ chmod 100 a/.hg/store
134
134
135 $ hg -R b push a
135 $ hg -R b push a
136 pushing to a
136 pushing to a
137 searching for changes
137 searching for changes
138 abort: could not lock repository a: Permission denied
138 abort: could not lock repository a: Permission denied
139 [20]
139 [20]
140
140
141 $ chmod 700 a/.hg/store
141 $ chmod 700 a/.hg/store
@@ -1,1825 +1,1825 b''
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [extensions]
2 > [extensions]
3 > drawdag=$TESTDIR/drawdag.py
3 > drawdag=$TESTDIR/drawdag.py
4 > phasereport=$TESTDIR/testlib/ext-phase-report.py
4 > phasereport=$TESTDIR/testlib/ext-phase-report.py
5 > EOF
5 > EOF
6
6
7 $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
7 $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
8
8
9 $ mkcommit() {
9 $ mkcommit() {
10 > echo "$1" > "$1"
10 > echo "$1" > "$1"
11 > hg add "$1"
11 > hg add "$1"
12 > message="$1"
12 > message="$1"
13 > shift
13 > shift
14 > hg ci -m "$message" $*
14 > hg ci -m "$message" $*
15 > }
15 > }
16
16
17 $ hg init alpha
17 $ hg init alpha
18 $ cd alpha
18 $ cd alpha
19 $ mkcommit a-A
19 $ mkcommit a-A
20 test-debug-phase: new rev 0: x -> 1
20 test-debug-phase: new rev 0: x -> 1
21 $ mkcommit a-B
21 $ mkcommit a-B
22 test-debug-phase: new rev 1: x -> 1
22 test-debug-phase: new rev 1: x -> 1
23 $ mkcommit a-C
23 $ mkcommit a-C
24 test-debug-phase: new rev 2: x -> 1
24 test-debug-phase: new rev 2: x -> 1
25 $ mkcommit a-D
25 $ mkcommit a-D
26 test-debug-phase: new rev 3: x -> 1
26 test-debug-phase: new rev 3: x -> 1
27 $ hgph
27 $ hgph
28 @ 3 draft a-D - b555f63b6063
28 @ 3 draft a-D - b555f63b6063
29 |
29 |
30 o 2 draft a-C - 54acac6f23ab
30 o 2 draft a-C - 54acac6f23ab
31 |
31 |
32 o 1 draft a-B - 548a3d25dbf0
32 o 1 draft a-B - 548a3d25dbf0
33 |
33 |
34 o 0 draft a-A - 054250a37db4
34 o 0 draft a-A - 054250a37db4
35
35
36
36
37 $ hg init ../beta
37 $ hg init ../beta
38 $ hg push -r 1 ../beta
38 $ hg push -r 1 ../beta
39 pushing to ../beta
39 pushing to ../beta
40 searching for changes
40 searching for changes
41 adding changesets
41 adding changesets
42 adding manifests
42 adding manifests
43 adding file changes
43 adding file changes
44 added 2 changesets with 2 changes to 2 files
44 added 2 changesets with 2 changes to 2 files
45 test-debug-phase: new rev 0: x -> 0
45 test-debug-phase: new rev 0: x -> 0
46 test-debug-phase: new rev 1: x -> 0
46 test-debug-phase: new rev 1: x -> 0
47 test-debug-phase: move rev 0: 1 -> 0
47 test-debug-phase: move rev 0: 1 -> 0
48 test-debug-phase: move rev 1: 1 -> 0
48 test-debug-phase: move rev 1: 1 -> 0
49 $ hgph
49 $ hgph
50 @ 3 draft a-D - b555f63b6063
50 @ 3 draft a-D - b555f63b6063
51 |
51 |
52 o 2 draft a-C - 54acac6f23ab
52 o 2 draft a-C - 54acac6f23ab
53 |
53 |
54 o 1 public a-B - 548a3d25dbf0
54 o 1 public a-B - 548a3d25dbf0
55 |
55 |
56 o 0 public a-A - 054250a37db4
56 o 0 public a-A - 054250a37db4
57
57
58
58
59 $ cd ../beta
59 $ cd ../beta
60 $ hgph
60 $ hgph
61 o 1 public a-B - 548a3d25dbf0
61 o 1 public a-B - 548a3d25dbf0
62 |
62 |
63 o 0 public a-A - 054250a37db4
63 o 0 public a-A - 054250a37db4
64
64
65 $ hg up -q
65 $ hg up -q
66 $ mkcommit b-A
66 $ mkcommit b-A
67 test-debug-phase: new rev 2: x -> 1
67 test-debug-phase: new rev 2: x -> 1
68 $ hgph
68 $ hgph
69 @ 2 draft b-A - f54f1bb90ff3
69 @ 2 draft b-A - f54f1bb90ff3
70 |
70 |
71 o 1 public a-B - 548a3d25dbf0
71 o 1 public a-B - 548a3d25dbf0
72 |
72 |
73 o 0 public a-A - 054250a37db4
73 o 0 public a-A - 054250a37db4
74
74
75 $ hg pull ../alpha
75 $ hg pull ../alpha
76 pulling from ../alpha
76 pulling from ../alpha
77 searching for changes
77 searching for changes
78 adding changesets
78 adding changesets
79 adding manifests
79 adding manifests
80 adding file changes
80 adding file changes
81 added 2 changesets with 2 changes to 2 files (+1 heads)
81 added 2 changesets with 2 changes to 2 files (+1 heads)
82 new changesets 54acac6f23ab:b555f63b6063
82 new changesets 54acac6f23ab:b555f63b6063
83 test-debug-phase: new rev 3: x -> 0
83 test-debug-phase: new rev 3: x -> 0
84 test-debug-phase: new rev 4: x -> 0
84 test-debug-phase: new rev 4: x -> 0
85 (run 'hg heads' to see heads, 'hg merge' to merge)
85 (run 'hg heads' to see heads, 'hg merge' to merge)
86 $ hgph
86 $ hgph
87 o 4 public a-D - b555f63b6063
87 o 4 public a-D - b555f63b6063
88 |
88 |
89 o 3 public a-C - 54acac6f23ab
89 o 3 public a-C - 54acac6f23ab
90 |
90 |
91 | @ 2 draft b-A - f54f1bb90ff3
91 | @ 2 draft b-A - f54f1bb90ff3
92 |/
92 |/
93 o 1 public a-B - 548a3d25dbf0
93 o 1 public a-B - 548a3d25dbf0
94 |
94 |
95 o 0 public a-A - 054250a37db4
95 o 0 public a-A - 054250a37db4
96
96
97
97
98 pull did not updated ../alpha state.
98 pull did not updated ../alpha state.
99 push from alpha to beta should update phase even if nothing is transferred
99 push from alpha to beta should update phase even if nothing is transferred
100
100
101 $ cd ../alpha
101 $ cd ../alpha
102 $ hgph # not updated by remote pull
102 $ hgph # not updated by remote pull
103 @ 3 draft a-D - b555f63b6063
103 @ 3 draft a-D - b555f63b6063
104 |
104 |
105 o 2 draft a-C - 54acac6f23ab
105 o 2 draft a-C - 54acac6f23ab
106 |
106 |
107 o 1 public a-B - 548a3d25dbf0
107 o 1 public a-B - 548a3d25dbf0
108 |
108 |
109 o 0 public a-A - 054250a37db4
109 o 0 public a-A - 054250a37db4
110
110
111 $ hg push -r 2 ../beta
111 $ hg push -r 2 ../beta
112 pushing to ../beta
112 pushing to ../beta
113 searching for changes
113 searching for changes
114 no changes found
114 no changes found
115 test-debug-phase: move rev 2: 1 -> 0
115 test-debug-phase: move rev 2: 1 -> 0
116 [1]
116 [1]
117 $ hgph
117 $ hgph
118 @ 3 draft a-D - b555f63b6063
118 @ 3 draft a-D - b555f63b6063
119 |
119 |
120 o 2 public a-C - 54acac6f23ab
120 o 2 public a-C - 54acac6f23ab
121 |
121 |
122 o 1 public a-B - 548a3d25dbf0
122 o 1 public a-B - 548a3d25dbf0
123 |
123 |
124 o 0 public a-A - 054250a37db4
124 o 0 public a-A - 054250a37db4
125
125
126 $ hg push ../beta
126 $ hg push ../beta
127 pushing to ../beta
127 pushing to ../beta
128 searching for changes
128 searching for changes
129 no changes found
129 no changes found
130 test-debug-phase: move rev 3: 1 -> 0
130 test-debug-phase: move rev 3: 1 -> 0
131 [1]
131 [1]
132 $ hgph
132 $ hgph
133 @ 3 public a-D - b555f63b6063
133 @ 3 public a-D - b555f63b6063
134 |
134 |
135 o 2 public a-C - 54acac6f23ab
135 o 2 public a-C - 54acac6f23ab
136 |
136 |
137 o 1 public a-B - 548a3d25dbf0
137 o 1 public a-B - 548a3d25dbf0
138 |
138 |
139 o 0 public a-A - 054250a37db4
139 o 0 public a-A - 054250a37db4
140
140
141
141
142 update must update phase of common changeset too
142 update must update phase of common changeset too
143
143
144 $ hg pull ../beta # getting b-A
144 $ hg pull ../beta # getting b-A
145 pulling from ../beta
145 pulling from ../beta
146 searching for changes
146 searching for changes
147 adding changesets
147 adding changesets
148 adding manifests
148 adding manifests
149 adding file changes
149 adding file changes
150 added 1 changesets with 1 changes to 1 files (+1 heads)
150 added 1 changesets with 1 changes to 1 files (+1 heads)
151 new changesets f54f1bb90ff3
151 new changesets f54f1bb90ff3
152 test-debug-phase: new rev 4: x -> 0
152 test-debug-phase: new rev 4: x -> 0
153 (run 'hg heads' to see heads, 'hg merge' to merge)
153 (run 'hg heads' to see heads, 'hg merge' to merge)
154
154
155 $ cd ../beta
155 $ cd ../beta
156 $ hgph # not updated by remote pull
156 $ hgph # not updated by remote pull
157 o 4 public a-D - b555f63b6063
157 o 4 public a-D - b555f63b6063
158 |
158 |
159 o 3 public a-C - 54acac6f23ab
159 o 3 public a-C - 54acac6f23ab
160 |
160 |
161 | @ 2 draft b-A - f54f1bb90ff3
161 | @ 2 draft b-A - f54f1bb90ff3
162 |/
162 |/
163 o 1 public a-B - 548a3d25dbf0
163 o 1 public a-B - 548a3d25dbf0
164 |
164 |
165 o 0 public a-A - 054250a37db4
165 o 0 public a-A - 054250a37db4
166
166
167 $ hg pull ../alpha
167 $ hg pull ../alpha
168 pulling from ../alpha
168 pulling from ../alpha
169 searching for changes
169 searching for changes
170 no changes found
170 no changes found
171 1 local changesets published
171 1 local changesets published
172 test-debug-phase: move rev 2: 1 -> 0
172 test-debug-phase: move rev 2: 1 -> 0
173 $ hgph
173 $ hgph
174 o 4 public a-D - b555f63b6063
174 o 4 public a-D - b555f63b6063
175 |
175 |
176 o 3 public a-C - 54acac6f23ab
176 o 3 public a-C - 54acac6f23ab
177 |
177 |
178 | @ 2 public b-A - f54f1bb90ff3
178 | @ 2 public b-A - f54f1bb90ff3
179 |/
179 |/
180 o 1 public a-B - 548a3d25dbf0
180 o 1 public a-B - 548a3d25dbf0
181 |
181 |
182 o 0 public a-A - 054250a37db4
182 o 0 public a-A - 054250a37db4
183
183
184
184
185 Publish configuration option
185 Publish configuration option
186 ----------------------------
186 ----------------------------
187
187
188 Pull
188 Pull
189 ````
189 ````
190
190
191 changegroup are added without phase movement
191 changegroup are added without phase movement
192
192
193 $ hg bundle -a ../base.bundle
193 $ hg bundle -a ../base.bundle
194 5 changesets found
194 5 changesets found
195 $ cd ..
195 $ cd ..
196 $ hg init mu
196 $ hg init mu
197 $ cd mu
197 $ cd mu
198 $ cat > .hg/hgrc << EOF
198 $ cat > .hg/hgrc << EOF
199 > [phases]
199 > [phases]
200 > publish=0
200 > publish=0
201 > EOF
201 > EOF
202 $ hg unbundle ../base.bundle
202 $ hg unbundle ../base.bundle
203 adding changesets
203 adding changesets
204 adding manifests
204 adding manifests
205 adding file changes
205 adding file changes
206 added 5 changesets with 5 changes to 5 files (+1 heads)
206 added 5 changesets with 5 changes to 5 files (+1 heads)
207 new changesets 054250a37db4:b555f63b6063 (5 drafts)
207 new changesets 054250a37db4:b555f63b6063 (5 drafts)
208 test-debug-phase: new rev 0: x -> 1
208 test-debug-phase: new rev 0: x -> 1
209 test-debug-phase: new rev 1: x -> 1
209 test-debug-phase: new rev 1: x -> 1
210 test-debug-phase: new rev 2: x -> 1
210 test-debug-phase: new rev 2: x -> 1
211 test-debug-phase: new rev 3: x -> 1
211 test-debug-phase: new rev 3: x -> 1
212 test-debug-phase: new rev 4: x -> 1
212 test-debug-phase: new rev 4: x -> 1
213 (run 'hg heads' to see heads, 'hg merge' to merge)
213 (run 'hg heads' to see heads, 'hg merge' to merge)
214 $ hgph
214 $ hgph
215 o 4 draft a-D - b555f63b6063
215 o 4 draft a-D - b555f63b6063
216 |
216 |
217 o 3 draft a-C - 54acac6f23ab
217 o 3 draft a-C - 54acac6f23ab
218 |
218 |
219 | o 2 draft b-A - f54f1bb90ff3
219 | o 2 draft b-A - f54f1bb90ff3
220 |/
220 |/
221 o 1 draft a-B - 548a3d25dbf0
221 o 1 draft a-B - 548a3d25dbf0
222 |
222 |
223 o 0 draft a-A - 054250a37db4
223 o 0 draft a-A - 054250a37db4
224
224
225 $ cd ..
225 $ cd ..
226
226
227 Pulling from publish=False to publish=False does not move boundary.
227 Pulling from publish=False to publish=False does not move boundary.
228
228
229 $ hg init nu
229 $ hg init nu
230 $ cd nu
230 $ cd nu
231 $ cat > .hg/hgrc << EOF
231 $ cat > .hg/hgrc << EOF
232 > [phases]
232 > [phases]
233 > publish=0
233 > publish=0
234 > EOF
234 > EOF
235 $ hg pull ../mu -r 54acac6f23ab
235 $ hg pull ../mu -r 54acac6f23ab
236 pulling from ../mu
236 pulling from ../mu
237 adding changesets
237 adding changesets
238 adding manifests
238 adding manifests
239 adding file changes
239 adding file changes
240 added 3 changesets with 3 changes to 3 files
240 added 3 changesets with 3 changes to 3 files
241 new changesets 054250a37db4:54acac6f23ab (3 drafts)
241 new changesets 054250a37db4:54acac6f23ab (3 drafts)
242 test-debug-phase: new rev 0: x -> 1
242 test-debug-phase: new rev 0: x -> 1
243 test-debug-phase: new rev 1: x -> 1
243 test-debug-phase: new rev 1: x -> 1
244 test-debug-phase: new rev 2: x -> 1
244 test-debug-phase: new rev 2: x -> 1
245 (run 'hg update' to get a working copy)
245 (run 'hg update' to get a working copy)
246 $ hgph
246 $ hgph
247 o 2 draft a-C - 54acac6f23ab
247 o 2 draft a-C - 54acac6f23ab
248 |
248 |
249 o 1 draft a-B - 548a3d25dbf0
249 o 1 draft a-B - 548a3d25dbf0
250 |
250 |
251 o 0 draft a-A - 054250a37db4
251 o 0 draft a-A - 054250a37db4
252
252
253
253
254 Even for common
254 Even for common
255
255
256 $ hg pull ../mu -r f54f1bb90ff3
256 $ hg pull ../mu -r f54f1bb90ff3
257 pulling from ../mu
257 pulling from ../mu
258 searching for changes
258 searching for changes
259 adding changesets
259 adding changesets
260 adding manifests
260 adding manifests
261 adding file changes
261 adding file changes
262 added 1 changesets with 1 changes to 1 files (+1 heads)
262 added 1 changesets with 1 changes to 1 files (+1 heads)
263 new changesets f54f1bb90ff3 (1 drafts)
263 new changesets f54f1bb90ff3 (1 drafts)
264 test-debug-phase: new rev 3: x -> 1
264 test-debug-phase: new rev 3: x -> 1
265 (run 'hg heads' to see heads, 'hg merge' to merge)
265 (run 'hg heads' to see heads, 'hg merge' to merge)
266 $ hgph
266 $ hgph
267 o 3 draft b-A - f54f1bb90ff3
267 o 3 draft b-A - f54f1bb90ff3
268 |
268 |
269 | o 2 draft a-C - 54acac6f23ab
269 | o 2 draft a-C - 54acac6f23ab
270 |/
270 |/
271 o 1 draft a-B - 548a3d25dbf0
271 o 1 draft a-B - 548a3d25dbf0
272 |
272 |
273 o 0 draft a-A - 054250a37db4
273 o 0 draft a-A - 054250a37db4
274
274
275
275
276
276
277 Pulling from Publish=True to Publish=False move boundary in common set.
277 Pulling from Publish=True to Publish=False move boundary in common set.
278 we are in nu
278 we are in nu
279
279
280 $ hg pull ../alpha -r b555f63b6063
280 $ hg pull ../alpha -r b555f63b6063
281 pulling from ../alpha
281 pulling from ../alpha
282 searching for changes
282 searching for changes
283 adding changesets
283 adding changesets
284 adding manifests
284 adding manifests
285 adding file changes
285 adding file changes
286 added 1 changesets with 1 changes to 1 files
286 added 1 changesets with 1 changes to 1 files
287 new changesets b555f63b6063
287 new changesets b555f63b6063
288 3 local changesets published
288 3 local changesets published
289 test-debug-phase: move rev 0: 1 -> 0
289 test-debug-phase: move rev 0: 1 -> 0
290 test-debug-phase: move rev 1: 1 -> 0
290 test-debug-phase: move rev 1: 1 -> 0
291 test-debug-phase: move rev 2: 1 -> 0
291 test-debug-phase: move rev 2: 1 -> 0
292 test-debug-phase: new rev 4: x -> 0
292 test-debug-phase: new rev 4: x -> 0
293 (run 'hg update' to get a working copy)
293 (run 'hg update' to get a working copy)
294 $ hgph # f54f1bb90ff3 stay draft, not ancestor of -r
294 $ hgph # f54f1bb90ff3 stay draft, not ancestor of -r
295 o 4 public a-D - b555f63b6063
295 o 4 public a-D - b555f63b6063
296 |
296 |
297 | o 3 draft b-A - f54f1bb90ff3
297 | o 3 draft b-A - f54f1bb90ff3
298 | |
298 | |
299 o | 2 public a-C - 54acac6f23ab
299 o | 2 public a-C - 54acac6f23ab
300 |/
300 |/
301 o 1 public a-B - 548a3d25dbf0
301 o 1 public a-B - 548a3d25dbf0
302 |
302 |
303 o 0 public a-A - 054250a37db4
303 o 0 public a-A - 054250a37db4
304
304
305
305
306 pulling from Publish=False to publish=False with some public
306 pulling from Publish=False to publish=False with some public
307
307
308 $ hg up -q f54f1bb90ff3
308 $ hg up -q f54f1bb90ff3
309 $ mkcommit n-A
309 $ mkcommit n-A
310 test-debug-phase: new rev 5: x -> 1
310 test-debug-phase: new rev 5: x -> 1
311 $ mkcommit n-B
311 $ mkcommit n-B
312 test-debug-phase: new rev 6: x -> 1
312 test-debug-phase: new rev 6: x -> 1
313 $ hgph
313 $ hgph
314 @ 6 draft n-B - 145e75495359
314 @ 6 draft n-B - 145e75495359
315 |
315 |
316 o 5 draft n-A - d6bcb4f74035
316 o 5 draft n-A - d6bcb4f74035
317 |
317 |
318 | o 4 public a-D - b555f63b6063
318 | o 4 public a-D - b555f63b6063
319 | |
319 | |
320 o | 3 draft b-A - f54f1bb90ff3
320 o | 3 draft b-A - f54f1bb90ff3
321 | |
321 | |
322 | o 2 public a-C - 54acac6f23ab
322 | o 2 public a-C - 54acac6f23ab
323 |/
323 |/
324 o 1 public a-B - 548a3d25dbf0
324 o 1 public a-B - 548a3d25dbf0
325 |
325 |
326 o 0 public a-A - 054250a37db4
326 o 0 public a-A - 054250a37db4
327
327
328 $ cd ../mu
328 $ cd ../mu
329 $ hg pull ../nu --confirm --config ui.interactive=True<<EOF
329 $ hg pull ../nu --confirm --config ui.interactive=True<<EOF
330 > y
330 > y
331 > EOF
331 > EOF
332 pulling from ../nu
332 pulling from ../nu
333 searching for changes
333 searching for changes
334 adding changesets
334 adding changesets
335 adding manifests
335 adding manifests
336 adding file changes
336 adding file changes
337 adding 2 changesets with 2 changes to 2 files
337 adding 2 changesets with 2 changes to 2 files
338 new changesets d6bcb4f74035:145e75495359 (2 drafts)
338 new changesets d6bcb4f74035:145e75495359 (2 drafts)
339 4 local changesets will be published
339 4 local changesets will be published
340 accept incoming changes (yn)? y
340 accept incoming changes (yn)? y
341 added 2 changesets with 2 changes to 2 files
341 added 2 changesets with 2 changes to 2 files
342 new changesets d6bcb4f74035:145e75495359 (2 drafts)
342 new changesets d6bcb4f74035:145e75495359 (2 drafts)
343 4 local changesets published
343 4 local changesets published
344 test-debug-phase: move rev 0: 1 -> 0
344 test-debug-phase: move rev 0: 1 -> 0
345 test-debug-phase: move rev 1: 1 -> 0
345 test-debug-phase: move rev 1: 1 -> 0
346 test-debug-phase: move rev 3: 1 -> 0
346 test-debug-phase: move rev 3: 1 -> 0
347 test-debug-phase: move rev 4: 1 -> 0
347 test-debug-phase: move rev 4: 1 -> 0
348 test-debug-phase: new rev 5: x -> 1
348 test-debug-phase: new rev 5: x -> 1
349 test-debug-phase: new rev 6: x -> 1
349 test-debug-phase: new rev 6: x -> 1
350 (run 'hg update' to get a working copy)
350 (run 'hg update' to get a working copy)
351 $ hgph
351 $ hgph
352 o 6 draft n-B - 145e75495359
352 o 6 draft n-B - 145e75495359
353 |
353 |
354 o 5 draft n-A - d6bcb4f74035
354 o 5 draft n-A - d6bcb4f74035
355 |
355 |
356 | o 4 public a-D - b555f63b6063
356 | o 4 public a-D - b555f63b6063
357 | |
357 | |
358 | o 3 public a-C - 54acac6f23ab
358 | o 3 public a-C - 54acac6f23ab
359 | |
359 | |
360 o | 2 draft b-A - f54f1bb90ff3
360 o | 2 draft b-A - f54f1bb90ff3
361 |/
361 |/
362 o 1 public a-B - 548a3d25dbf0
362 o 1 public a-B - 548a3d25dbf0
363 |
363 |
364 o 0 public a-A - 054250a37db4
364 o 0 public a-A - 054250a37db4
365
365
366 $ cd ..
366 $ cd ..
367
367
368 pulling into publish=True
368 pulling into publish=True
369
369
370 $ cd alpha
370 $ cd alpha
371 $ hgph
371 $ hgph
372 o 4 public b-A - f54f1bb90ff3
372 o 4 public b-A - f54f1bb90ff3
373 |
373 |
374 | @ 3 public a-D - b555f63b6063
374 | @ 3 public a-D - b555f63b6063
375 | |
375 | |
376 | o 2 public a-C - 54acac6f23ab
376 | o 2 public a-C - 54acac6f23ab
377 |/
377 |/
378 o 1 public a-B - 548a3d25dbf0
378 o 1 public a-B - 548a3d25dbf0
379 |
379 |
380 o 0 public a-A - 054250a37db4
380 o 0 public a-A - 054250a37db4
381
381
382 $ hg pull ../mu
382 $ hg pull ../mu
383 pulling from ../mu
383 pulling from ../mu
384 searching for changes
384 searching for changes
385 adding changesets
385 adding changesets
386 adding manifests
386 adding manifests
387 adding file changes
387 adding file changes
388 added 2 changesets with 2 changes to 2 files
388 added 2 changesets with 2 changes to 2 files
389 new changesets d6bcb4f74035:145e75495359 (2 drafts)
389 new changesets d6bcb4f74035:145e75495359 (2 drafts)
390 test-debug-phase: new rev 5: x -> 1
390 test-debug-phase: new rev 5: x -> 1
391 test-debug-phase: new rev 6: x -> 1
391 test-debug-phase: new rev 6: x -> 1
392 (run 'hg update' to get a working copy)
392 (run 'hg update' to get a working copy)
393 $ hgph
393 $ hgph
394 o 6 draft n-B - 145e75495359
394 o 6 draft n-B - 145e75495359
395 |
395 |
396 o 5 draft n-A - d6bcb4f74035
396 o 5 draft n-A - d6bcb4f74035
397 |
397 |
398 o 4 public b-A - f54f1bb90ff3
398 o 4 public b-A - f54f1bb90ff3
399 |
399 |
400 | @ 3 public a-D - b555f63b6063
400 | @ 3 public a-D - b555f63b6063
401 | |
401 | |
402 | o 2 public a-C - 54acac6f23ab
402 | o 2 public a-C - 54acac6f23ab
403 |/
403 |/
404 o 1 public a-B - 548a3d25dbf0
404 o 1 public a-B - 548a3d25dbf0
405 |
405 |
406 o 0 public a-A - 054250a37db4
406 o 0 public a-A - 054250a37db4
407
407
408 $ cd ..
408 $ cd ..
409
409
410 pulling back into original repo
410 pulling back into original repo
411
411
412 $ cd nu
412 $ cd nu
413 $ hg pull ../alpha
413 $ hg pull ../alpha
414 pulling from ../alpha
414 pulling from ../alpha
415 searching for changes
415 searching for changes
416 no changes found
416 no changes found
417 3 local changesets published
417 3 local changesets published
418 test-debug-phase: move rev 3: 1 -> 0
418 test-debug-phase: move rev 3: 1 -> 0
419 test-debug-phase: move rev 5: 1 -> 0
419 test-debug-phase: move rev 5: 1 -> 0
420 test-debug-phase: move rev 6: 1 -> 0
420 test-debug-phase: move rev 6: 1 -> 0
421 $ hgph
421 $ hgph
422 @ 6 public n-B - 145e75495359
422 @ 6 public n-B - 145e75495359
423 |
423 |
424 o 5 public n-A - d6bcb4f74035
424 o 5 public n-A - d6bcb4f74035
425 |
425 |
426 | o 4 public a-D - b555f63b6063
426 | o 4 public a-D - b555f63b6063
427 | |
427 | |
428 o | 3 public b-A - f54f1bb90ff3
428 o | 3 public b-A - f54f1bb90ff3
429 | |
429 | |
430 | o 2 public a-C - 54acac6f23ab
430 | o 2 public a-C - 54acac6f23ab
431 |/
431 |/
432 o 1 public a-B - 548a3d25dbf0
432 o 1 public a-B - 548a3d25dbf0
433 |
433 |
434 o 0 public a-A - 054250a37db4
434 o 0 public a-A - 054250a37db4
435
435
436
436
437 Push
437 Push
438 ````
438 ````
439
439
440 (inserted)
440 (inserted)
441
441
442 Test that phase are pushed even when they are nothing to pus
442 Test that phase are pushed even when they are nothing to pus
443 (this might be tested later bu are very convenient to not alter too much test)
443 (this might be tested later bu are very convenient to not alter too much test)
444
444
445 Push back to alpha
445 Push back to alpha
446
446
447 $ hg push ../alpha # from nu
447 $ hg push ../alpha # from nu
448 pushing to ../alpha
448 pushing to ../alpha
449 searching for changes
449 searching for changes
450 no changes found
450 no changes found
451 test-debug-phase: move rev 5: 1 -> 0
451 test-debug-phase: move rev 5: 1 -> 0
452 test-debug-phase: move rev 6: 1 -> 0
452 test-debug-phase: move rev 6: 1 -> 0
453 [1]
453 [1]
454 $ cd ..
454 $ cd ..
455 $ cd alpha
455 $ cd alpha
456 $ hgph
456 $ hgph
457 o 6 public n-B - 145e75495359
457 o 6 public n-B - 145e75495359
458 |
458 |
459 o 5 public n-A - d6bcb4f74035
459 o 5 public n-A - d6bcb4f74035
460 |
460 |
461 o 4 public b-A - f54f1bb90ff3
461 o 4 public b-A - f54f1bb90ff3
462 |
462 |
463 | @ 3 public a-D - b555f63b6063
463 | @ 3 public a-D - b555f63b6063
464 | |
464 | |
465 | o 2 public a-C - 54acac6f23ab
465 | o 2 public a-C - 54acac6f23ab
466 |/
466 |/
467 o 1 public a-B - 548a3d25dbf0
467 o 1 public a-B - 548a3d25dbf0
468 |
468 |
469 o 0 public a-A - 054250a37db4
469 o 0 public a-A - 054250a37db4
470
470
471
471
472 (end insertion)
472 (end insertion)
473
473
474
474
475 initial setup
475 initial setup
476
476
477 $ hg log -G # of alpha
477 $ hg log -G # of alpha
478 o changeset: 6:145e75495359
478 o changeset: 6:145e75495359
479 | tag: tip
479 | tag: tip
480 | user: test
480 | user: test
481 | date: Thu Jan 01 00:00:00 1970 +0000
481 | date: Thu Jan 01 00:00:00 1970 +0000
482 | summary: n-B
482 | summary: n-B
483 |
483 |
484 o changeset: 5:d6bcb4f74035
484 o changeset: 5:d6bcb4f74035
485 | user: test
485 | user: test
486 | date: Thu Jan 01 00:00:00 1970 +0000
486 | date: Thu Jan 01 00:00:00 1970 +0000
487 | summary: n-A
487 | summary: n-A
488 |
488 |
489 o changeset: 4:f54f1bb90ff3
489 o changeset: 4:f54f1bb90ff3
490 | parent: 1:548a3d25dbf0
490 | parent: 1:548a3d25dbf0
491 | user: test
491 | user: test
492 | date: Thu Jan 01 00:00:00 1970 +0000
492 | date: Thu Jan 01 00:00:00 1970 +0000
493 | summary: b-A
493 | summary: b-A
494 |
494 |
495 | @ changeset: 3:b555f63b6063
495 | @ changeset: 3:b555f63b6063
496 | | user: test
496 | | user: test
497 | | date: Thu Jan 01 00:00:00 1970 +0000
497 | | date: Thu Jan 01 00:00:00 1970 +0000
498 | | summary: a-D
498 | | summary: a-D
499 | |
499 | |
500 | o changeset: 2:54acac6f23ab
500 | o changeset: 2:54acac6f23ab
501 |/ user: test
501 |/ user: test
502 | date: Thu Jan 01 00:00:00 1970 +0000
502 | date: Thu Jan 01 00:00:00 1970 +0000
503 | summary: a-C
503 | summary: a-C
504 |
504 |
505 o changeset: 1:548a3d25dbf0
505 o changeset: 1:548a3d25dbf0
506 | user: test
506 | user: test
507 | date: Thu Jan 01 00:00:00 1970 +0000
507 | date: Thu Jan 01 00:00:00 1970 +0000
508 | summary: a-B
508 | summary: a-B
509 |
509 |
510 o changeset: 0:054250a37db4
510 o changeset: 0:054250a37db4
511 user: test
511 user: test
512 date: Thu Jan 01 00:00:00 1970 +0000
512 date: Thu Jan 01 00:00:00 1970 +0000
513 summary: a-A
513 summary: a-A
514
514
515 $ mkcommit a-E
515 $ mkcommit a-E
516 test-debug-phase: new rev 7: x -> 1
516 test-debug-phase: new rev 7: x -> 1
517 $ mkcommit a-F
517 $ mkcommit a-F
518 test-debug-phase: new rev 8: x -> 1
518 test-debug-phase: new rev 8: x -> 1
519 $ mkcommit a-G
519 $ mkcommit a-G
520 test-debug-phase: new rev 9: x -> 1
520 test-debug-phase: new rev 9: x -> 1
521 $ hg up d6bcb4f74035 -q
521 $ hg up d6bcb4f74035 -q
522 $ mkcommit a-H
522 $ mkcommit a-H
523 test-debug-phase: new rev 10: x -> 1
523 test-debug-phase: new rev 10: x -> 1
524 created new head
524 created new head
525 $ hgph
525 $ hgph
526 @ 10 draft a-H - 967b449fbc94
526 @ 10 draft a-H - 967b449fbc94
527 |
527 |
528 | o 9 draft a-G - 3e27b6f1eee1
528 | o 9 draft a-G - 3e27b6f1eee1
529 | |
529 | |
530 | o 8 draft a-F - b740e3e5c05d
530 | o 8 draft a-F - b740e3e5c05d
531 | |
531 | |
532 | o 7 draft a-E - e9f537e46dea
532 | o 7 draft a-E - e9f537e46dea
533 | |
533 | |
534 +---o 6 public n-B - 145e75495359
534 +---o 6 public n-B - 145e75495359
535 | |
535 | |
536 o | 5 public n-A - d6bcb4f74035
536 o | 5 public n-A - d6bcb4f74035
537 | |
537 | |
538 o | 4 public b-A - f54f1bb90ff3
538 o | 4 public b-A - f54f1bb90ff3
539 | |
539 | |
540 | o 3 public a-D - b555f63b6063
540 | o 3 public a-D - b555f63b6063
541 | |
541 | |
542 | o 2 public a-C - 54acac6f23ab
542 | o 2 public a-C - 54acac6f23ab
543 |/
543 |/
544 o 1 public a-B - 548a3d25dbf0
544 o 1 public a-B - 548a3d25dbf0
545 |
545 |
546 o 0 public a-A - 054250a37db4
546 o 0 public a-A - 054250a37db4
547
547
548
548
549 Pulling from bundle does not alter phases of changeset not present in the bundle
549 Pulling from bundle does not alter phases of changeset not present in the bundle
550
550
551 #if repobundlerepo
551 #if repobundlerepo
552 $ hg bundle --base 1 -r 6 -r 3 ../partial-bundle.hg
552 $ hg bundle --base 1 -r 6 -r 3 ../partial-bundle.hg
553 5 changesets found
553 5 changesets found
554 $ hg pull ../partial-bundle.hg
554 $ hg pull ../partial-bundle.hg
555 pulling from ../partial-bundle.hg
555 pulling from ../partial-bundle.hg
556 searching for changes
556 searching for changes
557 no changes found
557 no changes found
558 $ hgph
558 $ hgph
559 @ 10 draft a-H - 967b449fbc94
559 @ 10 draft a-H - 967b449fbc94
560 |
560 |
561 | o 9 draft a-G - 3e27b6f1eee1
561 | o 9 draft a-G - 3e27b6f1eee1
562 | |
562 | |
563 | o 8 draft a-F - b740e3e5c05d
563 | o 8 draft a-F - b740e3e5c05d
564 | |
564 | |
565 | o 7 draft a-E - e9f537e46dea
565 | o 7 draft a-E - e9f537e46dea
566 | |
566 | |
567 +---o 6 public n-B - 145e75495359
567 +---o 6 public n-B - 145e75495359
568 | |
568 | |
569 o | 5 public n-A - d6bcb4f74035
569 o | 5 public n-A - d6bcb4f74035
570 | |
570 | |
571 o | 4 public b-A - f54f1bb90ff3
571 o | 4 public b-A - f54f1bb90ff3
572 | |
572 | |
573 | o 3 public a-D - b555f63b6063
573 | o 3 public a-D - b555f63b6063
574 | |
574 | |
575 | o 2 public a-C - 54acac6f23ab
575 | o 2 public a-C - 54acac6f23ab
576 |/
576 |/
577 o 1 public a-B - 548a3d25dbf0
577 o 1 public a-B - 548a3d25dbf0
578 |
578 |
579 o 0 public a-A - 054250a37db4
579 o 0 public a-A - 054250a37db4
580
580
581 #endif
581 #endif
582
582
583 Pushing to Publish=False (unknown changeset)
583 Pushing to Publish=False (unknown changeset)
584
584
585 $ hg push ../mu -r b740e3e5c05d # a-F
585 $ hg push ../mu -r b740e3e5c05d # a-F
586 pushing to ../mu
586 pushing to ../mu
587 searching for changes
587 searching for changes
588 adding changesets
588 adding changesets
589 adding manifests
589 adding manifests
590 adding file changes
590 adding file changes
591 added 2 changesets with 2 changes to 2 files
591 added 2 changesets with 2 changes to 2 files
592 test-debug-phase: new rev 7: x -> 1
592 test-debug-phase: new rev 7: x -> 1
593 test-debug-phase: new rev 8: x -> 1
593 test-debug-phase: new rev 8: x -> 1
594 $ hgph
594 $ hgph
595 @ 10 draft a-H - 967b449fbc94
595 @ 10 draft a-H - 967b449fbc94
596 |
596 |
597 | o 9 draft a-G - 3e27b6f1eee1
597 | o 9 draft a-G - 3e27b6f1eee1
598 | |
598 | |
599 | o 8 draft a-F - b740e3e5c05d
599 | o 8 draft a-F - b740e3e5c05d
600 | |
600 | |
601 | o 7 draft a-E - e9f537e46dea
601 | o 7 draft a-E - e9f537e46dea
602 | |
602 | |
603 +---o 6 public n-B - 145e75495359
603 +---o 6 public n-B - 145e75495359
604 | |
604 | |
605 o | 5 public n-A - d6bcb4f74035
605 o | 5 public n-A - d6bcb4f74035
606 | |
606 | |
607 o | 4 public b-A - f54f1bb90ff3
607 o | 4 public b-A - f54f1bb90ff3
608 | |
608 | |
609 | o 3 public a-D - b555f63b6063
609 | o 3 public a-D - b555f63b6063
610 | |
610 | |
611 | o 2 public a-C - 54acac6f23ab
611 | o 2 public a-C - 54acac6f23ab
612 |/
612 |/
613 o 1 public a-B - 548a3d25dbf0
613 o 1 public a-B - 548a3d25dbf0
614 |
614 |
615 o 0 public a-A - 054250a37db4
615 o 0 public a-A - 054250a37db4
616
616
617
617
618 $ cd ../mu
618 $ cd ../mu
619 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
619 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
620 > # not ancestor of -r
620 > # not ancestor of -r
621 o 8 draft a-F - b740e3e5c05d
621 o 8 draft a-F - b740e3e5c05d
622 |
622 |
623 o 7 draft a-E - e9f537e46dea
623 o 7 draft a-E - e9f537e46dea
624 |
624 |
625 | o 6 draft n-B - 145e75495359
625 | o 6 draft n-B - 145e75495359
626 | |
626 | |
627 | o 5 draft n-A - d6bcb4f74035
627 | o 5 draft n-A - d6bcb4f74035
628 | |
628 | |
629 o | 4 public a-D - b555f63b6063
629 o | 4 public a-D - b555f63b6063
630 | |
630 | |
631 o | 3 public a-C - 54acac6f23ab
631 o | 3 public a-C - 54acac6f23ab
632 | |
632 | |
633 | o 2 draft b-A - f54f1bb90ff3
633 | o 2 draft b-A - f54f1bb90ff3
634 |/
634 |/
635 o 1 public a-B - 548a3d25dbf0
635 o 1 public a-B - 548a3d25dbf0
636 |
636 |
637 o 0 public a-A - 054250a37db4
637 o 0 public a-A - 054250a37db4
638
638
639
639
640 Pushing to Publish=True (unknown changeset)
640 Pushing to Publish=True (unknown changeset)
641
641
642 $ hg push ../beta -r b740e3e5c05d
642 $ hg push ../beta -r b740e3e5c05d
643 pushing to ../beta
643 pushing to ../beta
644 searching for changes
644 searching for changes
645 adding changesets
645 adding changesets
646 adding manifests
646 adding manifests
647 adding file changes
647 adding file changes
648 added 2 changesets with 2 changes to 2 files
648 added 2 changesets with 2 changes to 2 files
649 test-debug-phase: new rev 5: x -> 0
649 test-debug-phase: new rev 5: x -> 0
650 test-debug-phase: new rev 6: x -> 0
650 test-debug-phase: new rev 6: x -> 0
651 test-debug-phase: move rev 7: 1 -> 0
651 test-debug-phase: move rev 7: 1 -> 0
652 test-debug-phase: move rev 8: 1 -> 0
652 test-debug-phase: move rev 8: 1 -> 0
653 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
653 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
654 > # not ancestor of -r
654 > # not ancestor of -r
655 o 8 public a-F - b740e3e5c05d
655 o 8 public a-F - b740e3e5c05d
656 |
656 |
657 o 7 public a-E - e9f537e46dea
657 o 7 public a-E - e9f537e46dea
658 |
658 |
659 | o 6 draft n-B - 145e75495359
659 | o 6 draft n-B - 145e75495359
660 | |
660 | |
661 | o 5 draft n-A - d6bcb4f74035
661 | o 5 draft n-A - d6bcb4f74035
662 | |
662 | |
663 o | 4 public a-D - b555f63b6063
663 o | 4 public a-D - b555f63b6063
664 | |
664 | |
665 o | 3 public a-C - 54acac6f23ab
665 o | 3 public a-C - 54acac6f23ab
666 | |
666 | |
667 | o 2 draft b-A - f54f1bb90ff3
667 | o 2 draft b-A - f54f1bb90ff3
668 |/
668 |/
669 o 1 public a-B - 548a3d25dbf0
669 o 1 public a-B - 548a3d25dbf0
670 |
670 |
671 o 0 public a-A - 054250a37db4
671 o 0 public a-A - 054250a37db4
672
672
673
673
674 Pushing to Publish=True (common changeset)
674 Pushing to Publish=True (common changeset)
675
675
676 $ cd ../beta
676 $ cd ../beta
677 $ hg push ../alpha
677 $ hg push ../alpha
678 pushing to ../alpha
678 pushing to ../alpha
679 searching for changes
679 searching for changes
680 no changes found
680 no changes found
681 test-debug-phase: move rev 7: 1 -> 0
681 test-debug-phase: move rev 7: 1 -> 0
682 test-debug-phase: move rev 8: 1 -> 0
682 test-debug-phase: move rev 8: 1 -> 0
683 [1]
683 [1]
684 $ hgph
684 $ hgph
685 o 6 public a-F - b740e3e5c05d
685 o 6 public a-F - b740e3e5c05d
686 |
686 |
687 o 5 public a-E - e9f537e46dea
687 o 5 public a-E - e9f537e46dea
688 |
688 |
689 o 4 public a-D - b555f63b6063
689 o 4 public a-D - b555f63b6063
690 |
690 |
691 o 3 public a-C - 54acac6f23ab
691 o 3 public a-C - 54acac6f23ab
692 |
692 |
693 | @ 2 public b-A - f54f1bb90ff3
693 | @ 2 public b-A - f54f1bb90ff3
694 |/
694 |/
695 o 1 public a-B - 548a3d25dbf0
695 o 1 public a-B - 548a3d25dbf0
696 |
696 |
697 o 0 public a-A - 054250a37db4
697 o 0 public a-A - 054250a37db4
698
698
699 $ cd ../alpha
699 $ cd ../alpha
700 $ hgph
700 $ hgph
701 @ 10 draft a-H - 967b449fbc94
701 @ 10 draft a-H - 967b449fbc94
702 |
702 |
703 | o 9 draft a-G - 3e27b6f1eee1
703 | o 9 draft a-G - 3e27b6f1eee1
704 | |
704 | |
705 | o 8 public a-F - b740e3e5c05d
705 | o 8 public a-F - b740e3e5c05d
706 | |
706 | |
707 | o 7 public a-E - e9f537e46dea
707 | o 7 public a-E - e9f537e46dea
708 | |
708 | |
709 +---o 6 public n-B - 145e75495359
709 +---o 6 public n-B - 145e75495359
710 | |
710 | |
711 o | 5 public n-A - d6bcb4f74035
711 o | 5 public n-A - d6bcb4f74035
712 | |
712 | |
713 o | 4 public b-A - f54f1bb90ff3
713 o | 4 public b-A - f54f1bb90ff3
714 | |
714 | |
715 | o 3 public a-D - b555f63b6063
715 | o 3 public a-D - b555f63b6063
716 | |
716 | |
717 | o 2 public a-C - 54acac6f23ab
717 | o 2 public a-C - 54acac6f23ab
718 |/
718 |/
719 o 1 public a-B - 548a3d25dbf0
719 o 1 public a-B - 548a3d25dbf0
720 |
720 |
721 o 0 public a-A - 054250a37db4
721 o 0 public a-A - 054250a37db4
722
722
723
723
724 Pushing to Publish=False (common changeset that change phase + unknown one)
724 Pushing to Publish=False (common changeset that change phase + unknown one)
725
725
726 $ hg push ../mu -r 967b449fbc94 -f
726 $ hg push ../mu -r 967b449fbc94 -f
727 pushing to ../mu
727 pushing to ../mu
728 searching for changes
728 searching for changes
729 adding changesets
729 adding changesets
730 adding manifests
730 adding manifests
731 adding file changes
731 adding file changes
732 added 1 changesets with 1 changes to 1 files (+1 heads)
732 added 1 changesets with 1 changes to 1 files (+1 heads)
733 test-debug-phase: move rev 2: 1 -> 0
733 test-debug-phase: move rev 2: 1 -> 0
734 test-debug-phase: move rev 5: 1 -> 0
734 test-debug-phase: move rev 5: 1 -> 0
735 test-debug-phase: new rev 9: x -> 1
735 test-debug-phase: new rev 9: x -> 1
736 $ hgph
736 $ hgph
737 @ 10 draft a-H - 967b449fbc94
737 @ 10 draft a-H - 967b449fbc94
738 |
738 |
739 | o 9 draft a-G - 3e27b6f1eee1
739 | o 9 draft a-G - 3e27b6f1eee1
740 | |
740 | |
741 | o 8 public a-F - b740e3e5c05d
741 | o 8 public a-F - b740e3e5c05d
742 | |
742 | |
743 | o 7 public a-E - e9f537e46dea
743 | o 7 public a-E - e9f537e46dea
744 | |
744 | |
745 +---o 6 public n-B - 145e75495359
745 +---o 6 public n-B - 145e75495359
746 | |
746 | |
747 o | 5 public n-A - d6bcb4f74035
747 o | 5 public n-A - d6bcb4f74035
748 | |
748 | |
749 o | 4 public b-A - f54f1bb90ff3
749 o | 4 public b-A - f54f1bb90ff3
750 | |
750 | |
751 | o 3 public a-D - b555f63b6063
751 | o 3 public a-D - b555f63b6063
752 | |
752 | |
753 | o 2 public a-C - 54acac6f23ab
753 | o 2 public a-C - 54acac6f23ab
754 |/
754 |/
755 o 1 public a-B - 548a3d25dbf0
755 o 1 public a-B - 548a3d25dbf0
756 |
756 |
757 o 0 public a-A - 054250a37db4
757 o 0 public a-A - 054250a37db4
758
758
759 $ cd ../mu
759 $ cd ../mu
760 $ hgph # d6bcb4f74035 should have changed phase
760 $ hgph # d6bcb4f74035 should have changed phase
761 > # 145e75495359 is still draft. not ancestor of -r
761 > # 145e75495359 is still draft. not ancestor of -r
762 o 9 draft a-H - 967b449fbc94
762 o 9 draft a-H - 967b449fbc94
763 |
763 |
764 | o 8 public a-F - b740e3e5c05d
764 | o 8 public a-F - b740e3e5c05d
765 | |
765 | |
766 | o 7 public a-E - e9f537e46dea
766 | o 7 public a-E - e9f537e46dea
767 | |
767 | |
768 +---o 6 draft n-B - 145e75495359
768 +---o 6 draft n-B - 145e75495359
769 | |
769 | |
770 o | 5 public n-A - d6bcb4f74035
770 o | 5 public n-A - d6bcb4f74035
771 | |
771 | |
772 | o 4 public a-D - b555f63b6063
772 | o 4 public a-D - b555f63b6063
773 | |
773 | |
774 | o 3 public a-C - 54acac6f23ab
774 | o 3 public a-C - 54acac6f23ab
775 | |
775 | |
776 o | 2 public b-A - f54f1bb90ff3
776 o | 2 public b-A - f54f1bb90ff3
777 |/
777 |/
778 o 1 public a-B - 548a3d25dbf0
778 o 1 public a-B - 548a3d25dbf0
779 |
779 |
780 o 0 public a-A - 054250a37db4
780 o 0 public a-A - 054250a37db4
781
781
782
782
783
783
784 Pushing to Publish=True (common changeset from publish=False)
784 Pushing to Publish=True (common changeset from publish=False)
785
785
786 (in mu)
786 (in mu)
787 $ hg push ../alpha
787 $ hg push ../alpha
788 pushing to ../alpha
788 pushing to ../alpha
789 searching for changes
789 searching for changes
790 no changes found
790 no changes found
791 test-debug-phase: move rev 10: 1 -> 0
791 test-debug-phase: move rev 10: 1 -> 0
792 test-debug-phase: move rev 6: 1 -> 0
792 test-debug-phase: move rev 6: 1 -> 0
793 test-debug-phase: move rev 9: 1 -> 0
793 test-debug-phase: move rev 9: 1 -> 0
794 [1]
794 [1]
795 $ hgph
795 $ hgph
796 o 9 public a-H - 967b449fbc94
796 o 9 public a-H - 967b449fbc94
797 |
797 |
798 | o 8 public a-F - b740e3e5c05d
798 | o 8 public a-F - b740e3e5c05d
799 | |
799 | |
800 | o 7 public a-E - e9f537e46dea
800 | o 7 public a-E - e9f537e46dea
801 | |
801 | |
802 +---o 6 public n-B - 145e75495359
802 +---o 6 public n-B - 145e75495359
803 | |
803 | |
804 o | 5 public n-A - d6bcb4f74035
804 o | 5 public n-A - d6bcb4f74035
805 | |
805 | |
806 | o 4 public a-D - b555f63b6063
806 | o 4 public a-D - b555f63b6063
807 | |
807 | |
808 | o 3 public a-C - 54acac6f23ab
808 | o 3 public a-C - 54acac6f23ab
809 | |
809 | |
810 o | 2 public b-A - f54f1bb90ff3
810 o | 2 public b-A - f54f1bb90ff3
811 |/
811 |/
812 o 1 public a-B - 548a3d25dbf0
812 o 1 public a-B - 548a3d25dbf0
813 |
813 |
814 o 0 public a-A - 054250a37db4
814 o 0 public a-A - 054250a37db4
815
815
816 $ hgph -R ../alpha # a-H should have been synced to 0
816 $ hgph -R ../alpha # a-H should have been synced to 0
817 @ 10 public a-H - 967b449fbc94
817 @ 10 public a-H - 967b449fbc94
818 |
818 |
819 | o 9 draft a-G - 3e27b6f1eee1
819 | o 9 draft a-G - 3e27b6f1eee1
820 | |
820 | |
821 | o 8 public a-F - b740e3e5c05d
821 | o 8 public a-F - b740e3e5c05d
822 | |
822 | |
823 | o 7 public a-E - e9f537e46dea
823 | o 7 public a-E - e9f537e46dea
824 | |
824 | |
825 +---o 6 public n-B - 145e75495359
825 +---o 6 public n-B - 145e75495359
826 | |
826 | |
827 o | 5 public n-A - d6bcb4f74035
827 o | 5 public n-A - d6bcb4f74035
828 | |
828 | |
829 o | 4 public b-A - f54f1bb90ff3
829 o | 4 public b-A - f54f1bb90ff3
830 | |
830 | |
831 | o 3 public a-D - b555f63b6063
831 | o 3 public a-D - b555f63b6063
832 | |
832 | |
833 | o 2 public a-C - 54acac6f23ab
833 | o 2 public a-C - 54acac6f23ab
834 |/
834 |/
835 o 1 public a-B - 548a3d25dbf0
835 o 1 public a-B - 548a3d25dbf0
836 |
836 |
837 o 0 public a-A - 054250a37db4
837 o 0 public a-A - 054250a37db4
838
838
839
839
840
840
841 Bare push with next changeset and common changeset needing sync (issue3575)
841 Bare push with next changeset and common changeset needing sync (issue3575)
842
842
843 (reset some stat on remote repo to avoid confusing other tests)
843 (reset some stat on remote repo to avoid confusing other tests)
844
844
845 $ hg -R ../alpha --config extensions.strip= strip --no-backup 967b449fbc94
845 $ hg -R ../alpha --config extensions.strip= strip --no-backup 967b449fbc94
846 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
846 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
847 $ hg phase --force --draft b740e3e5c05d 967b449fbc94
847 $ hg phase --force --draft b740e3e5c05d 967b449fbc94
848 test-debug-phase: move rev 8: 0 -> 1
848 test-debug-phase: move rev 8: 0 -> 1
849 test-debug-phase: move rev 9: 0 -> 1
849 test-debug-phase: move rev 9: 0 -> 1
850 $ hg push -fv ../alpha
850 $ hg push -fv ../alpha
851 pushing to ../alpha
851 pushing to ../alpha
852 searching for changes
852 searching for changes
853 1 changesets found
853 1 changesets found
854 uncompressed size of bundle content:
854 uncompressed size of bundle content:
855 178 (changelog)
855 178 (changelog)
856 165 (manifests)
856 165 (manifests)
857 131 a-H
857 131 a-H
858 adding changesets
858 adding changesets
859 adding manifests
859 adding manifests
860 adding file changes
860 adding file changes
861 added 1 changesets with 1 changes to 1 files (+1 heads)
861 added 1 changesets with 1 changes to 1 files (+1 heads)
862 test-debug-phase: new rev 10: x -> 0
862 test-debug-phase: new rev 10: x -> 0
863 test-debug-phase: move rev 8: 1 -> 0
863 test-debug-phase: move rev 8: 1 -> 0
864 test-debug-phase: move rev 9: 1 -> 0
864 test-debug-phase: move rev 9: 1 -> 0
865 $ hgph
865 $ hgph
866 o 9 public a-H - 967b449fbc94
866 o 9 public a-H - 967b449fbc94
867 |
867 |
868 | o 8 public a-F - b740e3e5c05d
868 | o 8 public a-F - b740e3e5c05d
869 | |
869 | |
870 | o 7 public a-E - e9f537e46dea
870 | o 7 public a-E - e9f537e46dea
871 | |
871 | |
872 +---o 6 public n-B - 145e75495359
872 +---o 6 public n-B - 145e75495359
873 | |
873 | |
874 o | 5 public n-A - d6bcb4f74035
874 o | 5 public n-A - d6bcb4f74035
875 | |
875 | |
876 | o 4 public a-D - b555f63b6063
876 | o 4 public a-D - b555f63b6063
877 | |
877 | |
878 | o 3 public a-C - 54acac6f23ab
878 | o 3 public a-C - 54acac6f23ab
879 | |
879 | |
880 o | 2 public b-A - f54f1bb90ff3
880 o | 2 public b-A - f54f1bb90ff3
881 |/
881 |/
882 o 1 public a-B - 548a3d25dbf0
882 o 1 public a-B - 548a3d25dbf0
883 |
883 |
884 o 0 public a-A - 054250a37db4
884 o 0 public a-A - 054250a37db4
885
885
886
886
887 $ hg -R ../alpha update 967b449fbc94 #for latter test consistency
887 $ hg -R ../alpha update 967b449fbc94 #for latter test consistency
888 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
888 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
889 $ hgph -R ../alpha
889 $ hgph -R ../alpha
890 @ 10 public a-H - 967b449fbc94
890 @ 10 public a-H - 967b449fbc94
891 |
891 |
892 | o 9 draft a-G - 3e27b6f1eee1
892 | o 9 draft a-G - 3e27b6f1eee1
893 | |
893 | |
894 | o 8 public a-F - b740e3e5c05d
894 | o 8 public a-F - b740e3e5c05d
895 | |
895 | |
896 | o 7 public a-E - e9f537e46dea
896 | o 7 public a-E - e9f537e46dea
897 | |
897 | |
898 +---o 6 public n-B - 145e75495359
898 +---o 6 public n-B - 145e75495359
899 | |
899 | |
900 o | 5 public n-A - d6bcb4f74035
900 o | 5 public n-A - d6bcb4f74035
901 | |
901 | |
902 o | 4 public b-A - f54f1bb90ff3
902 o | 4 public b-A - f54f1bb90ff3
903 | |
903 | |
904 | o 3 public a-D - b555f63b6063
904 | o 3 public a-D - b555f63b6063
905 | |
905 | |
906 | o 2 public a-C - 54acac6f23ab
906 | o 2 public a-C - 54acac6f23ab
907 |/
907 |/
908 o 1 public a-B - 548a3d25dbf0
908 o 1 public a-B - 548a3d25dbf0
909 |
909 |
910 o 0 public a-A - 054250a37db4
910 o 0 public a-A - 054250a37db4
911
911
912
912
913 Discovery locally secret changeset on a remote repository:
913 Discovery locally secret changeset on a remote repository:
914
914
915 - should make it non-secret
915 - should make it non-secret
916
916
917 $ cd ../alpha
917 $ cd ../alpha
918 $ mkcommit A-secret --config phases.new-commit=2
918 $ mkcommit A-secret --config phases.new-commit=2
919 test-debug-phase: new rev 11: x -> 2
919 test-debug-phase: new rev 11: x -> 2
920 $ hgph
920 $ hgph
921 @ 11 secret A-secret - 435b5d83910c
921 @ 11 secret A-secret - 435b5d83910c
922 |
922 |
923 o 10 public a-H - 967b449fbc94
923 o 10 public a-H - 967b449fbc94
924 |
924 |
925 | o 9 draft a-G - 3e27b6f1eee1
925 | o 9 draft a-G - 3e27b6f1eee1
926 | |
926 | |
927 | o 8 public a-F - b740e3e5c05d
927 | o 8 public a-F - b740e3e5c05d
928 | |
928 | |
929 | o 7 public a-E - e9f537e46dea
929 | o 7 public a-E - e9f537e46dea
930 | |
930 | |
931 +---o 6 public n-B - 145e75495359
931 +---o 6 public n-B - 145e75495359
932 | |
932 | |
933 o | 5 public n-A - d6bcb4f74035
933 o | 5 public n-A - d6bcb4f74035
934 | |
934 | |
935 o | 4 public b-A - f54f1bb90ff3
935 o | 4 public b-A - f54f1bb90ff3
936 | |
936 | |
937 | o 3 public a-D - b555f63b6063
937 | o 3 public a-D - b555f63b6063
938 | |
938 | |
939 | o 2 public a-C - 54acac6f23ab
939 | o 2 public a-C - 54acac6f23ab
940 |/
940 |/
941 o 1 public a-B - 548a3d25dbf0
941 o 1 public a-B - 548a3d25dbf0
942 |
942 |
943 o 0 public a-A - 054250a37db4
943 o 0 public a-A - 054250a37db4
944
944
945 $ hg bundle --base 'parents(.)' -r . ../secret-bundle.hg
945 $ hg bundle --base 'parents(.)' -r . ../secret-bundle.hg
946 1 changesets found
946 1 changesets found
947 $ hg -R ../mu unbundle ../secret-bundle.hg
947 $ hg -R ../mu unbundle ../secret-bundle.hg
948 adding changesets
948 adding changesets
949 adding manifests
949 adding manifests
950 adding file changes
950 adding file changes
951 added 1 changesets with 1 changes to 1 files
951 added 1 changesets with 1 changes to 1 files
952 new changesets 435b5d83910c (1 drafts)
952 new changesets 435b5d83910c (1 drafts)
953 test-debug-phase: new rev 10: x -> 1
953 test-debug-phase: new rev 10: x -> 1
954 (run 'hg update' to get a working copy)
954 (run 'hg update' to get a working copy)
955 $ hgph -R ../mu
955 $ hgph -R ../mu
956 o 10 draft A-secret - 435b5d83910c
956 o 10 draft A-secret - 435b5d83910c
957 |
957 |
958 o 9 public a-H - 967b449fbc94
958 o 9 public a-H - 967b449fbc94
959 |
959 |
960 | o 8 public a-F - b740e3e5c05d
960 | o 8 public a-F - b740e3e5c05d
961 | |
961 | |
962 | o 7 public a-E - e9f537e46dea
962 | o 7 public a-E - e9f537e46dea
963 | |
963 | |
964 +---o 6 public n-B - 145e75495359
964 +---o 6 public n-B - 145e75495359
965 | |
965 | |
966 o | 5 public n-A - d6bcb4f74035
966 o | 5 public n-A - d6bcb4f74035
967 | |
967 | |
968 | o 4 public a-D - b555f63b6063
968 | o 4 public a-D - b555f63b6063
969 | |
969 | |
970 | o 3 public a-C - 54acac6f23ab
970 | o 3 public a-C - 54acac6f23ab
971 | |
971 | |
972 o | 2 public b-A - f54f1bb90ff3
972 o | 2 public b-A - f54f1bb90ff3
973 |/
973 |/
974 o 1 public a-B - 548a3d25dbf0
974 o 1 public a-B - 548a3d25dbf0
975 |
975 |
976 o 0 public a-A - 054250a37db4
976 o 0 public a-A - 054250a37db4
977
977
978 $ hg pull ../mu
978 $ hg pull ../mu
979 pulling from ../mu
979 pulling from ../mu
980 searching for changes
980 searching for changes
981 no changes found
981 no changes found
982 test-debug-phase: move rev 11: 2 -> 1
982 test-debug-phase: move rev 11: 2 -> 1
983 $ hgph
983 $ hgph
984 @ 11 draft A-secret - 435b5d83910c
984 @ 11 draft A-secret - 435b5d83910c
985 |
985 |
986 o 10 public a-H - 967b449fbc94
986 o 10 public a-H - 967b449fbc94
987 |
987 |
988 | o 9 draft a-G - 3e27b6f1eee1
988 | o 9 draft a-G - 3e27b6f1eee1
989 | |
989 | |
990 | o 8 public a-F - b740e3e5c05d
990 | o 8 public a-F - b740e3e5c05d
991 | |
991 | |
992 | o 7 public a-E - e9f537e46dea
992 | o 7 public a-E - e9f537e46dea
993 | |
993 | |
994 +---o 6 public n-B - 145e75495359
994 +---o 6 public n-B - 145e75495359
995 | |
995 | |
996 o | 5 public n-A - d6bcb4f74035
996 o | 5 public n-A - d6bcb4f74035
997 | |
997 | |
998 o | 4 public b-A - f54f1bb90ff3
998 o | 4 public b-A - f54f1bb90ff3
999 | |
999 | |
1000 | o 3 public a-D - b555f63b6063
1000 | o 3 public a-D - b555f63b6063
1001 | |
1001 | |
1002 | o 2 public a-C - 54acac6f23ab
1002 | o 2 public a-C - 54acac6f23ab
1003 |/
1003 |/
1004 o 1 public a-B - 548a3d25dbf0
1004 o 1 public a-B - 548a3d25dbf0
1005 |
1005 |
1006 o 0 public a-A - 054250a37db4
1006 o 0 public a-A - 054250a37db4
1007
1007
1008
1008
1009 pushing a locally public and draft changesets remotely secret should make them
1009 pushing a locally public and draft changesets remotely secret should make them
1010 appear on the remote side.
1010 appear on the remote side.
1011
1011
1012 $ hg -R ../mu phase --secret --force 967b449fbc94
1012 $ hg -R ../mu phase --secret --force 967b449fbc94
1013 test-debug-phase: move rev 9: 0 -> 2
1013 test-debug-phase: move rev 9: 0 -> 2
1014 test-debug-phase: move rev 10: 1 -> 2
1014 test-debug-phase: move rev 10: 1 -> 2
1015 $ hg push -r 435b5d83910c ../mu
1015 $ hg push -r 435b5d83910c ../mu
1016 pushing to ../mu
1016 pushing to ../mu
1017 searching for changes
1017 searching for changes
1018 abort: push creates new remote head 435b5d83910c
1018 abort: push creates new remote head 435b5d83910c
1019 (merge or see 'hg help push' for details about pushing new heads)
1019 (merge or see 'hg help push' for details about pushing new heads)
1020 [255]
1020 [255]
1021 $ hg push -fr 435b5d83910c ../mu # because the push will create new visible head
1021 $ hg push -fr 435b5d83910c ../mu # because the push will create new visible head
1022 pushing to ../mu
1022 pushing to ../mu
1023 searching for changes
1023 searching for changes
1024 adding changesets
1024 adding changesets
1025 adding manifests
1025 adding manifests
1026 adding file changes
1026 adding file changes
1027 added 0 changesets with 0 changes to 2 files
1027 added 0 changesets with 0 changes to 2 files
1028 test-debug-phase: move rev 9: 2 -> 0
1028 test-debug-phase: move rev 9: 2 -> 0
1029 test-debug-phase: move rev 10: 2 -> 1
1029 test-debug-phase: move rev 10: 2 -> 1
1030 $ hgph -R ../mu
1030 $ hgph -R ../mu
1031 o 10 draft A-secret - 435b5d83910c
1031 o 10 draft A-secret - 435b5d83910c
1032 |
1032 |
1033 o 9 public a-H - 967b449fbc94
1033 o 9 public a-H - 967b449fbc94
1034 |
1034 |
1035 | o 8 public a-F - b740e3e5c05d
1035 | o 8 public a-F - b740e3e5c05d
1036 | |
1036 | |
1037 | o 7 public a-E - e9f537e46dea
1037 | o 7 public a-E - e9f537e46dea
1038 | |
1038 | |
1039 +---o 6 public n-B - 145e75495359
1039 +---o 6 public n-B - 145e75495359
1040 | |
1040 | |
1041 o | 5 public n-A - d6bcb4f74035
1041 o | 5 public n-A - d6bcb4f74035
1042 | |
1042 | |
1043 | o 4 public a-D - b555f63b6063
1043 | o 4 public a-D - b555f63b6063
1044 | |
1044 | |
1045 | o 3 public a-C - 54acac6f23ab
1045 | o 3 public a-C - 54acac6f23ab
1046 | |
1046 | |
1047 o | 2 public b-A - f54f1bb90ff3
1047 o | 2 public b-A - f54f1bb90ff3
1048 |/
1048 |/
1049 o 1 public a-B - 548a3d25dbf0
1049 o 1 public a-B - 548a3d25dbf0
1050 |
1050 |
1051 o 0 public a-A - 054250a37db4
1051 o 0 public a-A - 054250a37db4
1052
1052
1053
1053
1054 pull new changeset with common draft locally
1054 pull new changeset with common draft locally
1055
1055
1056 $ hg up -q 967b449fbc94 # create a new root for draft
1056 $ hg up -q 967b449fbc94 # create a new root for draft
1057 $ mkcommit 'alpha-more'
1057 $ mkcommit 'alpha-more'
1058 test-debug-phase: new rev 12: x -> 1
1058 test-debug-phase: new rev 12: x -> 1
1059 created new head
1059 created new head
1060 $ hg push -fr . ../mu
1060 $ hg push -fr . ../mu
1061 pushing to ../mu
1061 pushing to ../mu
1062 searching for changes
1062 searching for changes
1063 adding changesets
1063 adding changesets
1064 adding manifests
1064 adding manifests
1065 adding file changes
1065 adding file changes
1066 added 1 changesets with 1 changes to 1 files (+1 heads)
1066 added 1 changesets with 1 changes to 1 files (+1 heads)
1067 test-debug-phase: new rev 11: x -> 1
1067 test-debug-phase: new rev 11: x -> 1
1068 $ cd ../mu
1068 $ cd ../mu
1069 $ hg phase --secret --force 1c5cfd894796
1069 $ hg phase --secret --force 1c5cfd894796
1070 test-debug-phase: move rev 11: 1 -> 2
1070 test-debug-phase: move rev 11: 1 -> 2
1071 $ hg up -q 435b5d83910c
1071 $ hg up -q 435b5d83910c
1072 $ mkcommit 'mu-more'
1072 $ mkcommit 'mu-more'
1073 test-debug-phase: new rev 12: x -> 1
1073 test-debug-phase: new rev 12: x -> 1
1074 $ cd ../alpha
1074 $ cd ../alpha
1075 $ hg pull ../mu
1075 $ hg pull ../mu
1076 pulling from ../mu
1076 pulling from ../mu
1077 searching for changes
1077 searching for changes
1078 adding changesets
1078 adding changesets
1079 adding manifests
1079 adding manifests
1080 adding file changes
1080 adding file changes
1081 added 1 changesets with 1 changes to 1 files
1081 added 1 changesets with 1 changes to 1 files
1082 new changesets 5237fb433fc8 (1 drafts)
1082 new changesets 5237fb433fc8 (1 drafts)
1083 test-debug-phase: new rev 13: x -> 1
1083 test-debug-phase: new rev 13: x -> 1
1084 (run 'hg update' to get a working copy)
1084 (run 'hg update' to get a working copy)
1085 $ hgph
1085 $ hgph
1086 o 13 draft mu-more - 5237fb433fc8
1086 o 13 draft mu-more - 5237fb433fc8
1087 |
1087 |
1088 | @ 12 draft alpha-more - 1c5cfd894796
1088 | @ 12 draft alpha-more - 1c5cfd894796
1089 | |
1089 | |
1090 o | 11 draft A-secret - 435b5d83910c
1090 o | 11 draft A-secret - 435b5d83910c
1091 |/
1091 |/
1092 o 10 public a-H - 967b449fbc94
1092 o 10 public a-H - 967b449fbc94
1093 |
1093 |
1094 | o 9 draft a-G - 3e27b6f1eee1
1094 | o 9 draft a-G - 3e27b6f1eee1
1095 | |
1095 | |
1096 | o 8 public a-F - b740e3e5c05d
1096 | o 8 public a-F - b740e3e5c05d
1097 | |
1097 | |
1098 | o 7 public a-E - e9f537e46dea
1098 | o 7 public a-E - e9f537e46dea
1099 | |
1099 | |
1100 +---o 6 public n-B - 145e75495359
1100 +---o 6 public n-B - 145e75495359
1101 | |
1101 | |
1102 o | 5 public n-A - d6bcb4f74035
1102 o | 5 public n-A - d6bcb4f74035
1103 | |
1103 | |
1104 o | 4 public b-A - f54f1bb90ff3
1104 o | 4 public b-A - f54f1bb90ff3
1105 | |
1105 | |
1106 | o 3 public a-D - b555f63b6063
1106 | o 3 public a-D - b555f63b6063
1107 | |
1107 | |
1108 | o 2 public a-C - 54acac6f23ab
1108 | o 2 public a-C - 54acac6f23ab
1109 |/
1109 |/
1110 o 1 public a-B - 548a3d25dbf0
1110 o 1 public a-B - 548a3d25dbf0
1111 |
1111 |
1112 o 0 public a-A - 054250a37db4
1112 o 0 public a-A - 054250a37db4
1113
1113
1114
1114
1115 Test that test are properly ignored on remote event when existing locally
1115 Test that test are properly ignored on remote event when existing locally
1116
1116
1117 $ cd ..
1117 $ cd ..
1118 $ hg clone -qU -r b555f63b6063 -r f54f1bb90ff3 beta gamma
1118 $ hg clone -qU -r b555f63b6063 -r f54f1bb90ff3 beta gamma
1119 test-debug-phase: new rev 0: x -> 0
1119 test-debug-phase: new rev 0: x -> 0
1120 test-debug-phase: new rev 1: x -> 0
1120 test-debug-phase: new rev 1: x -> 0
1121 test-debug-phase: new rev 2: x -> 0
1121 test-debug-phase: new rev 2: x -> 0
1122 test-debug-phase: new rev 3: x -> 0
1122 test-debug-phase: new rev 3: x -> 0
1123 test-debug-phase: new rev 4: x -> 0
1123 test-debug-phase: new rev 4: x -> 0
1124
1124
1125 # pathological case are
1125 # pathological case are
1126 #
1126 #
1127 # * secret remotely
1127 # * secret remotely
1128 # * known locally
1128 # * known locally
1129 # * repo have uncommon changeset
1129 # * repo have uncommon changeset
1130
1130
1131 $ hg -R beta phase --secret --force f54f1bb90ff3
1131 $ hg -R beta phase --secret --force f54f1bb90ff3
1132 test-debug-phase: move rev 2: 0 -> 2
1132 test-debug-phase: move rev 2: 0 -> 2
1133 $ hg -R gamma phase --draft --force f54f1bb90ff3
1133 $ hg -R gamma phase --draft --force f54f1bb90ff3
1134 test-debug-phase: move rev 2: 0 -> 1
1134 test-debug-phase: move rev 2: 0 -> 1
1135
1135
1136 $ cd gamma
1136 $ cd gamma
1137 $ hg pull ../beta
1137 $ hg pull ../beta
1138 pulling from ../beta
1138 pulling from ../beta
1139 searching for changes
1139 searching for changes
1140 adding changesets
1140 adding changesets
1141 adding manifests
1141 adding manifests
1142 adding file changes
1142 adding file changes
1143 added 2 changesets with 2 changes to 2 files
1143 added 2 changesets with 2 changes to 2 files
1144 new changesets e9f537e46dea:b740e3e5c05d
1144 new changesets e9f537e46dea:b740e3e5c05d
1145 test-debug-phase: new rev 5: x -> 0
1145 test-debug-phase: new rev 5: x -> 0
1146 test-debug-phase: new rev 6: x -> 0
1146 test-debug-phase: new rev 6: x -> 0
1147 (run 'hg update' to get a working copy)
1147 (run 'hg update' to get a working copy)
1148 $ hg phase f54f1bb90ff3
1148 $ hg phase f54f1bb90ff3
1149 2: draft
1149 2: draft
1150
1150
1151 same over the wire
1151 same over the wire
1152
1152
1153 $ cd ../beta
1153 $ cd ../beta
1154 $ hg serve -p $HGPORT -d --pid-file=../beta.pid -E ../beta-error.log
1154 $ hg serve -p $HGPORT -d --pid-file=../beta.pid -E ../beta-error.log
1155 $ cat ../beta.pid >> $DAEMON_PIDS
1155 $ cat ../beta.pid >> $DAEMON_PIDS
1156 $ cd ../gamma
1156 $ cd ../gamma
1157
1157
1158 $ hg pull http://localhost:$HGPORT/ # bundle2+
1158 $ hg pull http://localhost:$HGPORT/ # bundle2+
1159 pulling from http://localhost:$HGPORT/
1159 pulling from http://localhost:$HGPORT/
1160 searching for changes
1160 searching for changes
1161 no changes found
1161 no changes found
1162 $ hg phase f54f1bb90ff3
1162 $ hg phase f54f1bb90ff3
1163 2: draft
1163 2: draft
1164
1164
1165 enforce bundle1
1165 enforce bundle1
1166
1166
1167 $ hg pull http://localhost:$HGPORT/ --config devel.legacy.exchange=bundle1
1167 $ hg pull http://localhost:$HGPORT/ --config devel.legacy.exchange=bundle1
1168 pulling from http://localhost:$HGPORT/
1168 pulling from http://localhost:$HGPORT/
1169 searching for changes
1169 searching for changes
1170 no changes found
1170 no changes found
1171 $ hg phase f54f1bb90ff3
1171 $ hg phase f54f1bb90ff3
1172 2: draft
1172 2: draft
1173
1173
1174 check that secret local on both side are not synced to public
1174 check that secret local on both side are not synced to public
1175
1175
1176 $ hg push -r b555f63b6063 http://localhost:$HGPORT/
1176 $ hg push -r b555f63b6063 http://localhost:$HGPORT/
1177 pushing to http://localhost:$HGPORT/
1177 pushing to http://localhost:$HGPORT/
1178 searching for changes
1178 searching for changes
1179 no changes found
1179 no changes found
1180 [1]
1180 [1]
1181 $ hg phase f54f1bb90ff3
1181 $ hg phase f54f1bb90ff3
1182 2: draft
1182 2: draft
1183
1183
1184 $ killdaemons.py
1184 $ killdaemons.py
1185
1185
1186 put the changeset in the draft state again
1186 put the changeset in the draft state again
1187 (first test after this one expect to be able to copy)
1187 (first test after this one expect to be able to copy)
1188
1188
1189 $ cd ..
1189 $ cd ..
1190
1190
1191
1191
1192 Test Clone behavior
1192 Test Clone behavior
1193
1193
1194 A. Clone without secret changeset
1194 A. Clone without secret changeset
1195
1195
1196 1. cloning non-publishing repository
1196 1. cloning non-publishing repository
1197 (Phase should be preserved)
1197 (Phase should be preserved)
1198
1198
1199 # make sure there is no secret so we can use a copy clone
1199 # make sure there is no secret so we can use a copy clone
1200
1200
1201 $ hg -R mu phase --draft 'secret()'
1201 $ hg -R mu phase --draft 'secret()'
1202 test-debug-phase: move rev 11: 2 -> 1
1202 test-debug-phase: move rev 11: 2 -> 1
1203
1203
1204 $ hg clone -U mu Tau
1204 $ hg clone -U mu Tau
1205 $ hgph -R Tau
1205 $ hgph -R Tau
1206 o 12 draft mu-more - 5237fb433fc8
1206 o 12 draft mu-more - 5237fb433fc8
1207 |
1207 |
1208 | o 11 draft alpha-more - 1c5cfd894796
1208 | o 11 draft alpha-more - 1c5cfd894796
1209 | |
1209 | |
1210 o | 10 draft A-secret - 435b5d83910c
1210 o | 10 draft A-secret - 435b5d83910c
1211 |/
1211 |/
1212 o 9 public a-H - 967b449fbc94
1212 o 9 public a-H - 967b449fbc94
1213 |
1213 |
1214 | o 8 public a-F - b740e3e5c05d
1214 | o 8 public a-F - b740e3e5c05d
1215 | |
1215 | |
1216 | o 7 public a-E - e9f537e46dea
1216 | o 7 public a-E - e9f537e46dea
1217 | |
1217 | |
1218 +---o 6 public n-B - 145e75495359
1218 +---o 6 public n-B - 145e75495359
1219 | |
1219 | |
1220 o | 5 public n-A - d6bcb4f74035
1220 o | 5 public n-A - d6bcb4f74035
1221 | |
1221 | |
1222 | o 4 public a-D - b555f63b6063
1222 | o 4 public a-D - b555f63b6063
1223 | |
1223 | |
1224 | o 3 public a-C - 54acac6f23ab
1224 | o 3 public a-C - 54acac6f23ab
1225 | |
1225 | |
1226 o | 2 public b-A - f54f1bb90ff3
1226 o | 2 public b-A - f54f1bb90ff3
1227 |/
1227 |/
1228 o 1 public a-B - 548a3d25dbf0
1228 o 1 public a-B - 548a3d25dbf0
1229 |
1229 |
1230 o 0 public a-A - 054250a37db4
1230 o 0 public a-A - 054250a37db4
1231
1231
1232
1232
1233 2. cloning publishing repository
1233 2. cloning publishing repository
1234
1234
1235 (everything should be public)
1235 (everything should be public)
1236
1236
1237 $ hg clone -U alpha Upsilon
1237 $ hg clone -U alpha Upsilon
1238 $ hgph -R Upsilon
1238 $ hgph -R Upsilon
1239 o 13 public mu-more - 5237fb433fc8
1239 o 13 public mu-more - 5237fb433fc8
1240 |
1240 |
1241 | o 12 public alpha-more - 1c5cfd894796
1241 | o 12 public alpha-more - 1c5cfd894796
1242 | |
1242 | |
1243 o | 11 public A-secret - 435b5d83910c
1243 o | 11 public A-secret - 435b5d83910c
1244 |/
1244 |/
1245 o 10 public a-H - 967b449fbc94
1245 o 10 public a-H - 967b449fbc94
1246 |
1246 |
1247 | o 9 public a-G - 3e27b6f1eee1
1247 | o 9 public a-G - 3e27b6f1eee1
1248 | |
1248 | |
1249 | o 8 public a-F - b740e3e5c05d
1249 | o 8 public a-F - b740e3e5c05d
1250 | |
1250 | |
1251 | o 7 public a-E - e9f537e46dea
1251 | o 7 public a-E - e9f537e46dea
1252 | |
1252 | |
1253 +---o 6 public n-B - 145e75495359
1253 +---o 6 public n-B - 145e75495359
1254 | |
1254 | |
1255 o | 5 public n-A - d6bcb4f74035
1255 o | 5 public n-A - d6bcb4f74035
1256 | |
1256 | |
1257 o | 4 public b-A - f54f1bb90ff3
1257 o | 4 public b-A - f54f1bb90ff3
1258 | |
1258 | |
1259 | o 3 public a-D - b555f63b6063
1259 | o 3 public a-D - b555f63b6063
1260 | |
1260 | |
1261 | o 2 public a-C - 54acac6f23ab
1261 | o 2 public a-C - 54acac6f23ab
1262 |/
1262 |/
1263 o 1 public a-B - 548a3d25dbf0
1263 o 1 public a-B - 548a3d25dbf0
1264 |
1264 |
1265 o 0 public a-A - 054250a37db4
1265 o 0 public a-A - 054250a37db4
1266
1266
1267 #if unix-permissions no-root
1267 #if unix-permissions no-root
1268
1268
1269 Pushing From an unlockable repo
1269 Pushing From an unlockable repo
1270 --------------------------------
1270 --------------------------------
1271 (issue3684)
1271 (issue3684)
1272
1272
1273 Unability to lock the source repo should not prevent the push. It will prevent
1273 Unability to lock the source repo should not prevent the push. It will prevent
1274 the retrieval of remote phase during push. For example, pushing to a publishing
1274 the retrieval of remote phase during push. For example, pushing to a publishing
1275 server won't turn changeset public.
1275 server won't turn changeset public.
1276
1276
1277 1. Test that push is not prevented
1277 1. Test that push is not prevented
1278
1278
1279 $ hg init Phi
1279 $ hg init Phi
1280 $ cd Upsilon
1280 $ cd Upsilon
1281 $ chmod -R -w .hg
1281 $ chmod -R -w .hg
1282 $ hg push ../Phi
1282 $ hg push ../Phi
1283 pushing to ../Phi
1283 pushing to ../Phi
1284 searching for changes
1284 searching for changes
1285 adding changesets
1285 adding changesets
1286 adding manifests
1286 adding manifests
1287 adding file changes
1287 adding file changes
1288 added 14 changesets with 14 changes to 14 files (+3 heads)
1288 added 14 changesets with 14 changes to 14 files (+3 heads)
1289 test-debug-phase: new rev 0: x -> 0
1289 test-debug-phase: new rev 0: x -> 0
1290 test-debug-phase: new rev 1: x -> 0
1290 test-debug-phase: new rev 1: x -> 0
1291 test-debug-phase: new rev 2: x -> 0
1291 test-debug-phase: new rev 2: x -> 0
1292 test-debug-phase: new rev 3: x -> 0
1292 test-debug-phase: new rev 3: x -> 0
1293 test-debug-phase: new rev 4: x -> 0
1293 test-debug-phase: new rev 4: x -> 0
1294 test-debug-phase: new rev 5: x -> 0
1294 test-debug-phase: new rev 5: x -> 0
1295 test-debug-phase: new rev 6: x -> 0
1295 test-debug-phase: new rev 6: x -> 0
1296 test-debug-phase: new rev 7: x -> 0
1296 test-debug-phase: new rev 7: x -> 0
1297 test-debug-phase: new rev 8: x -> 0
1297 test-debug-phase: new rev 8: x -> 0
1298 test-debug-phase: new rev 9: x -> 0
1298 test-debug-phase: new rev 9: x -> 0
1299 test-debug-phase: new rev 10: x -> 0
1299 test-debug-phase: new rev 10: x -> 0
1300 test-debug-phase: new rev 11: x -> 0
1300 test-debug-phase: new rev 11: x -> 0
1301 test-debug-phase: new rev 12: x -> 0
1301 test-debug-phase: new rev 12: x -> 0
1302 test-debug-phase: new rev 13: x -> 0
1302 test-debug-phase: new rev 13: x -> 0
1303 $ chmod -R +w .hg
1303 $ chmod -R +w .hg
1304
1304
1305 2. Test that failed phases movement are reported
1305 2. Test that failed phases movement are reported
1306
1306
1307 $ hg phase --force --draft 3
1307 $ hg phase --force --draft 3
1308 test-debug-phase: move rev 3: 0 -> 1
1308 test-debug-phase: move rev 3: 0 -> 1
1309 test-debug-phase: move rev 7: 0 -> 1
1309 test-debug-phase: move rev 7: 0 -> 1
1310 test-debug-phase: move rev 8: 0 -> 1
1310 test-debug-phase: move rev 8: 0 -> 1
1311 test-debug-phase: move rev 9: 0 -> 1
1311 test-debug-phase: move rev 9: 0 -> 1
1312 $ chmod -R -w .hg
1312 $ chmod -R -w .hg
1313 $ hg push ../Phi
1313 $ hg push ../Phi
1314 pushing to ../Phi
1314 pushing to ../Phi
1315 searching for changes
1315 searching for changes
1316 no changes found
1316 no changes found
1317 cannot lock source repo, skipping local public phase update
1317 cannot lock source repo, skipping local public phase update
1318 [1]
1318 [1]
1319 $ chmod -R +w .hg
1319 $ chmod -R +w .hg
1320
1320
1321 3. Test that push is prevented if lock was already acquired (not a permission
1321 3. Test that push is prevented if lock was already acquired (not a permission
1322 error, but EEXIST)
1322 error, but EEXIST)
1323
1323
1324 $ touch .hg/store/lock
1324 $ touch .hg/store/lock
1325 $ hg push ../Phi --config ui.timeout=1
1325 $ hg push ../Phi --config ui.timeout=1 --config ui.timeout.warn=0
1326 pushing to ../Phi
1326 pushing to ../Phi
1327 waiting for lock on repository $TESTTMP/Upsilon held by ''
1327 waiting for lock on repository $TESTTMP/Upsilon held by ''
1328 abort: repository $TESTTMP/Upsilon: timed out waiting for lock held by ''
1328 abort: repository $TESTTMP/Upsilon: timed out waiting for lock held by ''
1329 (lock might be very busy)
1329 (lock might be very busy)
1330 [20]
1330 [20]
1331 $ rm .hg/store/lock
1331 $ rm .hg/store/lock
1332
1332
1333 $ cd ..
1333 $ cd ..
1334
1334
1335 #endif
1335 #endif
1336
1336
1337 Test that clone behaves like pull and doesn't publish changesets as plain push
1337 Test that clone behaves like pull and doesn't publish changesets as plain push
1338 does. The conditional output accounts for changes in the conditional block
1338 does. The conditional output accounts for changes in the conditional block
1339 above.
1339 above.
1340
1340
1341 #if unix-permissions no-root
1341 #if unix-permissions no-root
1342 $ hg -R Upsilon phase -q --force --draft 2
1342 $ hg -R Upsilon phase -q --force --draft 2
1343 test-debug-phase: move rev 2: 0 -> 1
1343 test-debug-phase: move rev 2: 0 -> 1
1344 #else
1344 #else
1345 $ hg -R Upsilon phase -q --force --draft 2
1345 $ hg -R Upsilon phase -q --force --draft 2
1346 test-debug-phase: move rev 2: 0 -> 1
1346 test-debug-phase: move rev 2: 0 -> 1
1347 test-debug-phase: move rev 3: 0 -> 1
1347 test-debug-phase: move rev 3: 0 -> 1
1348 test-debug-phase: move rev 7: 0 -> 1
1348 test-debug-phase: move rev 7: 0 -> 1
1349 test-debug-phase: move rev 8: 0 -> 1
1349 test-debug-phase: move rev 8: 0 -> 1
1350 test-debug-phase: move rev 9: 0 -> 1
1350 test-debug-phase: move rev 9: 0 -> 1
1351 #endif
1351 #endif
1352
1352
1353 $ hg clone -q Upsilon Pi -r 7
1353 $ hg clone -q Upsilon Pi -r 7
1354 test-debug-phase: new rev 0: x -> 0
1354 test-debug-phase: new rev 0: x -> 0
1355 test-debug-phase: new rev 1: x -> 0
1355 test-debug-phase: new rev 1: x -> 0
1356 test-debug-phase: new rev 2: x -> 0
1356 test-debug-phase: new rev 2: x -> 0
1357 test-debug-phase: new rev 3: x -> 0
1357 test-debug-phase: new rev 3: x -> 0
1358 test-debug-phase: new rev 4: x -> 0
1358 test-debug-phase: new rev 4: x -> 0
1359 $ hgph Upsilon -r 'min(draft())'
1359 $ hgph Upsilon -r 'min(draft())'
1360 o 2 draft a-C - 54acac6f23ab
1360 o 2 draft a-C - 54acac6f23ab
1361 |
1361 |
1362 ~
1362 ~
1363
1363
1364 $ hg -R Upsilon push Pi -r 7
1364 $ hg -R Upsilon push Pi -r 7
1365 pushing to Pi
1365 pushing to Pi
1366 searching for changes
1366 searching for changes
1367 no changes found
1367 no changes found
1368 test-debug-phase: move rev 2: 1 -> 0
1368 test-debug-phase: move rev 2: 1 -> 0
1369 test-debug-phase: move rev 3: 1 -> 0
1369 test-debug-phase: move rev 3: 1 -> 0
1370 test-debug-phase: move rev 7: 1 -> 0
1370 test-debug-phase: move rev 7: 1 -> 0
1371 [1]
1371 [1]
1372 $ hgph Upsilon -r 'min(draft())'
1372 $ hgph Upsilon -r 'min(draft())'
1373 o 8 draft a-F - b740e3e5c05d
1373 o 8 draft a-F - b740e3e5c05d
1374 |
1374 |
1375 ~
1375 ~
1376
1376
1377 $ hg -R Upsilon push Pi -r 8
1377 $ hg -R Upsilon push Pi -r 8
1378 pushing to Pi
1378 pushing to Pi
1379 searching for changes
1379 searching for changes
1380 adding changesets
1380 adding changesets
1381 adding manifests
1381 adding manifests
1382 adding file changes
1382 adding file changes
1383 added 1 changesets with 1 changes to 1 files
1383 added 1 changesets with 1 changes to 1 files
1384 test-debug-phase: new rev 5: x -> 0
1384 test-debug-phase: new rev 5: x -> 0
1385 test-debug-phase: move rev 8: 1 -> 0
1385 test-debug-phase: move rev 8: 1 -> 0
1386
1386
1387 $ hgph Upsilon -r 'min(draft())'
1387 $ hgph Upsilon -r 'min(draft())'
1388 o 9 draft a-G - 3e27b6f1eee1
1388 o 9 draft a-G - 3e27b6f1eee1
1389 |
1389 |
1390 ~
1390 ~
1391
1391
1392 Test phases exchange when a phaseroot is on a merge
1392 Test phases exchange when a phaseroot is on a merge
1393
1393
1394 $ hg init mergetest
1394 $ hg init mergetest
1395 $ cd mergetest
1395 $ cd mergetest
1396 > cat > .hg/hgrc << EOF
1396 > cat > .hg/hgrc << EOF
1397 > [phases]
1397 > [phases]
1398 > publish = false
1398 > publish = false
1399 > EOF
1399 > EOF
1400
1400
1401 $ hg debugdrawdag << EOF
1401 $ hg debugdrawdag << EOF
1402 > E Z
1402 > E Z
1403 > |\|
1403 > |\|
1404 > D Y
1404 > D Y
1405 > | |
1405 > | |
1406 > C X
1406 > C X
1407 > |/
1407 > |/
1408 > B
1408 > B
1409 > |
1409 > |
1410 > A
1410 > A
1411 > EOF
1411 > EOF
1412 test-debug-phase: new rev 0: x -> 1
1412 test-debug-phase: new rev 0: x -> 1
1413 test-debug-phase: new rev 1: x -> 1
1413 test-debug-phase: new rev 1: x -> 1
1414 test-debug-phase: new rev 2: x -> 1
1414 test-debug-phase: new rev 2: x -> 1
1415 test-debug-phase: new rev 3: x -> 1
1415 test-debug-phase: new rev 3: x -> 1
1416 test-debug-phase: new rev 4: x -> 1
1416 test-debug-phase: new rev 4: x -> 1
1417 test-debug-phase: new rev 5: x -> 1
1417 test-debug-phase: new rev 5: x -> 1
1418 test-debug-phase: new rev 6: x -> 1
1418 test-debug-phase: new rev 6: x -> 1
1419 test-debug-phase: new rev 7: x -> 1
1419 test-debug-phase: new rev 7: x -> 1
1420
1420
1421 $ hg phase --public -r D
1421 $ hg phase --public -r D
1422 test-debug-phase: move rev 0: 1 -> 0
1422 test-debug-phase: move rev 0: 1 -> 0
1423 test-debug-phase: move rev 1: 1 -> 0
1423 test-debug-phase: move rev 1: 1 -> 0
1424 test-debug-phase: move rev 2: 1 -> 0
1424 test-debug-phase: move rev 2: 1 -> 0
1425 test-debug-phase: move rev 4: 1 -> 0
1425 test-debug-phase: move rev 4: 1 -> 0
1426
1426
1427 $ hg log -G -T '{shortest(node, 5)} {phase}'
1427 $ hg log -G -T '{shortest(node, 5)} {phase}'
1428 o bb947 draft
1428 o bb947 draft
1429 |
1429 |
1430 | o 5ac28 draft
1430 | o 5ac28 draft
1431 |/|
1431 |/|
1432 o | 13b7b draft
1432 o | 13b7b draft
1433 | |
1433 | |
1434 | o f5853 public
1434 | o f5853 public
1435 | |
1435 | |
1436 o | c67c4 draft
1436 o | c67c4 draft
1437 | |
1437 | |
1438 | o 26805 public
1438 | o 26805 public
1439 |/
1439 |/
1440 o 11247 public
1440 o 11247 public
1441 |
1441 |
1442 o 426ba public
1442 o 426ba public
1443
1443
1444 $ cd ..
1444 $ cd ..
1445
1445
1446 Works with default settings
1446 Works with default settings
1447
1447
1448 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1448 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1449 $ cat hg.pid >> $DAEMON_PIDS
1449 $ cat hg.pid >> $DAEMON_PIDS
1450
1450
1451 $ hg clone -U http://localhost:$HGPORT mergetest-normal
1451 $ hg clone -U http://localhost:$HGPORT mergetest-normal
1452 requesting all changes
1452 requesting all changes
1453 adding changesets
1453 adding changesets
1454 adding manifests
1454 adding manifests
1455 adding file changes
1455 adding file changes
1456 added 8 changesets with 7 changes to 7 files (+1 heads)
1456 added 8 changesets with 7 changes to 7 files (+1 heads)
1457 new changesets 426bada5c675:bb94757e651a (4 drafts)
1457 new changesets 426bada5c675:bb94757e651a (4 drafts)
1458 test-debug-phase: new rev 0: x -> 0
1458 test-debug-phase: new rev 0: x -> 0
1459 test-debug-phase: new rev 1: x -> 0
1459 test-debug-phase: new rev 1: x -> 0
1460 test-debug-phase: new rev 2: x -> 0
1460 test-debug-phase: new rev 2: x -> 0
1461 test-debug-phase: new rev 3: x -> 1
1461 test-debug-phase: new rev 3: x -> 1
1462 test-debug-phase: new rev 4: x -> 0
1462 test-debug-phase: new rev 4: x -> 0
1463 test-debug-phase: new rev 5: x -> 1
1463 test-debug-phase: new rev 5: x -> 1
1464 test-debug-phase: new rev 6: x -> 1
1464 test-debug-phase: new rev 6: x -> 1
1465 test-debug-phase: new rev 7: x -> 1
1465 test-debug-phase: new rev 7: x -> 1
1466
1466
1467 $ hg -R mergetest-normal log -G -T '{shortest(node, 5)} {phase}'
1467 $ hg -R mergetest-normal log -G -T '{shortest(node, 5)} {phase}'
1468 o bb947 draft
1468 o bb947 draft
1469 |
1469 |
1470 | o 5ac28 draft
1470 | o 5ac28 draft
1471 |/|
1471 |/|
1472 o | 13b7b draft
1472 o | 13b7b draft
1473 | |
1473 | |
1474 | o f5853 public
1474 | o f5853 public
1475 | |
1475 | |
1476 o | c67c4 draft
1476 o | c67c4 draft
1477 | |
1477 | |
1478 | o 26805 public
1478 | o 26805 public
1479 |/
1479 |/
1480 o 11247 public
1480 o 11247 public
1481 |
1481 |
1482 o 426ba public
1482 o 426ba public
1483
1483
1484 $ killdaemons.py
1484 $ killdaemons.py
1485
1485
1486 With legacy listkeys over bundle2
1486 With legacy listkeys over bundle2
1487 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1487 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1488 of phase heads computation)
1488 of phase heads computation)
1489
1489
1490 $ hg -R mergetest --config devel.legacy.exchange=phases serve -p $HGPORT -d --pid-file=hg.pid
1490 $ hg -R mergetest --config devel.legacy.exchange=phases serve -p $HGPORT -d --pid-file=hg.pid
1491 $ cat hg.pid >> $DAEMON_PIDS
1491 $ cat hg.pid >> $DAEMON_PIDS
1492
1492
1493 $ hg clone -U http://localhost:$HGPORT mergetest-nobinarypart
1493 $ hg clone -U http://localhost:$HGPORT mergetest-nobinarypart
1494 requesting all changes
1494 requesting all changes
1495 adding changesets
1495 adding changesets
1496 adding manifests
1496 adding manifests
1497 adding file changes
1497 adding file changes
1498 added 8 changesets with 7 changes to 7 files (+1 heads)
1498 added 8 changesets with 7 changes to 7 files (+1 heads)
1499 new changesets 426bada5c675:bb94757e651a (4 drafts)
1499 new changesets 426bada5c675:bb94757e651a (4 drafts)
1500 test-debug-phase: new rev 0: x -> 0
1500 test-debug-phase: new rev 0: x -> 0
1501 test-debug-phase: new rev 1: x -> 0
1501 test-debug-phase: new rev 1: x -> 0
1502 test-debug-phase: new rev 2: x -> 0
1502 test-debug-phase: new rev 2: x -> 0
1503 test-debug-phase: new rev 3: x -> 1
1503 test-debug-phase: new rev 3: x -> 1
1504 test-debug-phase: new rev 4: x -> 0
1504 test-debug-phase: new rev 4: x -> 0
1505 test-debug-phase: new rev 5: x -> 1
1505 test-debug-phase: new rev 5: x -> 1
1506 test-debug-phase: new rev 6: x -> 1
1506 test-debug-phase: new rev 6: x -> 1
1507 test-debug-phase: new rev 7: x -> 1
1507 test-debug-phase: new rev 7: x -> 1
1508
1508
1509 $ hg -R mergetest-nobinarypart log -G -T '{shortest(node, 5)} {phase}'
1509 $ hg -R mergetest-nobinarypart log -G -T '{shortest(node, 5)} {phase}'
1510 o bb947 draft
1510 o bb947 draft
1511 |
1511 |
1512 | o 5ac28 draft
1512 | o 5ac28 draft
1513 |/|
1513 |/|
1514 o | 13b7b draft
1514 o | 13b7b draft
1515 | |
1515 | |
1516 | o f5853 public
1516 | o f5853 public
1517 | |
1517 | |
1518 o | c67c4 draft
1518 o | c67c4 draft
1519 | |
1519 | |
1520 | o 26805 public
1520 | o 26805 public
1521 |/
1521 |/
1522 o 11247 public
1522 o 11247 public
1523 |
1523 |
1524 o 426ba public
1524 o 426ba public
1525
1525
1526 $ killdaemons.py
1526 $ killdaemons.py
1527
1527
1528 Without bundle2
1528 Without bundle2
1529 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1529 (issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
1530 of phase heads computation)
1530 of phase heads computation)
1531
1531
1532 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1532 $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
1533 $ cat hg.pid >> $DAEMON_PIDS
1533 $ cat hg.pid >> $DAEMON_PIDS
1534
1534
1535 $ hg --config devel.legacy.exchange=bundle1 clone -U http://localhost:$HGPORT mergetest-bundle1
1535 $ hg --config devel.legacy.exchange=bundle1 clone -U http://localhost:$HGPORT mergetest-bundle1
1536 requesting all changes
1536 requesting all changes
1537 adding changesets
1537 adding changesets
1538 adding manifests
1538 adding manifests
1539 adding file changes
1539 adding file changes
1540 added 8 changesets with 7 changes to 7 files (+1 heads)
1540 added 8 changesets with 7 changes to 7 files (+1 heads)
1541 new changesets 426bada5c675:bb94757e651a (4 drafts)
1541 new changesets 426bada5c675:bb94757e651a (4 drafts)
1542 test-debug-phase: new rev 0: x -> 0
1542 test-debug-phase: new rev 0: x -> 0
1543 test-debug-phase: new rev 1: x -> 0
1543 test-debug-phase: new rev 1: x -> 0
1544 test-debug-phase: new rev 2: x -> 0
1544 test-debug-phase: new rev 2: x -> 0
1545 test-debug-phase: new rev 3: x -> 1
1545 test-debug-phase: new rev 3: x -> 1
1546 test-debug-phase: new rev 4: x -> 0
1546 test-debug-phase: new rev 4: x -> 0
1547 test-debug-phase: new rev 5: x -> 1
1547 test-debug-phase: new rev 5: x -> 1
1548 test-debug-phase: new rev 6: x -> 1
1548 test-debug-phase: new rev 6: x -> 1
1549 test-debug-phase: new rev 7: x -> 1
1549 test-debug-phase: new rev 7: x -> 1
1550
1550
1551 $ hg -R mergetest-bundle1 log -G -T '{shortest(node, 5)} {phase}'
1551 $ hg -R mergetest-bundle1 log -G -T '{shortest(node, 5)} {phase}'
1552 o bb947 draft
1552 o bb947 draft
1553 |
1553 |
1554 | o 5ac28 draft
1554 | o 5ac28 draft
1555 |/|
1555 |/|
1556 o | 13b7b draft
1556 o | 13b7b draft
1557 | |
1557 | |
1558 | o f5853 public
1558 | o f5853 public
1559 | |
1559 | |
1560 o | c67c4 draft
1560 o | c67c4 draft
1561 | |
1561 | |
1562 | o 26805 public
1562 | o 26805 public
1563 |/
1563 |/
1564 o 11247 public
1564 o 11247 public
1565 |
1565 |
1566 o 426ba public
1566 o 426ba public
1567
1567
1568 $ killdaemons.py
1568 $ killdaemons.py
1569
1569
1570
1570
1571 auto-publish config
1571 auto-publish config
1572 -------------------
1572 -------------------
1573
1573
1574 $ hg init auto-publish-orig
1574 $ hg init auto-publish-orig
1575 $ hg clone -q auto-publish-orig auto-publish-clone
1575 $ hg clone -q auto-publish-orig auto-publish-clone
1576 $ cd auto-publish-clone
1576 $ cd auto-publish-clone
1577 $ mkcommit a-p-A
1577 $ mkcommit a-p-A
1578 test-debug-phase: new rev 0: x -> 1
1578 test-debug-phase: new rev 0: x -> 1
1579 $ mkcommit a-p-B
1579 $ mkcommit a-p-B
1580 test-debug-phase: new rev 1: x -> 1
1580 test-debug-phase: new rev 1: x -> 1
1581
1581
1582 abort behavior
1582 abort behavior
1583
1583
1584 $ hg push --config experimental.auto-publish=abort
1584 $ hg push --config experimental.auto-publish=abort
1585 pushing to $TESTTMP/auto-publish-orig
1585 pushing to $TESTTMP/auto-publish-orig
1586 abort: push would publish 2 changesets
1586 abort: push would publish 2 changesets
1587 (use --publish or adjust 'experimental.auto-publish' config)
1587 (use --publish or adjust 'experimental.auto-publish' config)
1588 [255]
1588 [255]
1589 $ hg push -r '.^' --config experimental.auto-publish=abort
1589 $ hg push -r '.^' --config experimental.auto-publish=abort
1590 pushing to $TESTTMP/auto-publish-orig
1590 pushing to $TESTTMP/auto-publish-orig
1591 abort: push would publish 1 changesets
1591 abort: push would publish 1 changesets
1592 (use --publish or adjust 'experimental.auto-publish' config)
1592 (use --publish or adjust 'experimental.auto-publish' config)
1593 [255]
1593 [255]
1594
1594
1595 --publish flag makes push succeed
1595 --publish flag makes push succeed
1596
1596
1597 $ hg push -r '.^' --publish --config experimental.auto-publish=abort
1597 $ hg push -r '.^' --publish --config experimental.auto-publish=abort
1598 pushing to $TESTTMP/auto-publish-orig
1598 pushing to $TESTTMP/auto-publish-orig
1599 searching for changes
1599 searching for changes
1600 adding changesets
1600 adding changesets
1601 adding manifests
1601 adding manifests
1602 adding file changes
1602 adding file changes
1603 added 1 changesets with 1 changes to 1 files
1603 added 1 changesets with 1 changes to 1 files
1604 test-debug-phase: new rev 0: x -> 0
1604 test-debug-phase: new rev 0: x -> 0
1605 test-debug-phase: move rev 0: 1 -> 0
1605 test-debug-phase: move rev 0: 1 -> 0
1606
1606
1607 warn behavior
1607 warn behavior
1608
1608
1609 $ hg push --config experimental.auto-publish=warn
1609 $ hg push --config experimental.auto-publish=warn
1610 pushing to $TESTTMP/auto-publish-orig
1610 pushing to $TESTTMP/auto-publish-orig
1611 1 changesets about to be published
1611 1 changesets about to be published
1612 searching for changes
1612 searching for changes
1613 adding changesets
1613 adding changesets
1614 adding manifests
1614 adding manifests
1615 adding file changes
1615 adding file changes
1616 added 1 changesets with 1 changes to 1 files
1616 added 1 changesets with 1 changes to 1 files
1617 test-debug-phase: new rev 1: x -> 0
1617 test-debug-phase: new rev 1: x -> 0
1618 test-debug-phase: move rev 1: 1 -> 0
1618 test-debug-phase: move rev 1: 1 -> 0
1619
1619
1620 confirm behavior
1620 confirm behavior
1621
1621
1622 $ mkcommit a-p-C
1622 $ mkcommit a-p-C
1623 test-debug-phase: new rev 2: x -> 1
1623 test-debug-phase: new rev 2: x -> 1
1624 $ hg push --config experimental.auto-publish=confirm
1624 $ hg push --config experimental.auto-publish=confirm
1625 pushing to $TESTTMP/auto-publish-orig
1625 pushing to $TESTTMP/auto-publish-orig
1626 push and publish 1 changesets (yn)? y
1626 push and publish 1 changesets (yn)? y
1627 searching for changes
1627 searching for changes
1628 adding changesets
1628 adding changesets
1629 adding manifests
1629 adding manifests
1630 adding file changes
1630 adding file changes
1631 added 1 changesets with 1 changes to 1 files
1631 added 1 changesets with 1 changes to 1 files
1632 test-debug-phase: new rev 2: x -> 0
1632 test-debug-phase: new rev 2: x -> 0
1633 test-debug-phase: move rev 2: 1 -> 0
1633 test-debug-phase: move rev 2: 1 -> 0
1634
1634
1635 $ cd ..
1635 $ cd ..
1636
1636
1637
1637
1638 --publish flag
1638 --publish flag
1639 --------------
1639 --------------
1640
1640
1641 $ hg init doesnt-publish
1641 $ hg init doesnt-publish
1642 $ cd doesnt-publish
1642 $ cd doesnt-publish
1643 $ cat > .hg/hgrc << EOF
1643 $ cat > .hg/hgrc << EOF
1644 > [phases]
1644 > [phases]
1645 > publish=0
1645 > publish=0
1646 > EOF
1646 > EOF
1647 $ mkcommit orig-root
1647 $ mkcommit orig-root
1648 test-debug-phase: new rev 0: x -> 1
1648 test-debug-phase: new rev 0: x -> 1
1649 $ hg phase --public -r 'all()'
1649 $ hg phase --public -r 'all()'
1650 test-debug-phase: move rev 0: 1 -> 0
1650 test-debug-phase: move rev 0: 1 -> 0
1651 $ cd ..
1651 $ cd ..
1652
1652
1653 $ hg clone -q doesnt-publish client
1653 $ hg clone -q doesnt-publish client
1654 $ cd client
1654 $ cd client
1655
1655
1656 pushing nothing
1656 pushing nothing
1657
1657
1658 $ mkcommit new-A
1658 $ mkcommit new-A
1659 test-debug-phase: new rev 1: x -> 1
1659 test-debug-phase: new rev 1: x -> 1
1660 $ mkcommit new-B
1660 $ mkcommit new-B
1661 test-debug-phase: new rev 2: x -> 1
1661 test-debug-phase: new rev 2: x -> 1
1662 $ hg push --publish -r null
1662 $ hg push --publish -r null
1663 pushing to $TESTTMP/doesnt-publish
1663 pushing to $TESTTMP/doesnt-publish
1664 searching for changes
1664 searching for changes
1665 no changes found
1665 no changes found
1666 [1]
1666 [1]
1667 $ hgph
1667 $ hgph
1668 @ 2 draft new-B - 89512e87d697
1668 @ 2 draft new-B - 89512e87d697
1669 |
1669 |
1670 o 1 draft new-A - 4826e44e690e
1670 o 1 draft new-A - 4826e44e690e
1671 |
1671 |
1672 o 0 public orig-root - c48edaf99a10
1672 o 0 public orig-root - c48edaf99a10
1673
1673
1674
1674
1675 pushing a new changeset (selective)
1675 pushing a new changeset (selective)
1676
1676
1677 $ hg push --publish -r 'desc("new-A")'
1677 $ hg push --publish -r 'desc("new-A")'
1678 pushing to $TESTTMP/doesnt-publish
1678 pushing to $TESTTMP/doesnt-publish
1679 searching for changes
1679 searching for changes
1680 adding changesets
1680 adding changesets
1681 adding manifests
1681 adding manifests
1682 adding file changes
1682 adding file changes
1683 added 1 changesets with 1 changes to 1 files
1683 added 1 changesets with 1 changes to 1 files
1684 test-debug-phase: new rev 1: x -> 0
1684 test-debug-phase: new rev 1: x -> 0
1685 test-debug-phase: move rev 1: 1 -> 0
1685 test-debug-phase: move rev 1: 1 -> 0
1686 $ hgph
1686 $ hgph
1687 @ 2 draft new-B - 89512e87d697
1687 @ 2 draft new-B - 89512e87d697
1688 |
1688 |
1689 o 1 public new-A - 4826e44e690e
1689 o 1 public new-A - 4826e44e690e
1690 |
1690 |
1691 o 0 public orig-root - c48edaf99a10
1691 o 0 public orig-root - c48edaf99a10
1692
1692
1693
1693
1694 pushing a new changeset (linear)
1694 pushing a new changeset (linear)
1695
1695
1696 $ hg push --publish
1696 $ hg push --publish
1697 pushing to $TESTTMP/doesnt-publish
1697 pushing to $TESTTMP/doesnt-publish
1698 searching for changes
1698 searching for changes
1699 adding changesets
1699 adding changesets
1700 adding manifests
1700 adding manifests
1701 adding file changes
1701 adding file changes
1702 added 1 changesets with 1 changes to 1 files
1702 added 1 changesets with 1 changes to 1 files
1703 test-debug-phase: new rev 2: x -> 0
1703 test-debug-phase: new rev 2: x -> 0
1704 test-debug-phase: move rev 2: 1 -> 0
1704 test-debug-phase: move rev 2: 1 -> 0
1705 $ hgph
1705 $ hgph
1706 @ 2 public new-B - 89512e87d697
1706 @ 2 public new-B - 89512e87d697
1707 |
1707 |
1708 o 1 public new-A - 4826e44e690e
1708 o 1 public new-A - 4826e44e690e
1709 |
1709 |
1710 o 0 public orig-root - c48edaf99a10
1710 o 0 public orig-root - c48edaf99a10
1711
1711
1712
1712
1713 pushing new changesets (different branches)
1713 pushing new changesets (different branches)
1714
1714
1715 $ mkcommit new-C
1715 $ mkcommit new-C
1716 test-debug-phase: new rev 3: x -> 1
1716 test-debug-phase: new rev 3: x -> 1
1717 $ hg update -q '.^'
1717 $ hg update -q '.^'
1718 $ hg branch -q another
1718 $ hg branch -q another
1719 $ mkcommit new-D
1719 $ mkcommit new-D
1720 test-debug-phase: new rev 4: x -> 1
1720 test-debug-phase: new rev 4: x -> 1
1721 $ hg push --new-branch --publish
1721 $ hg push --new-branch --publish
1722 pushing to $TESTTMP/doesnt-publish
1722 pushing to $TESTTMP/doesnt-publish
1723 searching for changes
1723 searching for changes
1724 adding changesets
1724 adding changesets
1725 adding manifests
1725 adding manifests
1726 adding file changes
1726 adding file changes
1727 added 2 changesets with 2 changes to 2 files (+1 heads)
1727 added 2 changesets with 2 changes to 2 files (+1 heads)
1728 test-debug-phase: new rev 3: x -> 0
1728 test-debug-phase: new rev 3: x -> 0
1729 test-debug-phase: new rev 4: x -> 0
1729 test-debug-phase: new rev 4: x -> 0
1730 test-debug-phase: move rev 3: 1 -> 0
1730 test-debug-phase: move rev 3: 1 -> 0
1731 test-debug-phase: move rev 4: 1 -> 0
1731 test-debug-phase: move rev 4: 1 -> 0
1732 $ hgph
1732 $ hgph
1733 @ 4 public new-D - 5e53dcafd13c
1733 @ 4 public new-D - 5e53dcafd13c
1734 |
1734 |
1735 | o 3 public new-C - 1665482cc06d
1735 | o 3 public new-C - 1665482cc06d
1736 |/
1736 |/
1737 o 2 public new-B - 89512e87d697
1737 o 2 public new-B - 89512e87d697
1738 |
1738 |
1739 o 1 public new-A - 4826e44e690e
1739 o 1 public new-A - 4826e44e690e
1740 |
1740 |
1741 o 0 public orig-root - c48edaf99a10
1741 o 0 public orig-root - c48edaf99a10
1742
1742
1743
1743
1744 pushing a shared changeset
1744 pushing a shared changeset
1745
1745
1746 $ mkcommit new-E
1746 $ mkcommit new-E
1747 test-debug-phase: new rev 5: x -> 1
1747 test-debug-phase: new rev 5: x -> 1
1748 $ hg push
1748 $ hg push
1749 pushing to $TESTTMP/doesnt-publish
1749 pushing to $TESTTMP/doesnt-publish
1750 searching for changes
1750 searching for changes
1751 adding changesets
1751 adding changesets
1752 adding manifests
1752 adding manifests
1753 adding file changes
1753 adding file changes
1754 added 1 changesets with 1 changes to 1 files
1754 added 1 changesets with 1 changes to 1 files
1755 test-debug-phase: new rev 5: x -> 1
1755 test-debug-phase: new rev 5: x -> 1
1756 $ hg push --publish
1756 $ hg push --publish
1757 pushing to $TESTTMP/doesnt-publish
1757 pushing to $TESTTMP/doesnt-publish
1758 searching for changes
1758 searching for changes
1759 no changes found
1759 no changes found
1760 test-debug-phase: move rev 5: 1 -> 0
1760 test-debug-phase: move rev 5: 1 -> 0
1761 test-debug-phase: move rev 5: 1 -> 0
1761 test-debug-phase: move rev 5: 1 -> 0
1762 [1]
1762 [1]
1763 $ hgph
1763 $ hgph
1764 @ 5 public new-E - 48931ee3529c
1764 @ 5 public new-E - 48931ee3529c
1765 |
1765 |
1766 o 4 public new-D - 5e53dcafd13c
1766 o 4 public new-D - 5e53dcafd13c
1767 |
1767 |
1768 | o 3 public new-C - 1665482cc06d
1768 | o 3 public new-C - 1665482cc06d
1769 |/
1769 |/
1770 o 2 public new-B - 89512e87d697
1770 o 2 public new-B - 89512e87d697
1771 |
1771 |
1772 o 1 public new-A - 4826e44e690e
1772 o 1 public new-A - 4826e44e690e
1773 |
1773 |
1774 o 0 public orig-root - c48edaf99a10
1774 o 0 public orig-root - c48edaf99a10
1775
1775
1776 $ cd ..
1776 $ cd ..
1777
1777
1778 --publish with subrepos (doesn't propagate to subrepos currently)
1778 --publish with subrepos (doesn't propagate to subrepos currently)
1779
1779
1780 $ hg init with-subrepo
1780 $ hg init with-subrepo
1781 $ cd with-subrepo
1781 $ cd with-subrepo
1782 $ cat > .hg/hgrc << EOF
1782 $ cat > .hg/hgrc << EOF
1783 > [phases]
1783 > [phases]
1784 > publish=0
1784 > publish=0
1785 > EOF
1785 > EOF
1786 $ hg init subrepo
1786 $ hg init subrepo
1787 $ cd subrepo
1787 $ cd subrepo
1788 $ cat > .hg/hgrc << EOF
1788 $ cat > .hg/hgrc << EOF
1789 > [phases]
1789 > [phases]
1790 > publish=0
1790 > publish=0
1791 > EOF
1791 > EOF
1792 $ echo foo > foo
1792 $ echo foo > foo
1793 $ hg ci -qAm0
1793 $ hg ci -qAm0
1794 test-debug-phase: new rev 0: x -> 1
1794 test-debug-phase: new rev 0: x -> 1
1795 $ cd ..
1795 $ cd ..
1796 $ echo 'subrepo = subrepo' > .hgsub
1796 $ echo 'subrepo = subrepo' > .hgsub
1797 $ hg add .hgsub
1797 $ hg add .hgsub
1798 $ hg ci -m 'Adding subrepo'
1798 $ hg ci -m 'Adding subrepo'
1799 test-debug-phase: new rev 0: x -> 1
1799 test-debug-phase: new rev 0: x -> 1
1800 $ hgph
1800 $ hgph
1801 @ 0 draft Adding subrepo - 74d5b62379c0
1801 @ 0 draft Adding subrepo - 74d5b62379c0
1802
1802
1803 $ hgph -R subrepo
1803 $ hgph -R subrepo
1804 @ 0 draft 0 - 4b3f578e3344
1804 @ 0 draft 0 - 4b3f578e3344
1805
1805
1806 $ cd ..
1806 $ cd ..
1807 $ hg clone with-subrepo client-with-subrepo
1807 $ hg clone with-subrepo client-with-subrepo
1808 updating to branch default
1808 updating to branch default
1809 cloning subrepo subrepo from $TESTTMP/with-subrepo/subrepo
1809 cloning subrepo subrepo from $TESTTMP/with-subrepo/subrepo
1810 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1810 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1811 $ cd client-with-subrepo
1811 $ cd client-with-subrepo
1812 $ hg push --publish
1812 $ hg push --publish
1813 pushing to $TESTTMP/with-subrepo
1813 pushing to $TESTTMP/with-subrepo
1814 no changes made to subrepo subrepo since last push to $TESTTMP/with-subrepo/subrepo
1814 no changes made to subrepo subrepo since last push to $TESTTMP/with-subrepo/subrepo
1815 searching for changes
1815 searching for changes
1816 no changes found
1816 no changes found
1817 test-debug-phase: move rev 0: 1 -> 0
1817 test-debug-phase: move rev 0: 1 -> 0
1818 test-debug-phase: move rev 0: 1 -> 0
1818 test-debug-phase: move rev 0: 1 -> 0
1819 [1]
1819 [1]
1820 $ hgph
1820 $ hgph
1821 @ 0 public Adding subrepo - 74d5b62379c0
1821 @ 0 public Adding subrepo - 74d5b62379c0
1822
1822
1823 $ hgph -R subrepo
1823 $ hgph -R subrepo
1824 @ 0 draft 0 - 4b3f578e3344
1824 @ 0 draft 0 - 4b3f578e3344
1825
1825
General Comments 0
You need to be logged in to leave comments. Login now