##// END OF EJS Templates
run-tests: configure the environment to expand `~` properly with Windows py38+...
Matt Harbison -
r46708:08fd76a5 default
parent child Browse files
Show More
@@ -1,3819 +1,3822 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 if os.name == 'nt':
1339 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1340 env['USERPROFILE'] = env['HOME']
1338 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1341 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1339 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1342 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1340 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1343 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1341 # This number should match portneeded in _getport
1344 # This number should match portneeded in _getport
1342 for port in xrange(3):
1345 for port in xrange(3):
1343 # This list should be parallel to _portmap in _getreplacements
1346 # This list should be parallel to _portmap in _getreplacements
1344 defineport(port)
1347 defineport(port)
1345 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1348 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1346 env["DAEMON_PIDS"] = _bytes2sys(
1349 env["DAEMON_PIDS"] = _bytes2sys(
1347 os.path.join(self._threadtmp, b'daemon.pids')
1350 os.path.join(self._threadtmp, b'daemon.pids')
1348 )
1351 )
1349 env["HGEDITOR"] = (
1352 env["HGEDITOR"] = (
1350 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1353 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1351 )
1354 )
1352 env["HGUSER"] = "test"
1355 env["HGUSER"] = "test"
1353 env["HGENCODING"] = "ascii"
1356 env["HGENCODING"] = "ascii"
1354 env["HGENCODINGMODE"] = "strict"
1357 env["HGENCODINGMODE"] = "strict"
1355 env["HGHOSTNAME"] = "test-hostname"
1358 env["HGHOSTNAME"] = "test-hostname"
1356 env['HGIPV6'] = str(int(self._useipv6))
1359 env['HGIPV6'] = str(int(self._useipv6))
1357 # See contrib/catapipe.py for how to use this functionality.
1360 # See contrib/catapipe.py for how to use this functionality.
1358 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1361 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1359 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1362 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1360 # non-test one in as a default, otherwise set to devnull
1363 # non-test one in as a default, otherwise set to devnull
1361 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1364 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1362 'HGCATAPULTSERVERPIPE', os.devnull
1365 'HGCATAPULTSERVERPIPE', os.devnull
1363 )
1366 )
1364
1367
1365 extraextensions = []
1368 extraextensions = []
1366 for opt in self._extraconfigopts:
1369 for opt in self._extraconfigopts:
1367 section, key = opt.split('.', 1)
1370 section, key = opt.split('.', 1)
1368 if section != 'extensions':
1371 if section != 'extensions':
1369 continue
1372 continue
1370 name = key.split('=', 1)[0]
1373 name = key.split('=', 1)[0]
1371 extraextensions.append(name)
1374 extraextensions.append(name)
1372
1375
1373 if extraextensions:
1376 if extraextensions:
1374 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1377 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1375
1378
1376 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1379 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1377 # IP addresses.
1380 # IP addresses.
1378 env['LOCALIP'] = _bytes2sys(self._localip())
1381 env['LOCALIP'] = _bytes2sys(self._localip())
1379
1382
1380 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1383 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1381 # but this is needed for testing python instances like dummyssh,
1384 # but this is needed for testing python instances like dummyssh,
1382 # dummysmtpd.py, and dumbhttp.py.
1385 # dummysmtpd.py, and dumbhttp.py.
1383 if PYTHON3 and os.name == 'nt':
1386 if PYTHON3 and os.name == 'nt':
1384 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1387 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1385
1388
1386 # Modified HOME in test environment can confuse Rust tools. So set
1389 # Modified HOME in test environment can confuse Rust tools. So set
1387 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1390 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1388 # present and these variables aren't already defined.
1391 # present and these variables aren't already defined.
1389 cargo_home_path = os.path.expanduser('~/.cargo')
1392 cargo_home_path = os.path.expanduser('~/.cargo')
1390 rustup_home_path = os.path.expanduser('~/.rustup')
1393 rustup_home_path = os.path.expanduser('~/.rustup')
1391
1394
1392 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1395 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1393 env['CARGO_HOME'] = cargo_home_path
1396 env['CARGO_HOME'] = cargo_home_path
1394 if (
1397 if (
1395 os.path.exists(rustup_home_path)
1398 os.path.exists(rustup_home_path)
1396 and b'RUSTUP_HOME' not in osenvironb
1399 and b'RUSTUP_HOME' not in osenvironb
1397 ):
1400 ):
1398 env['RUSTUP_HOME'] = rustup_home_path
1401 env['RUSTUP_HOME'] = rustup_home_path
1399
1402
1400 # Reset some environment variables to well-known values so that
1403 # Reset some environment variables to well-known values so that
1401 # the tests produce repeatable output.
1404 # the tests produce repeatable output.
1402 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1405 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1403 env['TZ'] = 'GMT'
1406 env['TZ'] = 'GMT'
1404 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1407 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1405 env['COLUMNS'] = '80'
1408 env['COLUMNS'] = '80'
1406 env['TERM'] = 'xterm'
1409 env['TERM'] = 'xterm'
1407
1410
1408 dropped = [
1411 dropped = [
1409 'CDPATH',
1412 'CDPATH',
1410 'CHGDEBUG',
1413 'CHGDEBUG',
1411 'EDITOR',
1414 'EDITOR',
1412 'GREP_OPTIONS',
1415 'GREP_OPTIONS',
1413 'HG',
1416 'HG',
1414 'HGMERGE',
1417 'HGMERGE',
1415 'HGPLAIN',
1418 'HGPLAIN',
1416 'HGPLAINEXCEPT',
1419 'HGPLAINEXCEPT',
1417 'HGPROF',
1420 'HGPROF',
1418 'http_proxy',
1421 'http_proxy',
1419 'no_proxy',
1422 'no_proxy',
1420 'NO_PROXY',
1423 'NO_PROXY',
1421 'PAGER',
1424 'PAGER',
1422 'VISUAL',
1425 'VISUAL',
1423 ]
1426 ]
1424
1427
1425 for k in dropped:
1428 for k in dropped:
1426 if k in env:
1429 if k in env:
1427 del env[k]
1430 del env[k]
1428
1431
1429 # unset env related to hooks
1432 # unset env related to hooks
1430 for k in list(env):
1433 for k in list(env):
1431 if k.startswith('HG_'):
1434 if k.startswith('HG_'):
1432 del env[k]
1435 del env[k]
1433
1436
1434 if self._usechg:
1437 if self._usechg:
1435 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1438 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1436 if self._chgdebug:
1439 if self._chgdebug:
1437 env['CHGDEBUG'] = 'true'
1440 env['CHGDEBUG'] = 'true'
1438
1441
1439 return env
1442 return env
1440
1443
1441 def _createhgrc(self, path):
1444 def _createhgrc(self, path):
1442 """Create an hgrc file for this test."""
1445 """Create an hgrc file for this test."""
1443 with open(path, 'wb') as hgrc:
1446 with open(path, 'wb') as hgrc:
1444 hgrc.write(b'[ui]\n')
1447 hgrc.write(b'[ui]\n')
1445 hgrc.write(b'slash = True\n')
1448 hgrc.write(b'slash = True\n')
1446 hgrc.write(b'interactive = False\n')
1449 hgrc.write(b'interactive = False\n')
1447 hgrc.write(b'detailed-exit-code = True\n')
1450 hgrc.write(b'detailed-exit-code = True\n')
1448 hgrc.write(b'merge = internal:merge\n')
1451 hgrc.write(b'merge = internal:merge\n')
1449 hgrc.write(b'mergemarkers = detailed\n')
1452 hgrc.write(b'mergemarkers = detailed\n')
1450 hgrc.write(b'promptecho = True\n')
1453 hgrc.write(b'promptecho = True\n')
1451 hgrc.write(b'timeout.warn=15\n')
1454 hgrc.write(b'timeout.warn=15\n')
1452 hgrc.write(b'[defaults]\n')
1455 hgrc.write(b'[defaults]\n')
1453 hgrc.write(b'[devel]\n')
1456 hgrc.write(b'[devel]\n')
1454 hgrc.write(b'all-warnings = true\n')
1457 hgrc.write(b'all-warnings = true\n')
1455 hgrc.write(b'default-date = 0 0\n')
1458 hgrc.write(b'default-date = 0 0\n')
1456 hgrc.write(b'[largefiles]\n')
1459 hgrc.write(b'[largefiles]\n')
1457 hgrc.write(
1460 hgrc.write(
1458 b'usercache = %s\n'
1461 b'usercache = %s\n'
1459 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1462 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1460 )
1463 )
1461 hgrc.write(b'[lfs]\n')
1464 hgrc.write(b'[lfs]\n')
1462 hgrc.write(
1465 hgrc.write(
1463 b'usercache = %s\n'
1466 b'usercache = %s\n'
1464 % (os.path.join(self._testtmp, b'.cache/lfs'))
1467 % (os.path.join(self._testtmp, b'.cache/lfs'))
1465 )
1468 )
1466 hgrc.write(b'[web]\n')
1469 hgrc.write(b'[web]\n')
1467 hgrc.write(b'address = localhost\n')
1470 hgrc.write(b'address = localhost\n')
1468 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1471 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1469 hgrc.write(b'server-header = testing stub value\n')
1472 hgrc.write(b'server-header = testing stub value\n')
1470
1473
1471 for opt in self._extraconfigopts:
1474 for opt in self._extraconfigopts:
1472 section, key = _sys2bytes(opt).split(b'.', 1)
1475 section, key = _sys2bytes(opt).split(b'.', 1)
1473 assert b'=' in key, (
1476 assert b'=' in key, (
1474 'extra config opt %s must ' 'have an = for assignment' % opt
1477 'extra config opt %s must ' 'have an = for assignment' % opt
1475 )
1478 )
1476 hgrc.write(b'[%s]\n%s\n' % (section, key))
1479 hgrc.write(b'[%s]\n%s\n' % (section, key))
1477
1480
1478 def fail(self, msg):
1481 def fail(self, msg):
1479 # unittest differentiates between errored and failed.
1482 # unittest differentiates between errored and failed.
1480 # Failed is denoted by AssertionError (by default at least).
1483 # Failed is denoted by AssertionError (by default at least).
1481 raise AssertionError(msg)
1484 raise AssertionError(msg)
1482
1485
1483 def _runcommand(self, cmd, env, normalizenewlines=False):
1486 def _runcommand(self, cmd, env, normalizenewlines=False):
1484 """Run command in a sub-process, capturing the output (stdout and
1487 """Run command in a sub-process, capturing the output (stdout and
1485 stderr).
1488 stderr).
1486
1489
1487 Return a tuple (exitcode, output). output is None in debug mode.
1490 Return a tuple (exitcode, output). output is None in debug mode.
1488 """
1491 """
1489 if self._debug:
1492 if self._debug:
1490 proc = subprocess.Popen(
1493 proc = subprocess.Popen(
1491 _bytes2sys(cmd),
1494 _bytes2sys(cmd),
1492 shell=True,
1495 shell=True,
1493 cwd=_bytes2sys(self._testtmp),
1496 cwd=_bytes2sys(self._testtmp),
1494 env=env,
1497 env=env,
1495 )
1498 )
1496 ret = proc.wait()
1499 ret = proc.wait()
1497 return (ret, None)
1500 return (ret, None)
1498
1501
1499 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1502 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1500
1503
1501 def cleanup():
1504 def cleanup():
1502 terminate(proc)
1505 terminate(proc)
1503 ret = proc.wait()
1506 ret = proc.wait()
1504 if ret == 0:
1507 if ret == 0:
1505 ret = signal.SIGTERM << 8
1508 ret = signal.SIGTERM << 8
1506 killdaemons(env['DAEMON_PIDS'])
1509 killdaemons(env['DAEMON_PIDS'])
1507 return ret
1510 return ret
1508
1511
1509 proc.tochild.close()
1512 proc.tochild.close()
1510
1513
1511 try:
1514 try:
1512 output = proc.fromchild.read()
1515 output = proc.fromchild.read()
1513 except KeyboardInterrupt:
1516 except KeyboardInterrupt:
1514 vlog('# Handling keyboard interrupt')
1517 vlog('# Handling keyboard interrupt')
1515 cleanup()
1518 cleanup()
1516 raise
1519 raise
1517
1520
1518 ret = proc.wait()
1521 ret = proc.wait()
1519 if wifexited(ret):
1522 if wifexited(ret):
1520 ret = os.WEXITSTATUS(ret)
1523 ret = os.WEXITSTATUS(ret)
1521
1524
1522 if proc.timeout:
1525 if proc.timeout:
1523 ret = 'timeout'
1526 ret = 'timeout'
1524
1527
1525 if ret:
1528 if ret:
1526 killdaemons(env['DAEMON_PIDS'])
1529 killdaemons(env['DAEMON_PIDS'])
1527
1530
1528 for s, r in self._getreplacements():
1531 for s, r in self._getreplacements():
1529 output = re.sub(s, r, output)
1532 output = re.sub(s, r, output)
1530
1533
1531 if normalizenewlines:
1534 if normalizenewlines:
1532 output = output.replace(b'\r\n', b'\n')
1535 output = output.replace(b'\r\n', b'\n')
1533
1536
1534 return ret, output.splitlines(True)
1537 return ret, output.splitlines(True)
1535
1538
1536
1539
1537 class PythonTest(Test):
1540 class PythonTest(Test):
1538 """A Python-based test."""
1541 """A Python-based test."""
1539
1542
1540 @property
1543 @property
1541 def refpath(self):
1544 def refpath(self):
1542 return os.path.join(self._testdir, b'%s.out' % self.bname)
1545 return os.path.join(self._testdir, b'%s.out' % self.bname)
1543
1546
1544 def _run(self, env):
1547 def _run(self, env):
1545 # Quote the python(3) executable for Windows
1548 # Quote the python(3) executable for Windows
1546 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1549 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1547 vlog("# Running", cmd.decode("utf-8"))
1550 vlog("# Running", cmd.decode("utf-8"))
1548 normalizenewlines = os.name == 'nt'
1551 normalizenewlines = os.name == 'nt'
1549 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1552 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1550 if self._aborted:
1553 if self._aborted:
1551 raise KeyboardInterrupt()
1554 raise KeyboardInterrupt()
1552
1555
1553 return result
1556 return result
1554
1557
1555
1558
1556 # Some glob patterns apply only in some circumstances, so the script
1559 # Some glob patterns apply only in some circumstances, so the script
1557 # might want to remove (glob) annotations that otherwise should be
1560 # might want to remove (glob) annotations that otherwise should be
1558 # retained.
1561 # retained.
1559 checkcodeglobpats = [
1562 checkcodeglobpats = [
1560 # On Windows it looks like \ doesn't require a (glob), but we know
1563 # On Windows it looks like \ doesn't require a (glob), but we know
1561 # better.
1564 # better.
1562 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1565 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1563 re.compile(br'^moving \S+/.*[^)]$'),
1566 re.compile(br'^moving \S+/.*[^)]$'),
1564 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1567 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1565 # Not all platforms have 127.0.0.1 as loopback (though most do),
1568 # Not all platforms have 127.0.0.1 as loopback (though most do),
1566 # so we always glob that too.
1569 # so we always glob that too.
1567 re.compile(br'.*\$LOCALIP.*$'),
1570 re.compile(br'.*\$LOCALIP.*$'),
1568 ]
1571 ]
1569
1572
1570 bchr = chr
1573 bchr = chr
1571 if PYTHON3:
1574 if PYTHON3:
1572 bchr = lambda x: bytes([x])
1575 bchr = lambda x: bytes([x])
1573
1576
1574 WARN_UNDEFINED = 1
1577 WARN_UNDEFINED = 1
1575 WARN_YES = 2
1578 WARN_YES = 2
1576 WARN_NO = 3
1579 WARN_NO = 3
1577
1580
1578 MARK_OPTIONAL = b" (?)\n"
1581 MARK_OPTIONAL = b" (?)\n"
1579
1582
1580
1583
1581 def isoptional(line):
1584 def isoptional(line):
1582 return line.endswith(MARK_OPTIONAL)
1585 return line.endswith(MARK_OPTIONAL)
1583
1586
1584
1587
1585 class TTest(Test):
1588 class TTest(Test):
1586 """A "t test" is a test backed by a .t file."""
1589 """A "t test" is a test backed by a .t file."""
1587
1590
1588 SKIPPED_PREFIX = b'skipped: '
1591 SKIPPED_PREFIX = b'skipped: '
1589 FAILED_PREFIX = b'hghave check failed: '
1592 FAILED_PREFIX = b'hghave check failed: '
1590 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1593 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1591
1594
1592 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1595 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1593 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1596 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1594 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1597 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1595
1598
1596 def __init__(self, path, *args, **kwds):
1599 def __init__(self, path, *args, **kwds):
1597 # accept an extra "case" parameter
1600 # accept an extra "case" parameter
1598 case = kwds.pop('case', [])
1601 case = kwds.pop('case', [])
1599 self._case = case
1602 self._case = case
1600 self._allcases = {x for y in parsettestcases(path) for x in y}
1603 self._allcases = {x for y in parsettestcases(path) for x in y}
1601 super(TTest, self).__init__(path, *args, **kwds)
1604 super(TTest, self).__init__(path, *args, **kwds)
1602 if case:
1605 if case:
1603 casepath = b'#'.join(case)
1606 casepath = b'#'.join(case)
1604 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1607 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1605 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1608 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1606 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1609 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1607 self._have = {}
1610 self._have = {}
1608
1611
1609 @property
1612 @property
1610 def refpath(self):
1613 def refpath(self):
1611 return os.path.join(self._testdir, self.bname)
1614 return os.path.join(self._testdir, self.bname)
1612
1615
1613 def _run(self, env):
1616 def _run(self, env):
1614 with open(self.path, 'rb') as f:
1617 with open(self.path, 'rb') as f:
1615 lines = f.readlines()
1618 lines = f.readlines()
1616
1619
1617 # .t file is both reference output and the test input, keep reference
1620 # .t file is both reference output and the test input, keep reference
1618 # output updated with the the test input. This avoids some race
1621 # output updated with the the test input. This avoids some race
1619 # conditions where the reference output does not match the actual test.
1622 # conditions where the reference output does not match the actual test.
1620 if self._refout is not None:
1623 if self._refout is not None:
1621 self._refout = lines
1624 self._refout = lines
1622
1625
1623 salt, script, after, expected = self._parsetest(lines)
1626 salt, script, after, expected = self._parsetest(lines)
1624
1627
1625 # Write out the generated script.
1628 # Write out the generated script.
1626 fname = b'%s.sh' % self._testtmp
1629 fname = b'%s.sh' % self._testtmp
1627 with open(fname, 'wb') as f:
1630 with open(fname, 'wb') as f:
1628 for l in script:
1631 for l in script:
1629 f.write(l)
1632 f.write(l)
1630
1633
1631 cmd = b'%s "%s"' % (self._shell, fname)
1634 cmd = b'%s "%s"' % (self._shell, fname)
1632 vlog("# Running", cmd.decode("utf-8"))
1635 vlog("# Running", cmd.decode("utf-8"))
1633
1636
1634 exitcode, output = self._runcommand(cmd, env)
1637 exitcode, output = self._runcommand(cmd, env)
1635
1638
1636 if self._aborted:
1639 if self._aborted:
1637 raise KeyboardInterrupt()
1640 raise KeyboardInterrupt()
1638
1641
1639 # Do not merge output if skipped. Return hghave message instead.
1642 # Do not merge output if skipped. Return hghave message instead.
1640 # Similarly, with --debug, output is None.
1643 # Similarly, with --debug, output is None.
1641 if exitcode == self.SKIPPED_STATUS or output is None:
1644 if exitcode == self.SKIPPED_STATUS or output is None:
1642 return exitcode, output
1645 return exitcode, output
1643
1646
1644 return self._processoutput(exitcode, output, salt, after, expected)
1647 return self._processoutput(exitcode, output, salt, after, expected)
1645
1648
1646 def _hghave(self, reqs):
1649 def _hghave(self, reqs):
1647 allreqs = b' '.join(reqs)
1650 allreqs = b' '.join(reqs)
1648
1651
1649 self._detectslow(reqs)
1652 self._detectslow(reqs)
1650
1653
1651 if allreqs in self._have:
1654 if allreqs in self._have:
1652 return self._have.get(allreqs)
1655 return self._have.get(allreqs)
1653
1656
1654 # TODO do something smarter when all other uses of hghave are gone.
1657 # TODO do something smarter when all other uses of hghave are gone.
1655 runtestdir = osenvironb[b'RUNTESTDIR']
1658 runtestdir = osenvironb[b'RUNTESTDIR']
1656 tdir = runtestdir.replace(b'\\', b'/')
1659 tdir = runtestdir.replace(b'\\', b'/')
1657 proc = Popen4(
1660 proc = Popen4(
1658 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1661 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1659 self._testtmp,
1662 self._testtmp,
1660 0,
1663 0,
1661 self._getenv(),
1664 self._getenv(),
1662 )
1665 )
1663 stdout, stderr = proc.communicate()
1666 stdout, stderr = proc.communicate()
1664 ret = proc.wait()
1667 ret = proc.wait()
1665 if wifexited(ret):
1668 if wifexited(ret):
1666 ret = os.WEXITSTATUS(ret)
1669 ret = os.WEXITSTATUS(ret)
1667 if ret == 2:
1670 if ret == 2:
1668 print(stdout.decode('utf-8'))
1671 print(stdout.decode('utf-8'))
1669 sys.exit(1)
1672 sys.exit(1)
1670
1673
1671 if ret != 0:
1674 if ret != 0:
1672 self._have[allreqs] = (False, stdout)
1675 self._have[allreqs] = (False, stdout)
1673 return False, stdout
1676 return False, stdout
1674
1677
1675 self._have[allreqs] = (True, None)
1678 self._have[allreqs] = (True, None)
1676 return True, None
1679 return True, None
1677
1680
1678 def _detectslow(self, reqs):
1681 def _detectslow(self, reqs):
1679 """update the timeout of slow test when appropriate"""
1682 """update the timeout of slow test when appropriate"""
1680 if b'slow' in reqs:
1683 if b'slow' in reqs:
1681 self._timeout = self._slowtimeout
1684 self._timeout = self._slowtimeout
1682
1685
1683 def _iftest(self, args):
1686 def _iftest(self, args):
1684 # implements "#if"
1687 # implements "#if"
1685 reqs = []
1688 reqs = []
1686 for arg in args:
1689 for arg in args:
1687 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1690 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1688 if arg[3:] in self._case:
1691 if arg[3:] in self._case:
1689 return False
1692 return False
1690 elif arg in self._allcases:
1693 elif arg in self._allcases:
1691 if arg not in self._case:
1694 if arg not in self._case:
1692 return False
1695 return False
1693 else:
1696 else:
1694 reqs.append(arg)
1697 reqs.append(arg)
1695 self._detectslow(reqs)
1698 self._detectslow(reqs)
1696 return self._hghave(reqs)[0]
1699 return self._hghave(reqs)[0]
1697
1700
1698 def _parsetest(self, lines):
1701 def _parsetest(self, lines):
1699 # We generate a shell script which outputs unique markers to line
1702 # We generate a shell script which outputs unique markers to line
1700 # up script results with our source. These markers include input
1703 # up script results with our source. These markers include input
1701 # line number and the last return code.
1704 # line number and the last return code.
1702 salt = b"SALT%d" % time.time()
1705 salt = b"SALT%d" % time.time()
1703
1706
1704 def addsalt(line, inpython):
1707 def addsalt(line, inpython):
1705 if inpython:
1708 if inpython:
1706 script.append(b'%s %d 0\n' % (salt, line))
1709 script.append(b'%s %d 0\n' % (salt, line))
1707 else:
1710 else:
1708 script.append(b'echo %s %d $?\n' % (salt, line))
1711 script.append(b'echo %s %d $?\n' % (salt, line))
1709
1712
1710 activetrace = []
1713 activetrace = []
1711 session = str(uuid.uuid4())
1714 session = str(uuid.uuid4())
1712 if PYTHON3:
1715 if PYTHON3:
1713 session = session.encode('ascii')
1716 session = session.encode('ascii')
1714 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1717 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1715 'HGCATAPULTSERVERPIPE'
1718 'HGCATAPULTSERVERPIPE'
1716 )
1719 )
1717
1720
1718 def toggletrace(cmd=None):
1721 def toggletrace(cmd=None):
1719 if not hgcatapult or hgcatapult == os.devnull:
1722 if not hgcatapult or hgcatapult == os.devnull:
1720 return
1723 return
1721
1724
1722 if activetrace:
1725 if activetrace:
1723 script.append(
1726 script.append(
1724 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1727 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1725 % (session, activetrace[0])
1728 % (session, activetrace[0])
1726 )
1729 )
1727 if cmd is None:
1730 if cmd is None:
1728 return
1731 return
1729
1732
1730 if isinstance(cmd, str):
1733 if isinstance(cmd, str):
1731 quoted = shellquote(cmd.strip())
1734 quoted = shellquote(cmd.strip())
1732 else:
1735 else:
1733 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1736 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1734 quoted = quoted.replace(b'\\', b'\\\\')
1737 quoted = quoted.replace(b'\\', b'\\\\')
1735 script.append(
1738 script.append(
1736 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1739 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1737 % (session, quoted)
1740 % (session, quoted)
1738 )
1741 )
1739 activetrace[0:] = [quoted]
1742 activetrace[0:] = [quoted]
1740
1743
1741 script = []
1744 script = []
1742
1745
1743 # After we run the shell script, we re-unify the script output
1746 # After we run the shell script, we re-unify the script output
1744 # with non-active parts of the source, with synchronization by our
1747 # with non-active parts of the source, with synchronization by our
1745 # SALT line number markers. The after table contains the non-active
1748 # SALT line number markers. The after table contains the non-active
1746 # components, ordered by line number.
1749 # components, ordered by line number.
1747 after = {}
1750 after = {}
1748
1751
1749 # Expected shell script output.
1752 # Expected shell script output.
1750 expected = {}
1753 expected = {}
1751
1754
1752 pos = prepos = -1
1755 pos = prepos = -1
1753
1756
1754 # True or False when in a true or false conditional section
1757 # True or False when in a true or false conditional section
1755 skipping = None
1758 skipping = None
1756
1759
1757 # We keep track of whether or not we're in a Python block so we
1760 # We keep track of whether or not we're in a Python block so we
1758 # can generate the surrounding doctest magic.
1761 # can generate the surrounding doctest magic.
1759 inpython = False
1762 inpython = False
1760
1763
1761 if self._debug:
1764 if self._debug:
1762 script.append(b'set -x\n')
1765 script.append(b'set -x\n')
1763 if self._hgcommand != b'hg':
1766 if self._hgcommand != b'hg':
1764 script.append(b'alias hg="%s"\n' % self._hgcommand)
1767 script.append(b'alias hg="%s"\n' % self._hgcommand)
1765 if os.getenv('MSYSTEM'):
1768 if os.getenv('MSYSTEM'):
1766 script.append(b'alias pwd="pwd -W"\n')
1769 script.append(b'alias pwd="pwd -W"\n')
1767
1770
1768 if hgcatapult and hgcatapult != os.devnull:
1771 if hgcatapult and hgcatapult != os.devnull:
1769 if PYTHON3:
1772 if PYTHON3:
1770 hgcatapult = hgcatapult.encode('utf8')
1773 hgcatapult = hgcatapult.encode('utf8')
1771 cataname = self.name.encode('utf8')
1774 cataname = self.name.encode('utf8')
1772 else:
1775 else:
1773 cataname = self.name
1776 cataname = self.name
1774
1777
1775 # Kludge: use a while loop to keep the pipe from getting
1778 # Kludge: use a while loop to keep the pipe from getting
1776 # closed by our echo commands. The still-running file gets
1779 # closed by our echo commands. The still-running file gets
1777 # reaped at the end of the script, which causes the while
1780 # reaped at the end of the script, which causes the while
1778 # loop to exit and closes the pipe. Sigh.
1781 # loop to exit and closes the pipe. Sigh.
1779 script.append(
1782 script.append(
1780 b'rtendtracing() {\n'
1783 b'rtendtracing() {\n'
1781 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1784 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1782 b' rm -f "$TESTTMP/.still-running"\n'
1785 b' rm -f "$TESTTMP/.still-running"\n'
1783 b'}\n'
1786 b'}\n'
1784 b'trap "rtendtracing" 0\n'
1787 b'trap "rtendtracing" 0\n'
1785 b'touch "$TESTTMP/.still-running"\n'
1788 b'touch "$TESTTMP/.still-running"\n'
1786 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1789 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1787 b'> %(catapult)s &\n'
1790 b'> %(catapult)s &\n'
1788 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1791 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1789 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1792 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1790 % {
1793 % {
1791 b'name': cataname,
1794 b'name': cataname,
1792 b'session': session,
1795 b'session': session,
1793 b'catapult': hgcatapult,
1796 b'catapult': hgcatapult,
1794 }
1797 }
1795 )
1798 )
1796
1799
1797 if self._case:
1800 if self._case:
1798 casestr = b'#'.join(self._case)
1801 casestr = b'#'.join(self._case)
1799 if isinstance(casestr, str):
1802 if isinstance(casestr, str):
1800 quoted = shellquote(casestr)
1803 quoted = shellquote(casestr)
1801 else:
1804 else:
1802 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1805 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1803 script.append(b'TESTCASE=%s\n' % quoted)
1806 script.append(b'TESTCASE=%s\n' % quoted)
1804 script.append(b'export TESTCASE\n')
1807 script.append(b'export TESTCASE\n')
1805
1808
1806 n = 0
1809 n = 0
1807 for n, l in enumerate(lines):
1810 for n, l in enumerate(lines):
1808 if not l.endswith(b'\n'):
1811 if not l.endswith(b'\n'):
1809 l += b'\n'
1812 l += b'\n'
1810 if l.startswith(b'#require'):
1813 if l.startswith(b'#require'):
1811 lsplit = l.split()
1814 lsplit = l.split()
1812 if len(lsplit) < 2 or lsplit[0] != b'#require':
1815 if len(lsplit) < 2 or lsplit[0] != b'#require':
1813 after.setdefault(pos, []).append(
1816 after.setdefault(pos, []).append(
1814 b' !!! invalid #require\n'
1817 b' !!! invalid #require\n'
1815 )
1818 )
1816 if not skipping:
1819 if not skipping:
1817 haveresult, message = self._hghave(lsplit[1:])
1820 haveresult, message = self._hghave(lsplit[1:])
1818 if not haveresult:
1821 if not haveresult:
1819 script = [b'echo "%s"\nexit 80\n' % message]
1822 script = [b'echo "%s"\nexit 80\n' % message]
1820 break
1823 break
1821 after.setdefault(pos, []).append(l)
1824 after.setdefault(pos, []).append(l)
1822 elif l.startswith(b'#if'):
1825 elif l.startswith(b'#if'):
1823 lsplit = l.split()
1826 lsplit = l.split()
1824 if len(lsplit) < 2 or lsplit[0] != b'#if':
1827 if len(lsplit) < 2 or lsplit[0] != b'#if':
1825 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1828 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1826 if skipping is not None:
1829 if skipping is not None:
1827 after.setdefault(pos, []).append(b' !!! nested #if\n')
1830 after.setdefault(pos, []).append(b' !!! nested #if\n')
1828 skipping = not self._iftest(lsplit[1:])
1831 skipping = not self._iftest(lsplit[1:])
1829 after.setdefault(pos, []).append(l)
1832 after.setdefault(pos, []).append(l)
1830 elif l.startswith(b'#else'):
1833 elif l.startswith(b'#else'):
1831 if skipping is None:
1834 if skipping is None:
1832 after.setdefault(pos, []).append(b' !!! missing #if\n')
1835 after.setdefault(pos, []).append(b' !!! missing #if\n')
1833 skipping = not skipping
1836 skipping = not skipping
1834 after.setdefault(pos, []).append(l)
1837 after.setdefault(pos, []).append(l)
1835 elif l.startswith(b'#endif'):
1838 elif l.startswith(b'#endif'):
1836 if skipping is None:
1839 if skipping is None:
1837 after.setdefault(pos, []).append(b' !!! missing #if\n')
1840 after.setdefault(pos, []).append(b' !!! missing #if\n')
1838 skipping = None
1841 skipping = None
1839 after.setdefault(pos, []).append(l)
1842 after.setdefault(pos, []).append(l)
1840 elif skipping:
1843 elif skipping:
1841 after.setdefault(pos, []).append(l)
1844 after.setdefault(pos, []).append(l)
1842 elif l.startswith(b' >>> '): # python inlines
1845 elif l.startswith(b' >>> '): # python inlines
1843 after.setdefault(pos, []).append(l)
1846 after.setdefault(pos, []).append(l)
1844 prepos = pos
1847 prepos = pos
1845 pos = n
1848 pos = n
1846 if not inpython:
1849 if not inpython:
1847 # We've just entered a Python block. Add the header.
1850 # We've just entered a Python block. Add the header.
1848 inpython = True
1851 inpython = True
1849 addsalt(prepos, False) # Make sure we report the exit code.
1852 addsalt(prepos, False) # Make sure we report the exit code.
1850 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1853 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1851 addsalt(n, True)
1854 addsalt(n, True)
1852 script.append(l[2:])
1855 script.append(l[2:])
1853 elif l.startswith(b' ... '): # python inlines
1856 elif l.startswith(b' ... '): # python inlines
1854 after.setdefault(prepos, []).append(l)
1857 after.setdefault(prepos, []).append(l)
1855 script.append(l[2:])
1858 script.append(l[2:])
1856 elif l.startswith(b' $ '): # commands
1859 elif l.startswith(b' $ '): # commands
1857 if inpython:
1860 if inpython:
1858 script.append(b'EOF\n')
1861 script.append(b'EOF\n')
1859 inpython = False
1862 inpython = False
1860 after.setdefault(pos, []).append(l)
1863 after.setdefault(pos, []).append(l)
1861 prepos = pos
1864 prepos = pos
1862 pos = n
1865 pos = n
1863 addsalt(n, False)
1866 addsalt(n, False)
1864 rawcmd = l[4:]
1867 rawcmd = l[4:]
1865 cmd = rawcmd.split()
1868 cmd = rawcmd.split()
1866 toggletrace(rawcmd)
1869 toggletrace(rawcmd)
1867 if len(cmd) == 2 and cmd[0] == b'cd':
1870 if len(cmd) == 2 and cmd[0] == b'cd':
1868 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1871 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1869 script.append(rawcmd)
1872 script.append(rawcmd)
1870 elif l.startswith(b' > '): # continuations
1873 elif l.startswith(b' > '): # continuations
1871 after.setdefault(prepos, []).append(l)
1874 after.setdefault(prepos, []).append(l)
1872 script.append(l[4:])
1875 script.append(l[4:])
1873 elif l.startswith(b' '): # results
1876 elif l.startswith(b' '): # results
1874 # Queue up a list of expected results.
1877 # Queue up a list of expected results.
1875 expected.setdefault(pos, []).append(l[2:])
1878 expected.setdefault(pos, []).append(l[2:])
1876 else:
1879 else:
1877 if inpython:
1880 if inpython:
1878 script.append(b'EOF\n')
1881 script.append(b'EOF\n')
1879 inpython = False
1882 inpython = False
1880 # Non-command/result. Queue up for merged output.
1883 # Non-command/result. Queue up for merged output.
1881 after.setdefault(pos, []).append(l)
1884 after.setdefault(pos, []).append(l)
1882
1885
1883 if inpython:
1886 if inpython:
1884 script.append(b'EOF\n')
1887 script.append(b'EOF\n')
1885 if skipping is not None:
1888 if skipping is not None:
1886 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1889 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1887 addsalt(n + 1, False)
1890 addsalt(n + 1, False)
1888 # Need to end any current per-command trace
1891 # Need to end any current per-command trace
1889 if activetrace:
1892 if activetrace:
1890 toggletrace()
1893 toggletrace()
1891 return salt, script, after, expected
1894 return salt, script, after, expected
1892
1895
1893 def _processoutput(self, exitcode, output, salt, after, expected):
1896 def _processoutput(self, exitcode, output, salt, after, expected):
1894 # Merge the script output back into a unified test.
1897 # Merge the script output back into a unified test.
1895 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1898 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1896 if exitcode != 0:
1899 if exitcode != 0:
1897 warnonly = WARN_NO
1900 warnonly = WARN_NO
1898
1901
1899 pos = -1
1902 pos = -1
1900 postout = []
1903 postout = []
1901 for out_rawline in output:
1904 for out_rawline in output:
1902 out_line, cmd_line = out_rawline, None
1905 out_line, cmd_line = out_rawline, None
1903 if salt in out_rawline:
1906 if salt in out_rawline:
1904 out_line, cmd_line = out_rawline.split(salt, 1)
1907 out_line, cmd_line = out_rawline.split(salt, 1)
1905
1908
1906 pos, postout, warnonly = self._process_out_line(
1909 pos, postout, warnonly = self._process_out_line(
1907 out_line, pos, postout, expected, warnonly
1910 out_line, pos, postout, expected, warnonly
1908 )
1911 )
1909 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1912 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1910
1913
1911 if pos in after:
1914 if pos in after:
1912 postout += after.pop(pos)
1915 postout += after.pop(pos)
1913
1916
1914 if warnonly == WARN_YES:
1917 if warnonly == WARN_YES:
1915 exitcode = False # Set exitcode to warned.
1918 exitcode = False # Set exitcode to warned.
1916
1919
1917 return exitcode, postout
1920 return exitcode, postout
1918
1921
1919 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1922 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1920 while out_line:
1923 while out_line:
1921 if not out_line.endswith(b'\n'):
1924 if not out_line.endswith(b'\n'):
1922 out_line += b' (no-eol)\n'
1925 out_line += b' (no-eol)\n'
1923
1926
1924 # Find the expected output at the current position.
1927 # Find the expected output at the current position.
1925 els = [None]
1928 els = [None]
1926 if expected.get(pos, None):
1929 if expected.get(pos, None):
1927 els = expected[pos]
1930 els = expected[pos]
1928
1931
1929 optional = []
1932 optional = []
1930 for i, el in enumerate(els):
1933 for i, el in enumerate(els):
1931 r = False
1934 r = False
1932 if el:
1935 if el:
1933 r, exact = self.linematch(el, out_line)
1936 r, exact = self.linematch(el, out_line)
1934 if isinstance(r, str):
1937 if isinstance(r, str):
1935 if r == '-glob':
1938 if r == '-glob':
1936 out_line = ''.join(el.rsplit(' (glob)', 1))
1939 out_line = ''.join(el.rsplit(' (glob)', 1))
1937 r = '' # Warn only this line.
1940 r = '' # Warn only this line.
1938 elif r == "retry":
1941 elif r == "retry":
1939 postout.append(b' ' + el)
1942 postout.append(b' ' + el)
1940 else:
1943 else:
1941 log('\ninfo, unknown linematch result: %r\n' % r)
1944 log('\ninfo, unknown linematch result: %r\n' % r)
1942 r = False
1945 r = False
1943 if r:
1946 if r:
1944 els.pop(i)
1947 els.pop(i)
1945 break
1948 break
1946 if el:
1949 if el:
1947 if isoptional(el):
1950 if isoptional(el):
1948 optional.append(i)
1951 optional.append(i)
1949 else:
1952 else:
1950 m = optline.match(el)
1953 m = optline.match(el)
1951 if m:
1954 if m:
1952 conditions = [c for c in m.group(2).split(b' ')]
1955 conditions = [c for c in m.group(2).split(b' ')]
1953
1956
1954 if not self._iftest(conditions):
1957 if not self._iftest(conditions):
1955 optional.append(i)
1958 optional.append(i)
1956 if exact:
1959 if exact:
1957 # Don't allow line to be matches against a later
1960 # Don't allow line to be matches against a later
1958 # line in the output
1961 # line in the output
1959 els.pop(i)
1962 els.pop(i)
1960 break
1963 break
1961
1964
1962 if r:
1965 if r:
1963 if r == "retry":
1966 if r == "retry":
1964 continue
1967 continue
1965 # clean up any optional leftovers
1968 # clean up any optional leftovers
1966 for i in optional:
1969 for i in optional:
1967 postout.append(b' ' + els[i])
1970 postout.append(b' ' + els[i])
1968 for i in reversed(optional):
1971 for i in reversed(optional):
1969 del els[i]
1972 del els[i]
1970 postout.append(b' ' + el)
1973 postout.append(b' ' + el)
1971 else:
1974 else:
1972 if self.NEEDESCAPE(out_line):
1975 if self.NEEDESCAPE(out_line):
1973 out_line = TTest._stringescape(
1976 out_line = TTest._stringescape(
1974 b'%s (esc)\n' % out_line.rstrip(b'\n')
1977 b'%s (esc)\n' % out_line.rstrip(b'\n')
1975 )
1978 )
1976 postout.append(b' ' + out_line) # Let diff deal with it.
1979 postout.append(b' ' + out_line) # Let diff deal with it.
1977 if r != '': # If line failed.
1980 if r != '': # If line failed.
1978 warnonly = WARN_NO
1981 warnonly = WARN_NO
1979 elif warnonly == WARN_UNDEFINED:
1982 elif warnonly == WARN_UNDEFINED:
1980 warnonly = WARN_YES
1983 warnonly = WARN_YES
1981 break
1984 break
1982 else:
1985 else:
1983 # clean up any optional leftovers
1986 # clean up any optional leftovers
1984 while expected.get(pos, None):
1987 while expected.get(pos, None):
1985 el = expected[pos].pop(0)
1988 el = expected[pos].pop(0)
1986 if el:
1989 if el:
1987 if not isoptional(el):
1990 if not isoptional(el):
1988 m = optline.match(el)
1991 m = optline.match(el)
1989 if m:
1992 if m:
1990 conditions = [c for c in m.group(2).split(b' ')]
1993 conditions = [c for c in m.group(2).split(b' ')]
1991
1994
1992 if self._iftest(conditions):
1995 if self._iftest(conditions):
1993 # Don't append as optional line
1996 # Don't append as optional line
1994 continue
1997 continue
1995 else:
1998 else:
1996 continue
1999 continue
1997 postout.append(b' ' + el)
2000 postout.append(b' ' + el)
1998 return pos, postout, warnonly
2001 return pos, postout, warnonly
1999
2002
2000 def _process_cmd_line(self, cmd_line, pos, postout, after):
2003 def _process_cmd_line(self, cmd_line, pos, postout, after):
2001 """process a "command" part of a line from unified test output"""
2004 """process a "command" part of a line from unified test output"""
2002 if cmd_line:
2005 if cmd_line:
2003 # Add on last return code.
2006 # Add on last return code.
2004 ret = int(cmd_line.split()[1])
2007 ret = int(cmd_line.split()[1])
2005 if ret != 0:
2008 if ret != 0:
2006 postout.append(b' [%d]\n' % ret)
2009 postout.append(b' [%d]\n' % ret)
2007 if pos in after:
2010 if pos in after:
2008 # Merge in non-active test bits.
2011 # Merge in non-active test bits.
2009 postout += after.pop(pos)
2012 postout += after.pop(pos)
2010 pos = int(cmd_line.split()[0])
2013 pos = int(cmd_line.split()[0])
2011 return pos, postout
2014 return pos, postout
2012
2015
2013 @staticmethod
2016 @staticmethod
2014 def rematch(el, l):
2017 def rematch(el, l):
2015 try:
2018 try:
2016 # parse any flags at the beginning of the regex. Only 'i' is
2019 # parse any flags at the beginning of the regex. Only 'i' is
2017 # supported right now, but this should be easy to extend.
2020 # supported right now, but this should be easy to extend.
2018 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2021 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2019 flags = flags or b''
2022 flags = flags or b''
2020 el = flags + b'(?:' + el + b')'
2023 el = flags + b'(?:' + el + b')'
2021 # use \Z to ensure that the regex matches to the end of the string
2024 # use \Z to ensure that the regex matches to the end of the string
2022 if os.name == 'nt':
2025 if os.name == 'nt':
2023 return re.match(el + br'\r?\n\Z', l)
2026 return re.match(el + br'\r?\n\Z', l)
2024 return re.match(el + br'\n\Z', l)
2027 return re.match(el + br'\n\Z', l)
2025 except re.error:
2028 except re.error:
2026 # el is an invalid regex
2029 # el is an invalid regex
2027 return False
2030 return False
2028
2031
2029 @staticmethod
2032 @staticmethod
2030 def globmatch(el, l):
2033 def globmatch(el, l):
2031 # The only supported special characters are * and ? plus / which also
2034 # The only supported special characters are * and ? plus / which also
2032 # matches \ on windows. Escaping of these characters is supported.
2035 # matches \ on windows. Escaping of these characters is supported.
2033 if el + b'\n' == l:
2036 if el + b'\n' == l:
2034 if os.altsep:
2037 if os.altsep:
2035 # matching on "/" is not needed for this line
2038 # matching on "/" is not needed for this line
2036 for pat in checkcodeglobpats:
2039 for pat in checkcodeglobpats:
2037 if pat.match(el):
2040 if pat.match(el):
2038 return True
2041 return True
2039 return b'-glob'
2042 return b'-glob'
2040 return True
2043 return True
2041 el = el.replace(b'$LOCALIP', b'*')
2044 el = el.replace(b'$LOCALIP', b'*')
2042 i, n = 0, len(el)
2045 i, n = 0, len(el)
2043 res = b''
2046 res = b''
2044 while i < n:
2047 while i < n:
2045 c = el[i : i + 1]
2048 c = el[i : i + 1]
2046 i += 1
2049 i += 1
2047 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2050 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2048 res += el[i - 1 : i + 1]
2051 res += el[i - 1 : i + 1]
2049 i += 1
2052 i += 1
2050 elif c == b'*':
2053 elif c == b'*':
2051 res += b'.*'
2054 res += b'.*'
2052 elif c == b'?':
2055 elif c == b'?':
2053 res += b'.'
2056 res += b'.'
2054 elif c == b'/' and os.altsep:
2057 elif c == b'/' and os.altsep:
2055 res += b'[/\\\\]'
2058 res += b'[/\\\\]'
2056 else:
2059 else:
2057 res += re.escape(c)
2060 res += re.escape(c)
2058 return TTest.rematch(res, l)
2061 return TTest.rematch(res, l)
2059
2062
2060 def linematch(self, el, l):
2063 def linematch(self, el, l):
2061 if el == l: # perfect match (fast)
2064 if el == l: # perfect match (fast)
2062 return True, True
2065 return True, True
2063 retry = False
2066 retry = False
2064 if isoptional(el):
2067 if isoptional(el):
2065 retry = "retry"
2068 retry = "retry"
2066 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2069 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2067 else:
2070 else:
2068 m = optline.match(el)
2071 m = optline.match(el)
2069 if m:
2072 if m:
2070 conditions = [c for c in m.group(2).split(b' ')]
2073 conditions = [c for c in m.group(2).split(b' ')]
2071
2074
2072 el = m.group(1) + b"\n"
2075 el = m.group(1) + b"\n"
2073 if not self._iftest(conditions):
2076 if not self._iftest(conditions):
2074 # listed feature missing, should not match
2077 # listed feature missing, should not match
2075 return "retry", False
2078 return "retry", False
2076
2079
2077 if el.endswith(b" (esc)\n"):
2080 if el.endswith(b" (esc)\n"):
2078 if PYTHON3:
2081 if PYTHON3:
2079 el = el[:-7].decode('unicode_escape') + '\n'
2082 el = el[:-7].decode('unicode_escape') + '\n'
2080 el = el.encode('latin-1')
2083 el = el.encode('latin-1')
2081 else:
2084 else:
2082 el = el[:-7].decode('string-escape') + '\n'
2085 el = el[:-7].decode('string-escape') + '\n'
2083 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2086 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2084 return True, True
2087 return True, True
2085 if el.endswith(b" (re)\n"):
2088 if el.endswith(b" (re)\n"):
2086 return (TTest.rematch(el[:-6], l) or retry), False
2089 return (TTest.rematch(el[:-6], l) or retry), False
2087 if el.endswith(b" (glob)\n"):
2090 if el.endswith(b" (glob)\n"):
2088 # ignore '(glob)' added to l by 'replacements'
2091 # ignore '(glob)' added to l by 'replacements'
2089 if l.endswith(b" (glob)\n"):
2092 if l.endswith(b" (glob)\n"):
2090 l = l[:-8] + b"\n"
2093 l = l[:-8] + b"\n"
2091 return (TTest.globmatch(el[:-8], l) or retry), False
2094 return (TTest.globmatch(el[:-8], l) or retry), False
2092 if os.altsep:
2095 if os.altsep:
2093 _l = l.replace(b'\\', b'/')
2096 _l = l.replace(b'\\', b'/')
2094 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2097 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2095 return True, True
2098 return True, True
2096 return retry, True
2099 return retry, True
2097
2100
2098 @staticmethod
2101 @staticmethod
2099 def parsehghaveoutput(lines):
2102 def parsehghaveoutput(lines):
2100 """Parse hghave log lines.
2103 """Parse hghave log lines.
2101
2104
2102 Return tuple of lists (missing, failed):
2105 Return tuple of lists (missing, failed):
2103 * the missing/unknown features
2106 * the missing/unknown features
2104 * the features for which existence check failed"""
2107 * the features for which existence check failed"""
2105 missing = []
2108 missing = []
2106 failed = []
2109 failed = []
2107 for line in lines:
2110 for line in lines:
2108 if line.startswith(TTest.SKIPPED_PREFIX):
2111 if line.startswith(TTest.SKIPPED_PREFIX):
2109 line = line.splitlines()[0]
2112 line = line.splitlines()[0]
2110 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2113 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2111 elif line.startswith(TTest.FAILED_PREFIX):
2114 elif line.startswith(TTest.FAILED_PREFIX):
2112 line = line.splitlines()[0]
2115 line = line.splitlines()[0]
2113 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2116 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2114
2117
2115 return missing, failed
2118 return missing, failed
2116
2119
2117 @staticmethod
2120 @staticmethod
2118 def _escapef(m):
2121 def _escapef(m):
2119 return TTest.ESCAPEMAP[m.group(0)]
2122 return TTest.ESCAPEMAP[m.group(0)]
2120
2123
2121 @staticmethod
2124 @staticmethod
2122 def _stringescape(s):
2125 def _stringescape(s):
2123 return TTest.ESCAPESUB(TTest._escapef, s)
2126 return TTest.ESCAPESUB(TTest._escapef, s)
2124
2127
2125
2128
2126 iolock = threading.RLock()
2129 iolock = threading.RLock()
2127 firstlock = threading.RLock()
2130 firstlock = threading.RLock()
2128 firsterror = False
2131 firsterror = False
2129
2132
2130
2133
2131 class TestResult(unittest._TextTestResult):
2134 class TestResult(unittest._TextTestResult):
2132 """Holds results when executing via unittest."""
2135 """Holds results when executing via unittest."""
2133
2136
2134 # Don't worry too much about accessing the non-public _TextTestResult.
2137 # Don't worry too much about accessing the non-public _TextTestResult.
2135 # It is relatively common in Python testing tools.
2138 # It is relatively common in Python testing tools.
2136 def __init__(self, options, *args, **kwargs):
2139 def __init__(self, options, *args, **kwargs):
2137 super(TestResult, self).__init__(*args, **kwargs)
2140 super(TestResult, self).__init__(*args, **kwargs)
2138
2141
2139 self._options = options
2142 self._options = options
2140
2143
2141 # unittest.TestResult didn't have skipped until 2.7. We need to
2144 # unittest.TestResult didn't have skipped until 2.7. We need to
2142 # polyfill it.
2145 # polyfill it.
2143 self.skipped = []
2146 self.skipped = []
2144
2147
2145 # We have a custom "ignored" result that isn't present in any Python
2148 # We have a custom "ignored" result that isn't present in any Python
2146 # unittest implementation. It is very similar to skipped. It may make
2149 # unittest implementation. It is very similar to skipped. It may make
2147 # sense to map it into skip some day.
2150 # sense to map it into skip some day.
2148 self.ignored = []
2151 self.ignored = []
2149
2152
2150 self.times = []
2153 self.times = []
2151 self._firststarttime = None
2154 self._firststarttime = None
2152 # Data stored for the benefit of generating xunit reports.
2155 # Data stored for the benefit of generating xunit reports.
2153 self.successes = []
2156 self.successes = []
2154 self.faildata = {}
2157 self.faildata = {}
2155
2158
2156 if options.color == 'auto':
2159 if options.color == 'auto':
2157 self.color = pygmentspresent and self.stream.isatty()
2160 self.color = pygmentspresent and self.stream.isatty()
2158 elif options.color == 'never':
2161 elif options.color == 'never':
2159 self.color = False
2162 self.color = False
2160 else: # 'always', for testing purposes
2163 else: # 'always', for testing purposes
2161 self.color = pygmentspresent
2164 self.color = pygmentspresent
2162
2165
2163 def onStart(self, test):
2166 def onStart(self, test):
2164 """Can be overriden by custom TestResult"""
2167 """Can be overriden by custom TestResult"""
2165
2168
2166 def onEnd(self):
2169 def onEnd(self):
2167 """Can be overriden by custom TestResult"""
2170 """Can be overriden by custom TestResult"""
2168
2171
2169 def addFailure(self, test, reason):
2172 def addFailure(self, test, reason):
2170 self.failures.append((test, reason))
2173 self.failures.append((test, reason))
2171
2174
2172 if self._options.first:
2175 if self._options.first:
2173 self.stop()
2176 self.stop()
2174 else:
2177 else:
2175 with iolock:
2178 with iolock:
2176 if reason == "timed out":
2179 if reason == "timed out":
2177 self.stream.write('t')
2180 self.stream.write('t')
2178 else:
2181 else:
2179 if not self._options.nodiff:
2182 if not self._options.nodiff:
2180 self.stream.write('\n')
2183 self.stream.write('\n')
2181 # Exclude the '\n' from highlighting to lex correctly
2184 # Exclude the '\n' from highlighting to lex correctly
2182 formatted = 'ERROR: %s output changed\n' % test
2185 formatted = 'ERROR: %s output changed\n' % test
2183 self.stream.write(highlightmsg(formatted, self.color))
2186 self.stream.write(highlightmsg(formatted, self.color))
2184 self.stream.write('!')
2187 self.stream.write('!')
2185
2188
2186 self.stream.flush()
2189 self.stream.flush()
2187
2190
2188 def addSuccess(self, test):
2191 def addSuccess(self, test):
2189 with iolock:
2192 with iolock:
2190 super(TestResult, self).addSuccess(test)
2193 super(TestResult, self).addSuccess(test)
2191 self.successes.append(test)
2194 self.successes.append(test)
2192
2195
2193 def addError(self, test, err):
2196 def addError(self, test, err):
2194 super(TestResult, self).addError(test, err)
2197 super(TestResult, self).addError(test, err)
2195 if self._options.first:
2198 if self._options.first:
2196 self.stop()
2199 self.stop()
2197
2200
2198 # Polyfill.
2201 # Polyfill.
2199 def addSkip(self, test, reason):
2202 def addSkip(self, test, reason):
2200 self.skipped.append((test, reason))
2203 self.skipped.append((test, reason))
2201 with iolock:
2204 with iolock:
2202 if self.showAll:
2205 if self.showAll:
2203 self.stream.writeln('skipped %s' % reason)
2206 self.stream.writeln('skipped %s' % reason)
2204 else:
2207 else:
2205 self.stream.write('s')
2208 self.stream.write('s')
2206 self.stream.flush()
2209 self.stream.flush()
2207
2210
2208 def addIgnore(self, test, reason):
2211 def addIgnore(self, test, reason):
2209 self.ignored.append((test, reason))
2212 self.ignored.append((test, reason))
2210 with iolock:
2213 with iolock:
2211 if self.showAll:
2214 if self.showAll:
2212 self.stream.writeln('ignored %s' % reason)
2215 self.stream.writeln('ignored %s' % reason)
2213 else:
2216 else:
2214 if reason not in ('not retesting', "doesn't match keyword"):
2217 if reason not in ('not retesting', "doesn't match keyword"):
2215 self.stream.write('i')
2218 self.stream.write('i')
2216 else:
2219 else:
2217 self.testsRun += 1
2220 self.testsRun += 1
2218 self.stream.flush()
2221 self.stream.flush()
2219
2222
2220 def addOutputMismatch(self, test, ret, got, expected):
2223 def addOutputMismatch(self, test, ret, got, expected):
2221 """Record a mismatch in test output for a particular test."""
2224 """Record a mismatch in test output for a particular test."""
2222 if self.shouldStop or firsterror:
2225 if self.shouldStop or firsterror:
2223 # don't print, some other test case already failed and
2226 # don't print, some other test case already failed and
2224 # printed, we're just stale and probably failed due to our
2227 # printed, we're just stale and probably failed due to our
2225 # temp dir getting cleaned up.
2228 # temp dir getting cleaned up.
2226 return
2229 return
2227
2230
2228 accepted = False
2231 accepted = False
2229 lines = []
2232 lines = []
2230
2233
2231 with iolock:
2234 with iolock:
2232 if self._options.nodiff:
2235 if self._options.nodiff:
2233 pass
2236 pass
2234 elif self._options.view:
2237 elif self._options.view:
2235 v = self._options.view
2238 v = self._options.view
2236 subprocess.call(
2239 subprocess.call(
2237 r'"%s" "%s" "%s"'
2240 r'"%s" "%s" "%s"'
2238 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2241 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2239 shell=True,
2242 shell=True,
2240 )
2243 )
2241 else:
2244 else:
2242 servefail, lines = getdiff(
2245 servefail, lines = getdiff(
2243 expected, got, test.refpath, test.errpath
2246 expected, got, test.refpath, test.errpath
2244 )
2247 )
2245 self.stream.write('\n')
2248 self.stream.write('\n')
2246 for line in lines:
2249 for line in lines:
2247 line = highlightdiff(line, self.color)
2250 line = highlightdiff(line, self.color)
2248 if PYTHON3:
2251 if PYTHON3:
2249 self.stream.flush()
2252 self.stream.flush()
2250 self.stream.buffer.write(line)
2253 self.stream.buffer.write(line)
2251 self.stream.buffer.flush()
2254 self.stream.buffer.flush()
2252 else:
2255 else:
2253 self.stream.write(line)
2256 self.stream.write(line)
2254 self.stream.flush()
2257 self.stream.flush()
2255
2258
2256 if servefail:
2259 if servefail:
2257 raise test.failureException(
2260 raise test.failureException(
2258 'server failed to start (HGPORT=%s)' % test._startport
2261 'server failed to start (HGPORT=%s)' % test._startport
2259 )
2262 )
2260
2263
2261 # handle interactive prompt without releasing iolock
2264 # handle interactive prompt without releasing iolock
2262 if self._options.interactive:
2265 if self._options.interactive:
2263 if test.readrefout() != expected:
2266 if test.readrefout() != expected:
2264 self.stream.write(
2267 self.stream.write(
2265 'Reference output has changed (run again to prompt '
2268 'Reference output has changed (run again to prompt '
2266 'changes)'
2269 'changes)'
2267 )
2270 )
2268 else:
2271 else:
2269 self.stream.write('Accept this change? [y/N] ')
2272 self.stream.write('Accept this change? [y/N] ')
2270 self.stream.flush()
2273 self.stream.flush()
2271 answer = sys.stdin.readline().strip()
2274 answer = sys.stdin.readline().strip()
2272 if answer.lower() in ('y', 'yes'):
2275 if answer.lower() in ('y', 'yes'):
2273 if test.path.endswith(b'.t'):
2276 if test.path.endswith(b'.t'):
2274 rename(test.errpath, test.path)
2277 rename(test.errpath, test.path)
2275 else:
2278 else:
2276 rename(test.errpath, '%s.out' % test.path)
2279 rename(test.errpath, '%s.out' % test.path)
2277 accepted = True
2280 accepted = True
2278 if not accepted:
2281 if not accepted:
2279 self.faildata[test.name] = b''.join(lines)
2282 self.faildata[test.name] = b''.join(lines)
2280
2283
2281 return accepted
2284 return accepted
2282
2285
2283 def startTest(self, test):
2286 def startTest(self, test):
2284 super(TestResult, self).startTest(test)
2287 super(TestResult, self).startTest(test)
2285
2288
2286 # os.times module computes the user time and system time spent by
2289 # os.times module computes the user time and system time spent by
2287 # child's processes along with real elapsed time taken by a process.
2290 # child's processes along with real elapsed time taken by a process.
2288 # This module has one limitation. It can only work for Linux user
2291 # This module has one limitation. It can only work for Linux user
2289 # and not for Windows. Hence why we fall back to another function
2292 # and not for Windows. Hence why we fall back to another function
2290 # for wall time calculations.
2293 # for wall time calculations.
2291 test.started_times = os.times()
2294 test.started_times = os.times()
2292 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2295 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2293 test.started_time = time.time()
2296 test.started_time = time.time()
2294 if self._firststarttime is None: # thread racy but irrelevant
2297 if self._firststarttime is None: # thread racy but irrelevant
2295 self._firststarttime = test.started_time
2298 self._firststarttime = test.started_time
2296
2299
2297 def stopTest(self, test, interrupted=False):
2300 def stopTest(self, test, interrupted=False):
2298 super(TestResult, self).stopTest(test)
2301 super(TestResult, self).stopTest(test)
2299
2302
2300 test.stopped_times = os.times()
2303 test.stopped_times = os.times()
2301 stopped_time = time.time()
2304 stopped_time = time.time()
2302
2305
2303 starttime = test.started_times
2306 starttime = test.started_times
2304 endtime = test.stopped_times
2307 endtime = test.stopped_times
2305 origin = self._firststarttime
2308 origin = self._firststarttime
2306 self.times.append(
2309 self.times.append(
2307 (
2310 (
2308 test.name,
2311 test.name,
2309 endtime[2] - starttime[2], # user space CPU time
2312 endtime[2] - starttime[2], # user space CPU time
2310 endtime[3] - starttime[3], # sys space CPU time
2313 endtime[3] - starttime[3], # sys space CPU time
2311 stopped_time - test.started_time, # real time
2314 stopped_time - test.started_time, # real time
2312 test.started_time - origin, # start date in run context
2315 test.started_time - origin, # start date in run context
2313 stopped_time - origin, # end date in run context
2316 stopped_time - origin, # end date in run context
2314 )
2317 )
2315 )
2318 )
2316
2319
2317 if interrupted:
2320 if interrupted:
2318 with iolock:
2321 with iolock:
2319 self.stream.writeln(
2322 self.stream.writeln(
2320 'INTERRUPTED: %s (after %d seconds)'
2323 'INTERRUPTED: %s (after %d seconds)'
2321 % (test.name, self.times[-1][3])
2324 % (test.name, self.times[-1][3])
2322 )
2325 )
2323
2326
2324
2327
2325 def getTestResult():
2328 def getTestResult():
2326 """
2329 """
2327 Returns the relevant test result
2330 Returns the relevant test result
2328 """
2331 """
2329 if "CUSTOM_TEST_RESULT" in os.environ:
2332 if "CUSTOM_TEST_RESULT" in os.environ:
2330 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2333 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2331 return testresultmodule.TestResult
2334 return testresultmodule.TestResult
2332 else:
2335 else:
2333 return TestResult
2336 return TestResult
2334
2337
2335
2338
2336 class TestSuite(unittest.TestSuite):
2339 class TestSuite(unittest.TestSuite):
2337 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2340 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2338
2341
2339 def __init__(
2342 def __init__(
2340 self,
2343 self,
2341 testdir,
2344 testdir,
2342 jobs=1,
2345 jobs=1,
2343 whitelist=None,
2346 whitelist=None,
2344 blacklist=None,
2347 blacklist=None,
2345 keywords=None,
2348 keywords=None,
2346 loop=False,
2349 loop=False,
2347 runs_per_test=1,
2350 runs_per_test=1,
2348 loadtest=None,
2351 loadtest=None,
2349 showchannels=False,
2352 showchannels=False,
2350 *args,
2353 *args,
2351 **kwargs
2354 **kwargs
2352 ):
2355 ):
2353 """Create a new instance that can run tests with a configuration.
2356 """Create a new instance that can run tests with a configuration.
2354
2357
2355 testdir specifies the directory where tests are executed from. This
2358 testdir specifies the directory where tests are executed from. This
2356 is typically the ``tests`` directory from Mercurial's source
2359 is typically the ``tests`` directory from Mercurial's source
2357 repository.
2360 repository.
2358
2361
2359 jobs specifies the number of jobs to run concurrently. Each test
2362 jobs specifies the number of jobs to run concurrently. Each test
2360 executes on its own thread. Tests actually spawn new processes, so
2363 executes on its own thread. Tests actually spawn new processes, so
2361 state mutation should not be an issue.
2364 state mutation should not be an issue.
2362
2365
2363 If there is only one job, it will use the main thread.
2366 If there is only one job, it will use the main thread.
2364
2367
2365 whitelist and blacklist denote tests that have been whitelisted and
2368 whitelist and blacklist denote tests that have been whitelisted and
2366 blacklisted, respectively. These arguments don't belong in TestSuite.
2369 blacklisted, respectively. These arguments don't belong in TestSuite.
2367 Instead, whitelist and blacklist should be handled by the thing that
2370 Instead, whitelist and blacklist should be handled by the thing that
2368 populates the TestSuite with tests. They are present to preserve
2371 populates the TestSuite with tests. They are present to preserve
2369 backwards compatible behavior which reports skipped tests as part
2372 backwards compatible behavior which reports skipped tests as part
2370 of the results.
2373 of the results.
2371
2374
2372 keywords denotes key words that will be used to filter which tests
2375 keywords denotes key words that will be used to filter which tests
2373 to execute. This arguably belongs outside of TestSuite.
2376 to execute. This arguably belongs outside of TestSuite.
2374
2377
2375 loop denotes whether to loop over tests forever.
2378 loop denotes whether to loop over tests forever.
2376 """
2379 """
2377 super(TestSuite, self).__init__(*args, **kwargs)
2380 super(TestSuite, self).__init__(*args, **kwargs)
2378
2381
2379 self._jobs = jobs
2382 self._jobs = jobs
2380 self._whitelist = whitelist
2383 self._whitelist = whitelist
2381 self._blacklist = blacklist
2384 self._blacklist = blacklist
2382 self._keywords = keywords
2385 self._keywords = keywords
2383 self._loop = loop
2386 self._loop = loop
2384 self._runs_per_test = runs_per_test
2387 self._runs_per_test = runs_per_test
2385 self._loadtest = loadtest
2388 self._loadtest = loadtest
2386 self._showchannels = showchannels
2389 self._showchannels = showchannels
2387
2390
2388 def run(self, result):
2391 def run(self, result):
2389 # We have a number of filters that need to be applied. We do this
2392 # We have a number of filters that need to be applied. We do this
2390 # here instead of inside Test because it makes the running logic for
2393 # here instead of inside Test because it makes the running logic for
2391 # Test simpler.
2394 # Test simpler.
2392 tests = []
2395 tests = []
2393 num_tests = [0]
2396 num_tests = [0]
2394 for test in self._tests:
2397 for test in self._tests:
2395
2398
2396 def get():
2399 def get():
2397 num_tests[0] += 1
2400 num_tests[0] += 1
2398 if getattr(test, 'should_reload', False):
2401 if getattr(test, 'should_reload', False):
2399 return self._loadtest(test, num_tests[0])
2402 return self._loadtest(test, num_tests[0])
2400 return test
2403 return test
2401
2404
2402 if not os.path.exists(test.path):
2405 if not os.path.exists(test.path):
2403 result.addSkip(test, "Doesn't exist")
2406 result.addSkip(test, "Doesn't exist")
2404 continue
2407 continue
2405
2408
2406 is_whitelisted = self._whitelist and (
2409 is_whitelisted = self._whitelist and (
2407 test.relpath in self._whitelist or test.bname in self._whitelist
2410 test.relpath in self._whitelist or test.bname in self._whitelist
2408 )
2411 )
2409 if not is_whitelisted:
2412 if not is_whitelisted:
2410 is_blacklisted = self._blacklist and (
2413 is_blacklisted = self._blacklist and (
2411 test.relpath in self._blacklist
2414 test.relpath in self._blacklist
2412 or test.bname in self._blacklist
2415 or test.bname in self._blacklist
2413 )
2416 )
2414 if is_blacklisted:
2417 if is_blacklisted:
2415 result.addSkip(test, 'blacklisted')
2418 result.addSkip(test, 'blacklisted')
2416 continue
2419 continue
2417 if self._keywords:
2420 if self._keywords:
2418 with open(test.path, 'rb') as f:
2421 with open(test.path, 'rb') as f:
2419 t = f.read().lower() + test.bname.lower()
2422 t = f.read().lower() + test.bname.lower()
2420 ignored = False
2423 ignored = False
2421 for k in self._keywords.lower().split():
2424 for k in self._keywords.lower().split():
2422 if k not in t:
2425 if k not in t:
2423 result.addIgnore(test, "doesn't match keyword")
2426 result.addIgnore(test, "doesn't match keyword")
2424 ignored = True
2427 ignored = True
2425 break
2428 break
2426
2429
2427 if ignored:
2430 if ignored:
2428 continue
2431 continue
2429 for _ in xrange(self._runs_per_test):
2432 for _ in xrange(self._runs_per_test):
2430 tests.append(get())
2433 tests.append(get())
2431
2434
2432 runtests = list(tests)
2435 runtests = list(tests)
2433 done = queue.Queue()
2436 done = queue.Queue()
2434 running = 0
2437 running = 0
2435
2438
2436 channels = [""] * self._jobs
2439 channels = [""] * self._jobs
2437
2440
2438 def job(test, result):
2441 def job(test, result):
2439 for n, v in enumerate(channels):
2442 for n, v in enumerate(channels):
2440 if not v:
2443 if not v:
2441 channel = n
2444 channel = n
2442 break
2445 break
2443 else:
2446 else:
2444 raise ValueError('Could not find output channel')
2447 raise ValueError('Could not find output channel')
2445 channels[channel] = "=" + test.name[5:].split(".")[0]
2448 channels[channel] = "=" + test.name[5:].split(".")[0]
2446 try:
2449 try:
2447 test(result)
2450 test(result)
2448 done.put(None)
2451 done.put(None)
2449 except KeyboardInterrupt:
2452 except KeyboardInterrupt:
2450 pass
2453 pass
2451 except: # re-raises
2454 except: # re-raises
2452 done.put(('!', test, 'run-test raised an error, see traceback'))
2455 done.put(('!', test, 'run-test raised an error, see traceback'))
2453 raise
2456 raise
2454 finally:
2457 finally:
2455 try:
2458 try:
2456 channels[channel] = ''
2459 channels[channel] = ''
2457 except IndexError:
2460 except IndexError:
2458 pass
2461 pass
2459
2462
2460 def stat():
2463 def stat():
2461 count = 0
2464 count = 0
2462 while channels:
2465 while channels:
2463 d = '\n%03s ' % count
2466 d = '\n%03s ' % count
2464 for n, v in enumerate(channels):
2467 for n, v in enumerate(channels):
2465 if v:
2468 if v:
2466 d += v[0]
2469 d += v[0]
2467 channels[n] = v[1:] or '.'
2470 channels[n] = v[1:] or '.'
2468 else:
2471 else:
2469 d += ' '
2472 d += ' '
2470 d += ' '
2473 d += ' '
2471 with iolock:
2474 with iolock:
2472 sys.stdout.write(d + ' ')
2475 sys.stdout.write(d + ' ')
2473 sys.stdout.flush()
2476 sys.stdout.flush()
2474 for x in xrange(10):
2477 for x in xrange(10):
2475 if channels:
2478 if channels:
2476 time.sleep(0.1)
2479 time.sleep(0.1)
2477 count += 1
2480 count += 1
2478
2481
2479 stoppedearly = False
2482 stoppedearly = False
2480
2483
2481 if self._showchannels:
2484 if self._showchannels:
2482 statthread = threading.Thread(target=stat, name="stat")
2485 statthread = threading.Thread(target=stat, name="stat")
2483 statthread.start()
2486 statthread.start()
2484
2487
2485 try:
2488 try:
2486 while tests or running:
2489 while tests or running:
2487 if not done.empty() or running == self._jobs or not tests:
2490 if not done.empty() or running == self._jobs or not tests:
2488 try:
2491 try:
2489 done.get(True, 1)
2492 done.get(True, 1)
2490 running -= 1
2493 running -= 1
2491 if result and result.shouldStop:
2494 if result and result.shouldStop:
2492 stoppedearly = True
2495 stoppedearly = True
2493 break
2496 break
2494 except queue.Empty:
2497 except queue.Empty:
2495 continue
2498 continue
2496 if tests and not running == self._jobs:
2499 if tests and not running == self._jobs:
2497 test = tests.pop(0)
2500 test = tests.pop(0)
2498 if self._loop:
2501 if self._loop:
2499 if getattr(test, 'should_reload', False):
2502 if getattr(test, 'should_reload', False):
2500 num_tests[0] += 1
2503 num_tests[0] += 1
2501 tests.append(self._loadtest(test, num_tests[0]))
2504 tests.append(self._loadtest(test, num_tests[0]))
2502 else:
2505 else:
2503 tests.append(test)
2506 tests.append(test)
2504 if self._jobs == 1:
2507 if self._jobs == 1:
2505 job(test, result)
2508 job(test, result)
2506 else:
2509 else:
2507 t = threading.Thread(
2510 t = threading.Thread(
2508 target=job, name=test.name, args=(test, result)
2511 target=job, name=test.name, args=(test, result)
2509 )
2512 )
2510 t.start()
2513 t.start()
2511 running += 1
2514 running += 1
2512
2515
2513 # If we stop early we still need to wait on started tests to
2516 # If we stop early we still need to wait on started tests to
2514 # finish. Otherwise, there is a race between the test completing
2517 # finish. Otherwise, there is a race between the test completing
2515 # and the test's cleanup code running. This could result in the
2518 # and the test's cleanup code running. This could result in the
2516 # test reporting incorrect.
2519 # test reporting incorrect.
2517 if stoppedearly:
2520 if stoppedearly:
2518 while running:
2521 while running:
2519 try:
2522 try:
2520 done.get(True, 1)
2523 done.get(True, 1)
2521 running -= 1
2524 running -= 1
2522 except queue.Empty:
2525 except queue.Empty:
2523 continue
2526 continue
2524 except KeyboardInterrupt:
2527 except KeyboardInterrupt:
2525 for test in runtests:
2528 for test in runtests:
2526 test.abort()
2529 test.abort()
2527
2530
2528 channels = []
2531 channels = []
2529
2532
2530 return result
2533 return result
2531
2534
2532
2535
2533 # Save the most recent 5 wall-clock runtimes of each test to a
2536 # Save the most recent 5 wall-clock runtimes of each test to a
2534 # human-readable text file named .testtimes. Tests are sorted
2537 # human-readable text file named .testtimes. Tests are sorted
2535 # alphabetically, while times for each test are listed from oldest to
2538 # alphabetically, while times for each test are listed from oldest to
2536 # newest.
2539 # newest.
2537
2540
2538
2541
2539 def loadtimes(outputdir):
2542 def loadtimes(outputdir):
2540 times = []
2543 times = []
2541 try:
2544 try:
2542 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2545 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2543 for line in fp:
2546 for line in fp:
2544 m = re.match('(.*?) ([0-9. ]+)', line)
2547 m = re.match('(.*?) ([0-9. ]+)', line)
2545 times.append(
2548 times.append(
2546 (m.group(1), [float(t) for t in m.group(2).split()])
2549 (m.group(1), [float(t) for t in m.group(2).split()])
2547 )
2550 )
2548 except IOError as err:
2551 except IOError as err:
2549 if err.errno != errno.ENOENT:
2552 if err.errno != errno.ENOENT:
2550 raise
2553 raise
2551 return times
2554 return times
2552
2555
2553
2556
2554 def savetimes(outputdir, result):
2557 def savetimes(outputdir, result):
2555 saved = dict(loadtimes(outputdir))
2558 saved = dict(loadtimes(outputdir))
2556 maxruns = 5
2559 maxruns = 5
2557 skipped = {str(t[0]) for t in result.skipped}
2560 skipped = {str(t[0]) for t in result.skipped}
2558 for tdata in result.times:
2561 for tdata in result.times:
2559 test, real = tdata[0], tdata[3]
2562 test, real = tdata[0], tdata[3]
2560 if test not in skipped:
2563 if test not in skipped:
2561 ts = saved.setdefault(test, [])
2564 ts = saved.setdefault(test, [])
2562 ts.append(real)
2565 ts.append(real)
2563 ts[:] = ts[-maxruns:]
2566 ts[:] = ts[-maxruns:]
2564
2567
2565 fd, tmpname = tempfile.mkstemp(
2568 fd, tmpname = tempfile.mkstemp(
2566 prefix=b'.testtimes', dir=outputdir, text=True
2569 prefix=b'.testtimes', dir=outputdir, text=True
2567 )
2570 )
2568 with os.fdopen(fd, 'w') as fp:
2571 with os.fdopen(fd, 'w') as fp:
2569 for name, ts in sorted(saved.items()):
2572 for name, ts in sorted(saved.items()):
2570 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2573 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2571 timepath = os.path.join(outputdir, b'.testtimes')
2574 timepath = os.path.join(outputdir, b'.testtimes')
2572 try:
2575 try:
2573 os.unlink(timepath)
2576 os.unlink(timepath)
2574 except OSError:
2577 except OSError:
2575 pass
2578 pass
2576 try:
2579 try:
2577 os.rename(tmpname, timepath)
2580 os.rename(tmpname, timepath)
2578 except OSError:
2581 except OSError:
2579 pass
2582 pass
2580
2583
2581
2584
2582 class TextTestRunner(unittest.TextTestRunner):
2585 class TextTestRunner(unittest.TextTestRunner):
2583 """Custom unittest test runner that uses appropriate settings."""
2586 """Custom unittest test runner that uses appropriate settings."""
2584
2587
2585 def __init__(self, runner, *args, **kwargs):
2588 def __init__(self, runner, *args, **kwargs):
2586 super(TextTestRunner, self).__init__(*args, **kwargs)
2589 super(TextTestRunner, self).__init__(*args, **kwargs)
2587
2590
2588 self._runner = runner
2591 self._runner = runner
2589
2592
2590 self._result = getTestResult()(
2593 self._result = getTestResult()(
2591 self._runner.options, self.stream, self.descriptions, self.verbosity
2594 self._runner.options, self.stream, self.descriptions, self.verbosity
2592 )
2595 )
2593
2596
2594 def listtests(self, test):
2597 def listtests(self, test):
2595 test = sorted(test, key=lambda t: t.name)
2598 test = sorted(test, key=lambda t: t.name)
2596
2599
2597 self._result.onStart(test)
2600 self._result.onStart(test)
2598
2601
2599 for t in test:
2602 for t in test:
2600 print(t.name)
2603 print(t.name)
2601 self._result.addSuccess(t)
2604 self._result.addSuccess(t)
2602
2605
2603 if self._runner.options.xunit:
2606 if self._runner.options.xunit:
2604 with open(self._runner.options.xunit, "wb") as xuf:
2607 with open(self._runner.options.xunit, "wb") as xuf:
2605 self._writexunit(self._result, xuf)
2608 self._writexunit(self._result, xuf)
2606
2609
2607 if self._runner.options.json:
2610 if self._runner.options.json:
2608 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2611 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2609 with open(jsonpath, 'w') as fp:
2612 with open(jsonpath, 'w') as fp:
2610 self._writejson(self._result, fp)
2613 self._writejson(self._result, fp)
2611
2614
2612 return self._result
2615 return self._result
2613
2616
2614 def run(self, test):
2617 def run(self, test):
2615 self._result.onStart(test)
2618 self._result.onStart(test)
2616 test(self._result)
2619 test(self._result)
2617
2620
2618 failed = len(self._result.failures)
2621 failed = len(self._result.failures)
2619 skipped = len(self._result.skipped)
2622 skipped = len(self._result.skipped)
2620 ignored = len(self._result.ignored)
2623 ignored = len(self._result.ignored)
2621
2624
2622 with iolock:
2625 with iolock:
2623 self.stream.writeln('')
2626 self.stream.writeln('')
2624
2627
2625 if not self._runner.options.noskips:
2628 if not self._runner.options.noskips:
2626 for test, msg in sorted(
2629 for test, msg in sorted(
2627 self._result.skipped, key=lambda s: s[0].name
2630 self._result.skipped, key=lambda s: s[0].name
2628 ):
2631 ):
2629 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2632 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2630 msg = highlightmsg(formatted, self._result.color)
2633 msg = highlightmsg(formatted, self._result.color)
2631 self.stream.write(msg)
2634 self.stream.write(msg)
2632 for test, msg in sorted(
2635 for test, msg in sorted(
2633 self._result.failures, key=lambda f: f[0].name
2636 self._result.failures, key=lambda f: f[0].name
2634 ):
2637 ):
2635 formatted = 'Failed %s: %s\n' % (test.name, msg)
2638 formatted = 'Failed %s: %s\n' % (test.name, msg)
2636 self.stream.write(highlightmsg(formatted, self._result.color))
2639 self.stream.write(highlightmsg(formatted, self._result.color))
2637 for test, msg in sorted(
2640 for test, msg in sorted(
2638 self._result.errors, key=lambda e: e[0].name
2641 self._result.errors, key=lambda e: e[0].name
2639 ):
2642 ):
2640 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2643 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2641
2644
2642 if self._runner.options.xunit:
2645 if self._runner.options.xunit:
2643 with open(self._runner.options.xunit, "wb") as xuf:
2646 with open(self._runner.options.xunit, "wb") as xuf:
2644 self._writexunit(self._result, xuf)
2647 self._writexunit(self._result, xuf)
2645
2648
2646 if self._runner.options.json:
2649 if self._runner.options.json:
2647 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2650 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2648 with open(jsonpath, 'w') as fp:
2651 with open(jsonpath, 'w') as fp:
2649 self._writejson(self._result, fp)
2652 self._writejson(self._result, fp)
2650
2653
2651 self._runner._checkhglib('Tested')
2654 self._runner._checkhglib('Tested')
2652
2655
2653 savetimes(self._runner._outputdir, self._result)
2656 savetimes(self._runner._outputdir, self._result)
2654
2657
2655 if failed and self._runner.options.known_good_rev:
2658 if failed and self._runner.options.known_good_rev:
2656 self._bisecttests(t for t, m in self._result.failures)
2659 self._bisecttests(t for t, m in self._result.failures)
2657 self.stream.writeln(
2660 self.stream.writeln(
2658 '# Ran %d tests, %d skipped, %d failed.'
2661 '# Ran %d tests, %d skipped, %d failed.'
2659 % (self._result.testsRun, skipped + ignored, failed)
2662 % (self._result.testsRun, skipped + ignored, failed)
2660 )
2663 )
2661 if failed:
2664 if failed:
2662 self.stream.writeln(
2665 self.stream.writeln(
2663 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2666 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2664 )
2667 )
2665 if self._runner.options.time:
2668 if self._runner.options.time:
2666 self.printtimes(self._result.times)
2669 self.printtimes(self._result.times)
2667
2670
2668 if self._runner.options.exceptions:
2671 if self._runner.options.exceptions:
2669 exceptions = aggregateexceptions(
2672 exceptions = aggregateexceptions(
2670 os.path.join(self._runner._outputdir, b'exceptions')
2673 os.path.join(self._runner._outputdir, b'exceptions')
2671 )
2674 )
2672
2675
2673 self.stream.writeln('Exceptions Report:')
2676 self.stream.writeln('Exceptions Report:')
2674 self.stream.writeln(
2677 self.stream.writeln(
2675 '%d total from %d frames'
2678 '%d total from %d frames'
2676 % (exceptions['total'], len(exceptions['exceptioncounts']))
2679 % (exceptions['total'], len(exceptions['exceptioncounts']))
2677 )
2680 )
2678 combined = exceptions['combined']
2681 combined = exceptions['combined']
2679 for key in sorted(combined, key=combined.get, reverse=True):
2682 for key in sorted(combined, key=combined.get, reverse=True):
2680 frame, line, exc = key
2683 frame, line, exc = key
2681 totalcount, testcount, leastcount, leasttest = combined[key]
2684 totalcount, testcount, leastcount, leasttest = combined[key]
2682
2685
2683 self.stream.writeln(
2686 self.stream.writeln(
2684 '%d (%d tests)\t%s: %s (%s - %d total)'
2687 '%d (%d tests)\t%s: %s (%s - %d total)'
2685 % (
2688 % (
2686 totalcount,
2689 totalcount,
2687 testcount,
2690 testcount,
2688 frame,
2691 frame,
2689 exc,
2692 exc,
2690 leasttest,
2693 leasttest,
2691 leastcount,
2694 leastcount,
2692 )
2695 )
2693 )
2696 )
2694
2697
2695 self.stream.flush()
2698 self.stream.flush()
2696
2699
2697 return self._result
2700 return self._result
2698
2701
2699 def _bisecttests(self, tests):
2702 def _bisecttests(self, tests):
2700 bisectcmd = ['hg', 'bisect']
2703 bisectcmd = ['hg', 'bisect']
2701 bisectrepo = self._runner.options.bisect_repo
2704 bisectrepo = self._runner.options.bisect_repo
2702 if bisectrepo:
2705 if bisectrepo:
2703 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2706 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2704
2707
2705 def pread(args):
2708 def pread(args):
2706 env = os.environ.copy()
2709 env = os.environ.copy()
2707 env['HGPLAIN'] = '1'
2710 env['HGPLAIN'] = '1'
2708 p = subprocess.Popen(
2711 p = subprocess.Popen(
2709 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2712 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2710 )
2713 )
2711 data = p.stdout.read()
2714 data = p.stdout.read()
2712 p.wait()
2715 p.wait()
2713 return data
2716 return data
2714
2717
2715 for test in tests:
2718 for test in tests:
2716 pread(bisectcmd + ['--reset']),
2719 pread(bisectcmd + ['--reset']),
2717 pread(bisectcmd + ['--bad', '.'])
2720 pread(bisectcmd + ['--bad', '.'])
2718 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2721 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2719 # TODO: we probably need to forward more options
2722 # TODO: we probably need to forward more options
2720 # that alter hg's behavior inside the tests.
2723 # that alter hg's behavior inside the tests.
2721 opts = ''
2724 opts = ''
2722 withhg = self._runner.options.with_hg
2725 withhg = self._runner.options.with_hg
2723 if withhg:
2726 if withhg:
2724 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2727 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2725 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2728 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2726 data = pread(bisectcmd + ['--command', rtc])
2729 data = pread(bisectcmd + ['--command', rtc])
2727 m = re.search(
2730 m = re.search(
2728 (
2731 (
2729 br'\nThe first (?P<goodbad>bad|good) revision '
2732 br'\nThe first (?P<goodbad>bad|good) revision '
2730 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2733 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2731 br'summary: +(?P<summary>[^\n]+)\n'
2734 br'summary: +(?P<summary>[^\n]+)\n'
2732 ),
2735 ),
2733 data,
2736 data,
2734 (re.MULTILINE | re.DOTALL),
2737 (re.MULTILINE | re.DOTALL),
2735 )
2738 )
2736 if m is None:
2739 if m is None:
2737 self.stream.writeln(
2740 self.stream.writeln(
2738 'Failed to identify failure point for %s' % test
2741 'Failed to identify failure point for %s' % test
2739 )
2742 )
2740 continue
2743 continue
2741 dat = m.groupdict()
2744 dat = m.groupdict()
2742 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2745 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2743 self.stream.writeln(
2746 self.stream.writeln(
2744 '%s %s by %s (%s)'
2747 '%s %s by %s (%s)'
2745 % (
2748 % (
2746 test,
2749 test,
2747 verb,
2750 verb,
2748 dat['node'].decode('ascii'),
2751 dat['node'].decode('ascii'),
2749 dat['summary'].decode('utf8', 'ignore'),
2752 dat['summary'].decode('utf8', 'ignore'),
2750 )
2753 )
2751 )
2754 )
2752
2755
2753 def printtimes(self, times):
2756 def printtimes(self, times):
2754 # iolock held by run
2757 # iolock held by run
2755 self.stream.writeln('# Producing time report')
2758 self.stream.writeln('# Producing time report')
2756 times.sort(key=lambda t: (t[3]))
2759 times.sort(key=lambda t: (t[3]))
2757 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2760 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2758 self.stream.writeln(
2761 self.stream.writeln(
2759 '%-7s %-7s %-7s %-7s %-7s %s'
2762 '%-7s %-7s %-7s %-7s %-7s %s'
2760 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2763 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2761 )
2764 )
2762 for tdata in times:
2765 for tdata in times:
2763 test = tdata[0]
2766 test = tdata[0]
2764 cuser, csys, real, start, end = tdata[1:6]
2767 cuser, csys, real, start, end = tdata[1:6]
2765 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2768 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2766
2769
2767 @staticmethod
2770 @staticmethod
2768 def _writexunit(result, outf):
2771 def _writexunit(result, outf):
2769 # See http://llg.cubic.org/docs/junit/ for a reference.
2772 # See http://llg.cubic.org/docs/junit/ for a reference.
2770 timesd = {t[0]: t[3] for t in result.times}
2773 timesd = {t[0]: t[3] for t in result.times}
2771 doc = minidom.Document()
2774 doc = minidom.Document()
2772 s = doc.createElement('testsuite')
2775 s = doc.createElement('testsuite')
2773 s.setAttribute('errors', "0") # TODO
2776 s.setAttribute('errors', "0") # TODO
2774 s.setAttribute('failures', str(len(result.failures)))
2777 s.setAttribute('failures', str(len(result.failures)))
2775 s.setAttribute('name', 'run-tests')
2778 s.setAttribute('name', 'run-tests')
2776 s.setAttribute(
2779 s.setAttribute(
2777 'skipped', str(len(result.skipped) + len(result.ignored))
2780 'skipped', str(len(result.skipped) + len(result.ignored))
2778 )
2781 )
2779 s.setAttribute('tests', str(result.testsRun))
2782 s.setAttribute('tests', str(result.testsRun))
2780 doc.appendChild(s)
2783 doc.appendChild(s)
2781 for tc in result.successes:
2784 for tc in result.successes:
2782 t = doc.createElement('testcase')
2785 t = doc.createElement('testcase')
2783 t.setAttribute('name', tc.name)
2786 t.setAttribute('name', tc.name)
2784 tctime = timesd.get(tc.name)
2787 tctime = timesd.get(tc.name)
2785 if tctime is not None:
2788 if tctime is not None:
2786 t.setAttribute('time', '%.3f' % tctime)
2789 t.setAttribute('time', '%.3f' % tctime)
2787 s.appendChild(t)
2790 s.appendChild(t)
2788 for tc, err in sorted(result.faildata.items()):
2791 for tc, err in sorted(result.faildata.items()):
2789 t = doc.createElement('testcase')
2792 t = doc.createElement('testcase')
2790 t.setAttribute('name', tc)
2793 t.setAttribute('name', tc)
2791 tctime = timesd.get(tc)
2794 tctime = timesd.get(tc)
2792 if tctime is not None:
2795 if tctime is not None:
2793 t.setAttribute('time', '%.3f' % tctime)
2796 t.setAttribute('time', '%.3f' % tctime)
2794 # createCDATASection expects a unicode or it will
2797 # createCDATASection expects a unicode or it will
2795 # convert using default conversion rules, which will
2798 # convert using default conversion rules, which will
2796 # fail if string isn't ASCII.
2799 # fail if string isn't ASCII.
2797 err = cdatasafe(err).decode('utf-8', 'replace')
2800 err = cdatasafe(err).decode('utf-8', 'replace')
2798 cd = doc.createCDATASection(err)
2801 cd = doc.createCDATASection(err)
2799 # Use 'failure' here instead of 'error' to match errors = 0,
2802 # Use 'failure' here instead of 'error' to match errors = 0,
2800 # failures = len(result.failures) in the testsuite element.
2803 # failures = len(result.failures) in the testsuite element.
2801 failelem = doc.createElement('failure')
2804 failelem = doc.createElement('failure')
2802 failelem.setAttribute('message', 'output changed')
2805 failelem.setAttribute('message', 'output changed')
2803 failelem.setAttribute('type', 'output-mismatch')
2806 failelem.setAttribute('type', 'output-mismatch')
2804 failelem.appendChild(cd)
2807 failelem.appendChild(cd)
2805 t.appendChild(failelem)
2808 t.appendChild(failelem)
2806 s.appendChild(t)
2809 s.appendChild(t)
2807 for tc, message in result.skipped:
2810 for tc, message in result.skipped:
2808 # According to the schema, 'skipped' has no attributes. So store
2811 # According to the schema, 'skipped' has no attributes. So store
2809 # the skip message as a text node instead.
2812 # the skip message as a text node instead.
2810 t = doc.createElement('testcase')
2813 t = doc.createElement('testcase')
2811 t.setAttribute('name', tc.name)
2814 t.setAttribute('name', tc.name)
2812 binmessage = message.encode('utf-8')
2815 binmessage = message.encode('utf-8')
2813 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2816 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2814 cd = doc.createCDATASection(message)
2817 cd = doc.createCDATASection(message)
2815 skipelem = doc.createElement('skipped')
2818 skipelem = doc.createElement('skipped')
2816 skipelem.appendChild(cd)
2819 skipelem.appendChild(cd)
2817 t.appendChild(skipelem)
2820 t.appendChild(skipelem)
2818 s.appendChild(t)
2821 s.appendChild(t)
2819 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2822 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2820
2823
2821 @staticmethod
2824 @staticmethod
2822 def _writejson(result, outf):
2825 def _writejson(result, outf):
2823 timesd = {}
2826 timesd = {}
2824 for tdata in result.times:
2827 for tdata in result.times:
2825 test = tdata[0]
2828 test = tdata[0]
2826 timesd[test] = tdata[1:]
2829 timesd[test] = tdata[1:]
2827
2830
2828 outcome = {}
2831 outcome = {}
2829 groups = [
2832 groups = [
2830 ('success', ((tc, None) for tc in result.successes)),
2833 ('success', ((tc, None) for tc in result.successes)),
2831 ('failure', result.failures),
2834 ('failure', result.failures),
2832 ('skip', result.skipped),
2835 ('skip', result.skipped),
2833 ]
2836 ]
2834 for res, testcases in groups:
2837 for res, testcases in groups:
2835 for tc, __ in testcases:
2838 for tc, __ in testcases:
2836 if tc.name in timesd:
2839 if tc.name in timesd:
2837 diff = result.faildata.get(tc.name, b'')
2840 diff = result.faildata.get(tc.name, b'')
2838 try:
2841 try:
2839 diff = diff.decode('unicode_escape')
2842 diff = diff.decode('unicode_escape')
2840 except UnicodeDecodeError as e:
2843 except UnicodeDecodeError as e:
2841 diff = '%r decoding diff, sorry' % e
2844 diff = '%r decoding diff, sorry' % e
2842 tres = {
2845 tres = {
2843 'result': res,
2846 'result': res,
2844 'time': ('%0.3f' % timesd[tc.name][2]),
2847 'time': ('%0.3f' % timesd[tc.name][2]),
2845 'cuser': ('%0.3f' % timesd[tc.name][0]),
2848 'cuser': ('%0.3f' % timesd[tc.name][0]),
2846 'csys': ('%0.3f' % timesd[tc.name][1]),
2849 'csys': ('%0.3f' % timesd[tc.name][1]),
2847 'start': ('%0.3f' % timesd[tc.name][3]),
2850 'start': ('%0.3f' % timesd[tc.name][3]),
2848 'end': ('%0.3f' % timesd[tc.name][4]),
2851 'end': ('%0.3f' % timesd[tc.name][4]),
2849 'diff': diff,
2852 'diff': diff,
2850 }
2853 }
2851 else:
2854 else:
2852 # blacklisted test
2855 # blacklisted test
2853 tres = {'result': res}
2856 tres = {'result': res}
2854
2857
2855 outcome[tc.name] = tres
2858 outcome[tc.name] = tres
2856 jsonout = json.dumps(
2859 jsonout = json.dumps(
2857 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2860 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2858 )
2861 )
2859 outf.writelines(("testreport =", jsonout))
2862 outf.writelines(("testreport =", jsonout))
2860
2863
2861
2864
2862 def sorttests(testdescs, previoustimes, shuffle=False):
2865 def sorttests(testdescs, previoustimes, shuffle=False):
2863 """Do an in-place sort of tests."""
2866 """Do an in-place sort of tests."""
2864 if shuffle:
2867 if shuffle:
2865 random.shuffle(testdescs)
2868 random.shuffle(testdescs)
2866 return
2869 return
2867
2870
2868 if previoustimes:
2871 if previoustimes:
2869
2872
2870 def sortkey(f):
2873 def sortkey(f):
2871 f = f['path']
2874 f = f['path']
2872 if f in previoustimes:
2875 if f in previoustimes:
2873 # Use most recent time as estimate
2876 # Use most recent time as estimate
2874 return -(previoustimes[f][-1])
2877 return -(previoustimes[f][-1])
2875 else:
2878 else:
2876 # Default to a rather arbitrary value of 1 second for new tests
2879 # Default to a rather arbitrary value of 1 second for new tests
2877 return -1.0
2880 return -1.0
2878
2881
2879 else:
2882 else:
2880 # keywords for slow tests
2883 # keywords for slow tests
2881 slow = {
2884 slow = {
2882 b'svn': 10,
2885 b'svn': 10,
2883 b'cvs': 10,
2886 b'cvs': 10,
2884 b'hghave': 10,
2887 b'hghave': 10,
2885 b'largefiles-update': 10,
2888 b'largefiles-update': 10,
2886 b'run-tests': 10,
2889 b'run-tests': 10,
2887 b'corruption': 10,
2890 b'corruption': 10,
2888 b'race': 10,
2891 b'race': 10,
2889 b'i18n': 10,
2892 b'i18n': 10,
2890 b'check': 100,
2893 b'check': 100,
2891 b'gendoc': 100,
2894 b'gendoc': 100,
2892 b'contrib-perf': 200,
2895 b'contrib-perf': 200,
2893 b'merge-combination': 100,
2896 b'merge-combination': 100,
2894 }
2897 }
2895 perf = {}
2898 perf = {}
2896
2899
2897 def sortkey(f):
2900 def sortkey(f):
2898 # run largest tests first, as they tend to take the longest
2901 # run largest tests first, as they tend to take the longest
2899 f = f['path']
2902 f = f['path']
2900 try:
2903 try:
2901 return perf[f]
2904 return perf[f]
2902 except KeyError:
2905 except KeyError:
2903 try:
2906 try:
2904 val = -os.stat(f).st_size
2907 val = -os.stat(f).st_size
2905 except OSError as e:
2908 except OSError as e:
2906 if e.errno != errno.ENOENT:
2909 if e.errno != errno.ENOENT:
2907 raise
2910 raise
2908 perf[f] = -1e9 # file does not exist, tell early
2911 perf[f] = -1e9 # file does not exist, tell early
2909 return -1e9
2912 return -1e9
2910 for kw, mul in slow.items():
2913 for kw, mul in slow.items():
2911 if kw in f:
2914 if kw in f:
2912 val *= mul
2915 val *= mul
2913 if f.endswith(b'.py'):
2916 if f.endswith(b'.py'):
2914 val /= 10.0
2917 val /= 10.0
2915 perf[f] = val / 1000.0
2918 perf[f] = val / 1000.0
2916 return perf[f]
2919 return perf[f]
2917
2920
2918 testdescs.sort(key=sortkey)
2921 testdescs.sort(key=sortkey)
2919
2922
2920
2923
2921 class TestRunner(object):
2924 class TestRunner(object):
2922 """Holds context for executing tests.
2925 """Holds context for executing tests.
2923
2926
2924 Tests rely on a lot of state. This object holds it for them.
2927 Tests rely on a lot of state. This object holds it for them.
2925 """
2928 """
2926
2929
2927 # Programs required to run tests.
2930 # Programs required to run tests.
2928 REQUIREDTOOLS = [
2931 REQUIREDTOOLS = [
2929 b'diff',
2932 b'diff',
2930 b'grep',
2933 b'grep',
2931 b'unzip',
2934 b'unzip',
2932 b'gunzip',
2935 b'gunzip',
2933 b'bunzip2',
2936 b'bunzip2',
2934 b'sed',
2937 b'sed',
2935 ]
2938 ]
2936
2939
2937 # Maps file extensions to test class.
2940 # Maps file extensions to test class.
2938 TESTTYPES = [
2941 TESTTYPES = [
2939 (b'.py', PythonTest),
2942 (b'.py', PythonTest),
2940 (b'.t', TTest),
2943 (b'.t', TTest),
2941 ]
2944 ]
2942
2945
2943 def __init__(self):
2946 def __init__(self):
2944 self.options = None
2947 self.options = None
2945 self._hgroot = None
2948 self._hgroot = None
2946 self._testdir = None
2949 self._testdir = None
2947 self._outputdir = None
2950 self._outputdir = None
2948 self._hgtmp = None
2951 self._hgtmp = None
2949 self._installdir = None
2952 self._installdir = None
2950 self._bindir = None
2953 self._bindir = None
2951 self._tmpbindir = None
2954 self._tmpbindir = None
2952 self._pythondir = None
2955 self._pythondir = None
2953 self._coveragefile = None
2956 self._coveragefile = None
2954 self._createdfiles = []
2957 self._createdfiles = []
2955 self._hgcommand = None
2958 self._hgcommand = None
2956 self._hgpath = None
2959 self._hgpath = None
2957 self._portoffset = 0
2960 self._portoffset = 0
2958 self._ports = {}
2961 self._ports = {}
2959
2962
2960 def run(self, args, parser=None):
2963 def run(self, args, parser=None):
2961 """Run the test suite."""
2964 """Run the test suite."""
2962 oldmask = os.umask(0o22)
2965 oldmask = os.umask(0o22)
2963 try:
2966 try:
2964 parser = parser or getparser()
2967 parser = parser or getparser()
2965 options = parseargs(args, parser)
2968 options = parseargs(args, parser)
2966 tests = [_sys2bytes(a) for a in options.tests]
2969 tests = [_sys2bytes(a) for a in options.tests]
2967 if options.test_list is not None:
2970 if options.test_list is not None:
2968 for listfile in options.test_list:
2971 for listfile in options.test_list:
2969 with open(listfile, 'rb') as f:
2972 with open(listfile, 'rb') as f:
2970 tests.extend(t for t in f.read().splitlines() if t)
2973 tests.extend(t for t in f.read().splitlines() if t)
2971 self.options = options
2974 self.options = options
2972
2975
2973 self._checktools()
2976 self._checktools()
2974 testdescs = self.findtests(tests)
2977 testdescs = self.findtests(tests)
2975 if options.profile_runner:
2978 if options.profile_runner:
2976 import statprof
2979 import statprof
2977
2980
2978 statprof.start()
2981 statprof.start()
2979 result = self._run(testdescs)
2982 result = self._run(testdescs)
2980 if options.profile_runner:
2983 if options.profile_runner:
2981 statprof.stop()
2984 statprof.stop()
2982 statprof.display()
2985 statprof.display()
2983 return result
2986 return result
2984
2987
2985 finally:
2988 finally:
2986 os.umask(oldmask)
2989 os.umask(oldmask)
2987
2990
2988 def _run(self, testdescs):
2991 def _run(self, testdescs):
2989 testdir = getcwdb()
2992 testdir = getcwdb()
2990 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2993 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2991 # assume all tests in same folder for now
2994 # assume all tests in same folder for now
2992 if testdescs:
2995 if testdescs:
2993 pathname = os.path.dirname(testdescs[0]['path'])
2996 pathname = os.path.dirname(testdescs[0]['path'])
2994 if pathname:
2997 if pathname:
2995 testdir = os.path.join(testdir, pathname)
2998 testdir = os.path.join(testdir, pathname)
2996 self._testdir = osenvironb[b'TESTDIR'] = testdir
2999 self._testdir = osenvironb[b'TESTDIR'] = testdir
2997 if self.options.outputdir:
3000 if self.options.outputdir:
2998 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3001 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
2999 else:
3002 else:
3000 self._outputdir = getcwdb()
3003 self._outputdir = getcwdb()
3001 if testdescs and pathname:
3004 if testdescs and pathname:
3002 self._outputdir = os.path.join(self._outputdir, pathname)
3005 self._outputdir = os.path.join(self._outputdir, pathname)
3003 previoustimes = {}
3006 previoustimes = {}
3004 if self.options.order_by_runtime:
3007 if self.options.order_by_runtime:
3005 previoustimes = dict(loadtimes(self._outputdir))
3008 previoustimes = dict(loadtimes(self._outputdir))
3006 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3009 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3007
3010
3008 if 'PYTHONHASHSEED' not in os.environ:
3011 if 'PYTHONHASHSEED' not in os.environ:
3009 # use a random python hash seed all the time
3012 # use a random python hash seed all the time
3010 # we do the randomness ourself to know what seed is used
3013 # we do the randomness ourself to know what seed is used
3011 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3014 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3012
3015
3013 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3016 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3014 # by default, causing thrashing on high-cpu-count systems.
3017 # by default, causing thrashing on high-cpu-count systems.
3015 # Setting its limit to 3 during tests should still let us uncover
3018 # Setting its limit to 3 during tests should still let us uncover
3016 # multi-threading bugs while keeping the thrashing reasonable.
3019 # multi-threading bugs while keeping the thrashing reasonable.
3017 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3020 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3018
3021
3019 if self.options.tmpdir:
3022 if self.options.tmpdir:
3020 self.options.keep_tmpdir = True
3023 self.options.keep_tmpdir = True
3021 tmpdir = _sys2bytes(self.options.tmpdir)
3024 tmpdir = _sys2bytes(self.options.tmpdir)
3022 if os.path.exists(tmpdir):
3025 if os.path.exists(tmpdir):
3023 # Meaning of tmpdir has changed since 1.3: we used to create
3026 # Meaning of tmpdir has changed since 1.3: we used to create
3024 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3027 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3025 # tmpdir already exists.
3028 # tmpdir already exists.
3026 print("error: temp dir %r already exists" % tmpdir)
3029 print("error: temp dir %r already exists" % tmpdir)
3027 return 1
3030 return 1
3028
3031
3029 os.makedirs(tmpdir)
3032 os.makedirs(tmpdir)
3030 else:
3033 else:
3031 d = None
3034 d = None
3032 if os.name == 'nt':
3035 if os.name == 'nt':
3033 # without this, we get the default temp dir location, but
3036 # without this, we get the default temp dir location, but
3034 # in all lowercase, which causes troubles with paths (issue3490)
3037 # in all lowercase, which causes troubles with paths (issue3490)
3035 d = osenvironb.get(b'TMP', None)
3038 d = osenvironb.get(b'TMP', None)
3036 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3039 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3037
3040
3038 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3041 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3039
3042
3040 if self.options.with_hg:
3043 if self.options.with_hg:
3041 self._installdir = None
3044 self._installdir = None
3042 whg = self.options.with_hg
3045 whg = self.options.with_hg
3043 self._bindir = os.path.dirname(os.path.realpath(whg))
3046 self._bindir = os.path.dirname(os.path.realpath(whg))
3044 assert isinstance(self._bindir, bytes)
3047 assert isinstance(self._bindir, bytes)
3045 self._hgcommand = os.path.basename(whg)
3048 self._hgcommand = os.path.basename(whg)
3046 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3049 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3047 os.makedirs(self._tmpbindir)
3050 os.makedirs(self._tmpbindir)
3048
3051
3049 normbin = os.path.normpath(os.path.abspath(whg))
3052 normbin = os.path.normpath(os.path.abspath(whg))
3050 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3053 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3051
3054
3052 # Other Python scripts in the test harness need to
3055 # Other Python scripts in the test harness need to
3053 # `import mercurial`. If `hg` is a Python script, we assume
3056 # `import mercurial`. If `hg` is a Python script, we assume
3054 # the Mercurial modules are relative to its path and tell the tests
3057 # the Mercurial modules are relative to its path and tell the tests
3055 # to load Python modules from its directory.
3058 # to load Python modules from its directory.
3056 with open(whg, 'rb') as fh:
3059 with open(whg, 'rb') as fh:
3057 initial = fh.read(1024)
3060 initial = fh.read(1024)
3058
3061
3059 if re.match(b'#!.*python', initial):
3062 if re.match(b'#!.*python', initial):
3060 self._pythondir = self._bindir
3063 self._pythondir = self._bindir
3061 # If it looks like our in-repo Rust binary, use the source root.
3064 # If it looks like our in-repo Rust binary, use the source root.
3062 # This is a bit hacky. But rhg is still not supported outside the
3065 # This is a bit hacky. But rhg is still not supported outside the
3063 # source directory. So until it is, do the simple thing.
3066 # source directory. So until it is, do the simple thing.
3064 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3067 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3065 self._pythondir = os.path.dirname(self._testdir)
3068 self._pythondir = os.path.dirname(self._testdir)
3066 # Fall back to the legacy behavior.
3069 # Fall back to the legacy behavior.
3067 else:
3070 else:
3068 self._pythondir = self._bindir
3071 self._pythondir = self._bindir
3069
3072
3070 else:
3073 else:
3071 self._installdir = os.path.join(self._hgtmp, b"install")
3074 self._installdir = os.path.join(self._hgtmp, b"install")
3072 self._bindir = os.path.join(self._installdir, b"bin")
3075 self._bindir = os.path.join(self._installdir, b"bin")
3073 self._hgcommand = b'hg'
3076 self._hgcommand = b'hg'
3074 self._tmpbindir = self._bindir
3077 self._tmpbindir = self._bindir
3075 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3078 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3076
3079
3077 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3080 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3078 # a python script and feed it to python.exe. Legacy stdio is force
3081 # a python script and feed it to python.exe. Legacy stdio is force
3079 # enabled by hg.exe, and this is a more realistic way to launch hg
3082 # enabled by hg.exe, and this is a more realistic way to launch hg
3080 # anyway.
3083 # anyway.
3081 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3084 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3082 self._hgcommand += b'.exe'
3085 self._hgcommand += b'.exe'
3083
3086
3084 # set CHGHG, then replace "hg" command by "chg"
3087 # set CHGHG, then replace "hg" command by "chg"
3085 chgbindir = self._bindir
3088 chgbindir = self._bindir
3086 if self.options.chg or self.options.with_chg:
3089 if self.options.chg or self.options.with_chg:
3087 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3090 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3088 else:
3091 else:
3089 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3092 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3090 if self.options.chg:
3093 if self.options.chg:
3091 self._hgcommand = b'chg'
3094 self._hgcommand = b'chg'
3092 elif self.options.with_chg:
3095 elif self.options.with_chg:
3093 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3096 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3094 self._hgcommand = os.path.basename(self.options.with_chg)
3097 self._hgcommand = os.path.basename(self.options.with_chg)
3095
3098
3096 osenvironb[b"BINDIR"] = self._bindir
3099 osenvironb[b"BINDIR"] = self._bindir
3097 osenvironb[b"PYTHON"] = PYTHON
3100 osenvironb[b"PYTHON"] = PYTHON
3098
3101
3099 fileb = _sys2bytes(__file__)
3102 fileb = _sys2bytes(__file__)
3100 runtestdir = os.path.abspath(os.path.dirname(fileb))
3103 runtestdir = os.path.abspath(os.path.dirname(fileb))
3101 osenvironb[b'RUNTESTDIR'] = runtestdir
3104 osenvironb[b'RUNTESTDIR'] = runtestdir
3102 if PYTHON3:
3105 if PYTHON3:
3103 sepb = _sys2bytes(os.pathsep)
3106 sepb = _sys2bytes(os.pathsep)
3104 else:
3107 else:
3105 sepb = os.pathsep
3108 sepb = os.pathsep
3106 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3109 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3107 if os.path.islink(__file__):
3110 if os.path.islink(__file__):
3108 # test helper will likely be at the end of the symlink
3111 # test helper will likely be at the end of the symlink
3109 realfile = os.path.realpath(fileb)
3112 realfile = os.path.realpath(fileb)
3110 realdir = os.path.abspath(os.path.dirname(realfile))
3113 realdir = os.path.abspath(os.path.dirname(realfile))
3111 path.insert(2, realdir)
3114 path.insert(2, realdir)
3112 if chgbindir != self._bindir:
3115 if chgbindir != self._bindir:
3113 path.insert(1, chgbindir)
3116 path.insert(1, chgbindir)
3114 if self._testdir != runtestdir:
3117 if self._testdir != runtestdir:
3115 path = [self._testdir] + path
3118 path = [self._testdir] + path
3116 if self._tmpbindir != self._bindir:
3119 if self._tmpbindir != self._bindir:
3117 path = [self._tmpbindir] + path
3120 path = [self._tmpbindir] + path
3118 osenvironb[b"PATH"] = sepb.join(path)
3121 osenvironb[b"PATH"] = sepb.join(path)
3119
3122
3120 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3123 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3121 # can run .../tests/run-tests.py test-foo where test-foo
3124 # can run .../tests/run-tests.py test-foo where test-foo
3122 # adds an extension to HGRC. Also include run-test.py directory to
3125 # adds an extension to HGRC. Also include run-test.py directory to
3123 # import modules like heredoctest.
3126 # import modules like heredoctest.
3124 pypath = [self._pythondir, self._testdir, runtestdir]
3127 pypath = [self._pythondir, self._testdir, runtestdir]
3125 # We have to augment PYTHONPATH, rather than simply replacing
3128 # We have to augment PYTHONPATH, rather than simply replacing
3126 # it, in case external libraries are only available via current
3129 # it, in case external libraries are only available via current
3127 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3130 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3128 # are in /opt/subversion.)
3131 # are in /opt/subversion.)
3129 oldpypath = osenvironb.get(IMPL_PATH)
3132 oldpypath = osenvironb.get(IMPL_PATH)
3130 if oldpypath:
3133 if oldpypath:
3131 pypath.append(oldpypath)
3134 pypath.append(oldpypath)
3132 osenvironb[IMPL_PATH] = sepb.join(pypath)
3135 osenvironb[IMPL_PATH] = sepb.join(pypath)
3133
3136
3134 if self.options.pure:
3137 if self.options.pure:
3135 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3138 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3136 os.environ["HGMODULEPOLICY"] = "py"
3139 os.environ["HGMODULEPOLICY"] = "py"
3137 if self.options.rust:
3140 if self.options.rust:
3138 os.environ["HGMODULEPOLICY"] = "rust+c"
3141 os.environ["HGMODULEPOLICY"] = "rust+c"
3139 if self.options.no_rust:
3142 if self.options.no_rust:
3140 current_policy = os.environ.get("HGMODULEPOLICY", "")
3143 current_policy = os.environ.get("HGMODULEPOLICY", "")
3141 if current_policy.startswith("rust+"):
3144 if current_policy.startswith("rust+"):
3142 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3145 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3143 os.environ.pop("HGWITHRUSTEXT", None)
3146 os.environ.pop("HGWITHRUSTEXT", None)
3144
3147
3145 if self.options.allow_slow_tests:
3148 if self.options.allow_slow_tests:
3146 os.environ["HGTEST_SLOW"] = "slow"
3149 os.environ["HGTEST_SLOW"] = "slow"
3147 elif 'HGTEST_SLOW' in os.environ:
3150 elif 'HGTEST_SLOW' in os.environ:
3148 del os.environ['HGTEST_SLOW']
3151 del os.environ['HGTEST_SLOW']
3149
3152
3150 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3153 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3151
3154
3152 if self.options.exceptions:
3155 if self.options.exceptions:
3153 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3156 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3154 try:
3157 try:
3155 os.makedirs(exceptionsdir)
3158 os.makedirs(exceptionsdir)
3156 except OSError as e:
3159 except OSError as e:
3157 if e.errno != errno.EEXIST:
3160 if e.errno != errno.EEXIST:
3158 raise
3161 raise
3159
3162
3160 # Remove all existing exception reports.
3163 # Remove all existing exception reports.
3161 for f in os.listdir(exceptionsdir):
3164 for f in os.listdir(exceptionsdir):
3162 os.unlink(os.path.join(exceptionsdir, f))
3165 os.unlink(os.path.join(exceptionsdir, f))
3163
3166
3164 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3167 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3165 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3168 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3166 self.options.extra_config_opt.append(
3169 self.options.extra_config_opt.append(
3167 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3170 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3168 )
3171 )
3169
3172
3170 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3173 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3171 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3174 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3172 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3175 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3173 vlog("# Using PATH", os.environ["PATH"])
3176 vlog("# Using PATH", os.environ["PATH"])
3174 vlog(
3177 vlog(
3175 "# Using",
3178 "# Using",
3176 _bytes2sys(IMPL_PATH),
3179 _bytes2sys(IMPL_PATH),
3177 _bytes2sys(osenvironb[IMPL_PATH]),
3180 _bytes2sys(osenvironb[IMPL_PATH]),
3178 )
3181 )
3179 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3182 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3180
3183
3181 try:
3184 try:
3182 return self._runtests(testdescs) or 0
3185 return self._runtests(testdescs) or 0
3183 finally:
3186 finally:
3184 time.sleep(0.1)
3187 time.sleep(0.1)
3185 self._cleanup()
3188 self._cleanup()
3186
3189
3187 def findtests(self, args):
3190 def findtests(self, args):
3188 """Finds possible test files from arguments.
3191 """Finds possible test files from arguments.
3189
3192
3190 If you wish to inject custom tests into the test harness, this would
3193 If you wish to inject custom tests into the test harness, this would
3191 be a good function to monkeypatch or override in a derived class.
3194 be a good function to monkeypatch or override in a derived class.
3192 """
3195 """
3193 if not args:
3196 if not args:
3194 if self.options.changed:
3197 if self.options.changed:
3195 proc = Popen4(
3198 proc = Popen4(
3196 b'hg st --rev "%s" -man0 .'
3199 b'hg st --rev "%s" -man0 .'
3197 % _sys2bytes(self.options.changed),
3200 % _sys2bytes(self.options.changed),
3198 None,
3201 None,
3199 0,
3202 0,
3200 )
3203 )
3201 stdout, stderr = proc.communicate()
3204 stdout, stderr = proc.communicate()
3202 args = stdout.strip(b'\0').split(b'\0')
3205 args = stdout.strip(b'\0').split(b'\0')
3203 else:
3206 else:
3204 args = os.listdir(b'.')
3207 args = os.listdir(b'.')
3205
3208
3206 expanded_args = []
3209 expanded_args = []
3207 for arg in args:
3210 for arg in args:
3208 if os.path.isdir(arg):
3211 if os.path.isdir(arg):
3209 if not arg.endswith(b'/'):
3212 if not arg.endswith(b'/'):
3210 arg += b'/'
3213 arg += b'/'
3211 expanded_args.extend([arg + a for a in os.listdir(arg)])
3214 expanded_args.extend([arg + a for a in os.listdir(arg)])
3212 else:
3215 else:
3213 expanded_args.append(arg)
3216 expanded_args.append(arg)
3214 args = expanded_args
3217 args = expanded_args
3215
3218
3216 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3219 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3217 tests = []
3220 tests = []
3218 for t in args:
3221 for t in args:
3219 case = []
3222 case = []
3220
3223
3221 if not (
3224 if not (
3222 os.path.basename(t).startswith(b'test-')
3225 os.path.basename(t).startswith(b'test-')
3223 and (t.endswith(b'.py') or t.endswith(b'.t'))
3226 and (t.endswith(b'.py') or t.endswith(b'.t'))
3224 ):
3227 ):
3225
3228
3226 m = testcasepattern.match(os.path.basename(t))
3229 m = testcasepattern.match(os.path.basename(t))
3227 if m is not None:
3230 if m is not None:
3228 t_basename, casestr = m.groups()
3231 t_basename, casestr = m.groups()
3229 t = os.path.join(os.path.dirname(t), t_basename)
3232 t = os.path.join(os.path.dirname(t), t_basename)
3230 if casestr:
3233 if casestr:
3231 case = casestr.split(b'#')
3234 case = casestr.split(b'#')
3232 else:
3235 else:
3233 continue
3236 continue
3234
3237
3235 if t.endswith(b'.t'):
3238 if t.endswith(b'.t'):
3236 # .t file may contain multiple test cases
3239 # .t file may contain multiple test cases
3237 casedimensions = parsettestcases(t)
3240 casedimensions = parsettestcases(t)
3238 if casedimensions:
3241 if casedimensions:
3239 cases = []
3242 cases = []
3240
3243
3241 def addcases(case, casedimensions):
3244 def addcases(case, casedimensions):
3242 if not casedimensions:
3245 if not casedimensions:
3243 cases.append(case)
3246 cases.append(case)
3244 else:
3247 else:
3245 for c in casedimensions[0]:
3248 for c in casedimensions[0]:
3246 addcases(case + [c], casedimensions[1:])
3249 addcases(case + [c], casedimensions[1:])
3247
3250
3248 addcases([], casedimensions)
3251 addcases([], casedimensions)
3249 if case and case in cases:
3252 if case and case in cases:
3250 cases = [case]
3253 cases = [case]
3251 elif case:
3254 elif case:
3252 # Ignore invalid cases
3255 # Ignore invalid cases
3253 cases = []
3256 cases = []
3254 else:
3257 else:
3255 pass
3258 pass
3256 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3259 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3257 else:
3260 else:
3258 tests.append({'path': t})
3261 tests.append({'path': t})
3259 else:
3262 else:
3260 tests.append({'path': t})
3263 tests.append({'path': t})
3261
3264
3262 if self.options.retest:
3265 if self.options.retest:
3263 retest_args = []
3266 retest_args = []
3264 for test in tests:
3267 for test in tests:
3265 errpath = self._geterrpath(test)
3268 errpath = self._geterrpath(test)
3266 if os.path.exists(errpath):
3269 if os.path.exists(errpath):
3267 retest_args.append(test)
3270 retest_args.append(test)
3268 tests = retest_args
3271 tests = retest_args
3269 return tests
3272 return tests
3270
3273
3271 def _runtests(self, testdescs):
3274 def _runtests(self, testdescs):
3272 def _reloadtest(test, i):
3275 def _reloadtest(test, i):
3273 # convert a test back to its description dict
3276 # convert a test back to its description dict
3274 desc = {'path': test.path}
3277 desc = {'path': test.path}
3275 case = getattr(test, '_case', [])
3278 case = getattr(test, '_case', [])
3276 if case:
3279 if case:
3277 desc['case'] = case
3280 desc['case'] = case
3278 return self._gettest(desc, i)
3281 return self._gettest(desc, i)
3279
3282
3280 try:
3283 try:
3281 if self.options.restart:
3284 if self.options.restart:
3282 orig = list(testdescs)
3285 orig = list(testdescs)
3283 while testdescs:
3286 while testdescs:
3284 desc = testdescs[0]
3287 desc = testdescs[0]
3285 errpath = self._geterrpath(desc)
3288 errpath = self._geterrpath(desc)
3286 if os.path.exists(errpath):
3289 if os.path.exists(errpath):
3287 break
3290 break
3288 testdescs.pop(0)
3291 testdescs.pop(0)
3289 if not testdescs:
3292 if not testdescs:
3290 print("running all tests")
3293 print("running all tests")
3291 testdescs = orig
3294 testdescs = orig
3292
3295
3293 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3296 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3294 num_tests = len(tests) * self.options.runs_per_test
3297 num_tests = len(tests) * self.options.runs_per_test
3295
3298
3296 jobs = min(num_tests, self.options.jobs)
3299 jobs = min(num_tests, self.options.jobs)
3297
3300
3298 failed = False
3301 failed = False
3299 kws = self.options.keywords
3302 kws = self.options.keywords
3300 if kws is not None and PYTHON3:
3303 if kws is not None and PYTHON3:
3301 kws = kws.encode('utf-8')
3304 kws = kws.encode('utf-8')
3302
3305
3303 suite = TestSuite(
3306 suite = TestSuite(
3304 self._testdir,
3307 self._testdir,
3305 jobs=jobs,
3308 jobs=jobs,
3306 whitelist=self.options.whitelisted,
3309 whitelist=self.options.whitelisted,
3307 blacklist=self.options.blacklist,
3310 blacklist=self.options.blacklist,
3308 keywords=kws,
3311 keywords=kws,
3309 loop=self.options.loop,
3312 loop=self.options.loop,
3310 runs_per_test=self.options.runs_per_test,
3313 runs_per_test=self.options.runs_per_test,
3311 showchannels=self.options.showchannels,
3314 showchannels=self.options.showchannels,
3312 tests=tests,
3315 tests=tests,
3313 loadtest=_reloadtest,
3316 loadtest=_reloadtest,
3314 )
3317 )
3315 verbosity = 1
3318 verbosity = 1
3316 if self.options.list_tests:
3319 if self.options.list_tests:
3317 verbosity = 0
3320 verbosity = 0
3318 elif self.options.verbose:
3321 elif self.options.verbose:
3319 verbosity = 2
3322 verbosity = 2
3320 runner = TextTestRunner(self, verbosity=verbosity)
3323 runner = TextTestRunner(self, verbosity=verbosity)
3321
3324
3322 if self.options.list_tests:
3325 if self.options.list_tests:
3323 result = runner.listtests(suite)
3326 result = runner.listtests(suite)
3324 else:
3327 else:
3325 if self._installdir:
3328 if self._installdir:
3326 self._installhg()
3329 self._installhg()
3327 self._checkhglib("Testing")
3330 self._checkhglib("Testing")
3328 else:
3331 else:
3329 self._usecorrectpython()
3332 self._usecorrectpython()
3330 if self.options.chg:
3333 if self.options.chg:
3331 assert self._installdir
3334 assert self._installdir
3332 self._installchg()
3335 self._installchg()
3333
3336
3334 log(
3337 log(
3335 'running %d tests using %d parallel processes'
3338 'running %d tests using %d parallel processes'
3336 % (num_tests, jobs)
3339 % (num_tests, jobs)
3337 )
3340 )
3338
3341
3339 result = runner.run(suite)
3342 result = runner.run(suite)
3340
3343
3341 if result.failures or result.errors:
3344 if result.failures or result.errors:
3342 failed = True
3345 failed = True
3343
3346
3344 result.onEnd()
3347 result.onEnd()
3345
3348
3346 if self.options.anycoverage:
3349 if self.options.anycoverage:
3347 self._outputcoverage()
3350 self._outputcoverage()
3348 except KeyboardInterrupt:
3351 except KeyboardInterrupt:
3349 failed = True
3352 failed = True
3350 print("\ninterrupted!")
3353 print("\ninterrupted!")
3351
3354
3352 if failed:
3355 if failed:
3353 return 1
3356 return 1
3354
3357
3355 def _geterrpath(self, test):
3358 def _geterrpath(self, test):
3356 # test['path'] is a relative path
3359 # test['path'] is a relative path
3357 if 'case' in test:
3360 if 'case' in test:
3358 # for multiple dimensions test cases
3361 # for multiple dimensions test cases
3359 casestr = b'#'.join(test['case'])
3362 casestr = b'#'.join(test['case'])
3360 errpath = b'%s#%s.err' % (test['path'], casestr)
3363 errpath = b'%s#%s.err' % (test['path'], casestr)
3361 else:
3364 else:
3362 errpath = b'%s.err' % test['path']
3365 errpath = b'%s.err' % test['path']
3363 if self.options.outputdir:
3366 if self.options.outputdir:
3364 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3367 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3365 errpath = os.path.join(self._outputdir, errpath)
3368 errpath = os.path.join(self._outputdir, errpath)
3366 return errpath
3369 return errpath
3367
3370
3368 def _getport(self, count):
3371 def _getport(self, count):
3369 port = self._ports.get(count) # do we have a cached entry?
3372 port = self._ports.get(count) # do we have a cached entry?
3370 if port is None:
3373 if port is None:
3371 portneeded = 3
3374 portneeded = 3
3372 # above 100 tries we just give up and let test reports failure
3375 # above 100 tries we just give up and let test reports failure
3373 for tries in xrange(100):
3376 for tries in xrange(100):
3374 allfree = True
3377 allfree = True
3375 port = self.options.port + self._portoffset
3378 port = self.options.port + self._portoffset
3376 for idx in xrange(portneeded):
3379 for idx in xrange(portneeded):
3377 if not checkportisavailable(port + idx):
3380 if not checkportisavailable(port + idx):
3378 allfree = False
3381 allfree = False
3379 break
3382 break
3380 self._portoffset += portneeded
3383 self._portoffset += portneeded
3381 if allfree:
3384 if allfree:
3382 break
3385 break
3383 self._ports[count] = port
3386 self._ports[count] = port
3384 return port
3387 return port
3385
3388
3386 def _gettest(self, testdesc, count):
3389 def _gettest(self, testdesc, count):
3387 """Obtain a Test by looking at its filename.
3390 """Obtain a Test by looking at its filename.
3388
3391
3389 Returns a Test instance. The Test may not be runnable if it doesn't
3392 Returns a Test instance. The Test may not be runnable if it doesn't
3390 map to a known type.
3393 map to a known type.
3391 """
3394 """
3392 path = testdesc['path']
3395 path = testdesc['path']
3393 lctest = path.lower()
3396 lctest = path.lower()
3394 testcls = Test
3397 testcls = Test
3395
3398
3396 for ext, cls in self.TESTTYPES:
3399 for ext, cls in self.TESTTYPES:
3397 if lctest.endswith(ext):
3400 if lctest.endswith(ext):
3398 testcls = cls
3401 testcls = cls
3399 break
3402 break
3400
3403
3401 refpath = os.path.join(getcwdb(), path)
3404 refpath = os.path.join(getcwdb(), path)
3402 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3405 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3403
3406
3404 # extra keyword parameters. 'case' is used by .t tests
3407 # extra keyword parameters. 'case' is used by .t tests
3405 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3408 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3406
3409
3407 t = testcls(
3410 t = testcls(
3408 refpath,
3411 refpath,
3409 self._outputdir,
3412 self._outputdir,
3410 tmpdir,
3413 tmpdir,
3411 keeptmpdir=self.options.keep_tmpdir,
3414 keeptmpdir=self.options.keep_tmpdir,
3412 debug=self.options.debug,
3415 debug=self.options.debug,
3413 first=self.options.first,
3416 first=self.options.first,
3414 timeout=self.options.timeout,
3417 timeout=self.options.timeout,
3415 startport=self._getport(count),
3418 startport=self._getport(count),
3416 extraconfigopts=self.options.extra_config_opt,
3419 extraconfigopts=self.options.extra_config_opt,
3417 shell=self.options.shell,
3420 shell=self.options.shell,
3418 hgcommand=self._hgcommand,
3421 hgcommand=self._hgcommand,
3419 usechg=bool(self.options.with_chg or self.options.chg),
3422 usechg=bool(self.options.with_chg or self.options.chg),
3420 chgdebug=self.options.chg_debug,
3423 chgdebug=self.options.chg_debug,
3421 useipv6=useipv6,
3424 useipv6=useipv6,
3422 **kwds
3425 **kwds
3423 )
3426 )
3424 t.should_reload = True
3427 t.should_reload = True
3425 return t
3428 return t
3426
3429
3427 def _cleanup(self):
3430 def _cleanup(self):
3428 """Clean up state from this test invocation."""
3431 """Clean up state from this test invocation."""
3429 if self.options.keep_tmpdir:
3432 if self.options.keep_tmpdir:
3430 return
3433 return
3431
3434
3432 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3435 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3433 shutil.rmtree(self._hgtmp, True)
3436 shutil.rmtree(self._hgtmp, True)
3434 for f in self._createdfiles:
3437 for f in self._createdfiles:
3435 try:
3438 try:
3436 os.remove(f)
3439 os.remove(f)
3437 except OSError:
3440 except OSError:
3438 pass
3441 pass
3439
3442
3440 def _usecorrectpython(self):
3443 def _usecorrectpython(self):
3441 """Configure the environment to use the appropriate Python in tests."""
3444 """Configure the environment to use the appropriate Python in tests."""
3442 # Tests must use the same interpreter as us or bad things will happen.
3445 # Tests must use the same interpreter as us or bad things will happen.
3443 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3446 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3444
3447
3445 # os.symlink() is a thing with py3 on Windows, but it requires
3448 # os.symlink() is a thing with py3 on Windows, but it requires
3446 # Administrator rights.
3449 # Administrator rights.
3447 if getattr(os, 'symlink', None) and os.name != 'nt':
3450 if getattr(os, 'symlink', None) and os.name != 'nt':
3448 vlog(
3451 vlog(
3449 "# Making python executable in test path a symlink to '%s'"
3452 "# Making python executable in test path a symlink to '%s'"
3450 % sysexecutable
3453 % sysexecutable
3451 )
3454 )
3452 mypython = os.path.join(self._tmpbindir, pyexename)
3455 mypython = os.path.join(self._tmpbindir, pyexename)
3453 try:
3456 try:
3454 if os.readlink(mypython) == sysexecutable:
3457 if os.readlink(mypython) == sysexecutable:
3455 return
3458 return
3456 os.unlink(mypython)
3459 os.unlink(mypython)
3457 except OSError as err:
3460 except OSError as err:
3458 if err.errno != errno.ENOENT:
3461 if err.errno != errno.ENOENT:
3459 raise
3462 raise
3460 if self._findprogram(pyexename) != sysexecutable:
3463 if self._findprogram(pyexename) != sysexecutable:
3461 try:
3464 try:
3462 os.symlink(sysexecutable, mypython)
3465 os.symlink(sysexecutable, mypython)
3463 self._createdfiles.append(mypython)
3466 self._createdfiles.append(mypython)
3464 except OSError as err:
3467 except OSError as err:
3465 # child processes may race, which is harmless
3468 # child processes may race, which is harmless
3466 if err.errno != errno.EEXIST:
3469 if err.errno != errno.EEXIST:
3467 raise
3470 raise
3468 else:
3471 else:
3469 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3472 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3470 # reparse point with that name provided by Microsoft. Copy the
3473 # reparse point with that name provided by Microsoft. Copy the
3471 # current interpreter to PATH with that name so the shebang lines
3474 # current interpreter to PATH with that name so the shebang lines
3472 # work.
3475 # work.
3473 if os.getenv('MSYSTEM'):
3476 if os.getenv('MSYSTEM'):
3474 shutil.copy(
3477 shutil.copy(
3475 sys.executable,
3478 sys.executable,
3476 _bytes2sys(self._tmpbindir + b'/python3.exe'),
3479 _bytes2sys(self._tmpbindir + b'/python3.exe'),
3477 )
3480 )
3478
3481
3479 exedir, exename = os.path.split(sysexecutable)
3482 exedir, exename = os.path.split(sysexecutable)
3480 vlog(
3483 vlog(
3481 "# Modifying search path to find %s as %s in '%s'"
3484 "# Modifying search path to find %s as %s in '%s'"
3482 % (exename, pyexename, exedir)
3485 % (exename, pyexename, exedir)
3483 )
3486 )
3484 path = os.environ['PATH'].split(os.pathsep)
3487 path = os.environ['PATH'].split(os.pathsep)
3485 while exedir in path:
3488 while exedir in path:
3486 path.remove(exedir)
3489 path.remove(exedir)
3487
3490
3488 # Binaries installed by pip into the user area like pylint.exe may
3491 # Binaries installed by pip into the user area like pylint.exe may
3489 # not be in PATH by default.
3492 # not be in PATH by default.
3490 extra_paths = [exedir]
3493 extra_paths = [exedir]
3491 vi = sys.version_info
3494 vi = sys.version_info
3492 if 'APPDATA' in os.environ:
3495 if 'APPDATA' in os.environ:
3493 scripts_dir = os.path.join(
3496 scripts_dir = os.path.join(
3494 os.environ['APPDATA'],
3497 os.environ['APPDATA'],
3495 'Python',
3498 'Python',
3496 'Python%d%d' % (vi[0], vi[1]),
3499 'Python%d%d' % (vi[0], vi[1]),
3497 'Scripts',
3500 'Scripts',
3498 )
3501 )
3499
3502
3500 if vi.major == 2:
3503 if vi.major == 2:
3501 scripts_dir = os.path.join(
3504 scripts_dir = os.path.join(
3502 os.environ['APPDATA'],
3505 os.environ['APPDATA'],
3503 'Python',
3506 'Python',
3504 'Scripts',
3507 'Scripts',
3505 )
3508 )
3506
3509
3507 extra_paths.append(scripts_dir)
3510 extra_paths.append(scripts_dir)
3508
3511
3509 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3512 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3510 if not self._findprogram(pyexename):
3513 if not self._findprogram(pyexename):
3511 print("WARNING: Cannot find %s in search path" % pyexename)
3514 print("WARNING: Cannot find %s in search path" % pyexename)
3512
3515
3513 def _installhg(self):
3516 def _installhg(self):
3514 """Install hg into the test environment.
3517 """Install hg into the test environment.
3515
3518
3516 This will also configure hg with the appropriate testing settings.
3519 This will also configure hg with the appropriate testing settings.
3517 """
3520 """
3518 vlog("# Performing temporary installation of HG")
3521 vlog("# Performing temporary installation of HG")
3519 installerrs = os.path.join(self._hgtmp, b"install.err")
3522 installerrs = os.path.join(self._hgtmp, b"install.err")
3520 compiler = ''
3523 compiler = ''
3521 if self.options.compiler:
3524 if self.options.compiler:
3522 compiler = '--compiler ' + self.options.compiler
3525 compiler = '--compiler ' + self.options.compiler
3523 setup_opts = b""
3526 setup_opts = b""
3524 if self.options.pure:
3527 if self.options.pure:
3525 setup_opts = b"--pure"
3528 setup_opts = b"--pure"
3526 elif self.options.rust:
3529 elif self.options.rust:
3527 setup_opts = b"--rust"
3530 setup_opts = b"--rust"
3528 elif self.options.no_rust:
3531 elif self.options.no_rust:
3529 setup_opts = b"--no-rust"
3532 setup_opts = b"--no-rust"
3530
3533
3531 # Run installer in hg root
3534 # Run installer in hg root
3532 script = os.path.realpath(sys.argv[0])
3535 script = os.path.realpath(sys.argv[0])
3533 exe = sysexecutable
3536 exe = sysexecutable
3534 if PYTHON3:
3537 if PYTHON3:
3535 compiler = _sys2bytes(compiler)
3538 compiler = _sys2bytes(compiler)
3536 script = _sys2bytes(script)
3539 script = _sys2bytes(script)
3537 exe = _sys2bytes(exe)
3540 exe = _sys2bytes(exe)
3538 hgroot = os.path.dirname(os.path.dirname(script))
3541 hgroot = os.path.dirname(os.path.dirname(script))
3539 self._hgroot = hgroot
3542 self._hgroot = hgroot
3540 os.chdir(hgroot)
3543 os.chdir(hgroot)
3541 nohome = b'--home=""'
3544 nohome = b'--home=""'
3542 if os.name == 'nt':
3545 if os.name == 'nt':
3543 # The --home="" trick works only on OS where os.sep == '/'
3546 # The --home="" trick works only on OS where os.sep == '/'
3544 # because of a distutils convert_path() fast-path. Avoid it at
3547 # because of a distutils convert_path() fast-path. Avoid it at
3545 # least on Windows for now, deal with .pydistutils.cfg bugs
3548 # least on Windows for now, deal with .pydistutils.cfg bugs
3546 # when they happen.
3549 # when they happen.
3547 nohome = b''
3550 nohome = b''
3548 cmd = (
3551 cmd = (
3549 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3552 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3550 b' build %(compiler)s --build-base="%(base)s"'
3553 b' build %(compiler)s --build-base="%(base)s"'
3551 b' install --force --prefix="%(prefix)s"'
3554 b' install --force --prefix="%(prefix)s"'
3552 b' --install-lib="%(libdir)s"'
3555 b' --install-lib="%(libdir)s"'
3553 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3556 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3554 % {
3557 % {
3555 b'exe': exe,
3558 b'exe': exe,
3556 b'setup_opts': setup_opts,
3559 b'setup_opts': setup_opts,
3557 b'compiler': compiler,
3560 b'compiler': compiler,
3558 b'base': os.path.join(self._hgtmp, b"build"),
3561 b'base': os.path.join(self._hgtmp, b"build"),
3559 b'prefix': self._installdir,
3562 b'prefix': self._installdir,
3560 b'libdir': self._pythondir,
3563 b'libdir': self._pythondir,
3561 b'bindir': self._bindir,
3564 b'bindir': self._bindir,
3562 b'nohome': nohome,
3565 b'nohome': nohome,
3563 b'logfile': installerrs,
3566 b'logfile': installerrs,
3564 }
3567 }
3565 )
3568 )
3566
3569
3567 # setuptools requires install directories to exist.
3570 # setuptools requires install directories to exist.
3568 def makedirs(p):
3571 def makedirs(p):
3569 try:
3572 try:
3570 os.makedirs(p)
3573 os.makedirs(p)
3571 except OSError as e:
3574 except OSError as e:
3572 if e.errno != errno.EEXIST:
3575 if e.errno != errno.EEXIST:
3573 raise
3576 raise
3574
3577
3575 makedirs(self._pythondir)
3578 makedirs(self._pythondir)
3576 makedirs(self._bindir)
3579 makedirs(self._bindir)
3577
3580
3578 vlog("# Running", cmd.decode("utf-8"))
3581 vlog("# Running", cmd.decode("utf-8"))
3579 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3582 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3580 if not self.options.verbose:
3583 if not self.options.verbose:
3581 try:
3584 try:
3582 os.remove(installerrs)
3585 os.remove(installerrs)
3583 except OSError as e:
3586 except OSError as e:
3584 if e.errno != errno.ENOENT:
3587 if e.errno != errno.ENOENT:
3585 raise
3588 raise
3586 else:
3589 else:
3587 with open(installerrs, 'rb') as f:
3590 with open(installerrs, 'rb') as f:
3588 for line in f:
3591 for line in f:
3589 if PYTHON3:
3592 if PYTHON3:
3590 sys.stdout.buffer.write(line)
3593 sys.stdout.buffer.write(line)
3591 else:
3594 else:
3592 sys.stdout.write(line)
3595 sys.stdout.write(line)
3593 sys.exit(1)
3596 sys.exit(1)
3594 os.chdir(self._testdir)
3597 os.chdir(self._testdir)
3595
3598
3596 self._usecorrectpython()
3599 self._usecorrectpython()
3597
3600
3598 hgbat = os.path.join(self._bindir, b'hg.bat')
3601 hgbat = os.path.join(self._bindir, b'hg.bat')
3599 if os.path.isfile(hgbat):
3602 if os.path.isfile(hgbat):
3600 # hg.bat expects to be put in bin/scripts while run-tests.py
3603 # hg.bat expects to be put in bin/scripts while run-tests.py
3601 # installation layout put it in bin/ directly. Fix it
3604 # installation layout put it in bin/ directly. Fix it
3602 with open(hgbat, 'rb') as f:
3605 with open(hgbat, 'rb') as f:
3603 data = f.read()
3606 data = f.read()
3604 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3607 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3605 data = data.replace(
3608 data = data.replace(
3606 br'"%~dp0..\python" "%~dp0hg" %*',
3609 br'"%~dp0..\python" "%~dp0hg" %*',
3607 b'"%~dp0python" "%~dp0hg" %*',
3610 b'"%~dp0python" "%~dp0hg" %*',
3608 )
3611 )
3609 with open(hgbat, 'wb') as f:
3612 with open(hgbat, 'wb') as f:
3610 f.write(data)
3613 f.write(data)
3611 else:
3614 else:
3612 print('WARNING: cannot fix hg.bat reference to python.exe')
3615 print('WARNING: cannot fix hg.bat reference to python.exe')
3613
3616
3614 if self.options.anycoverage:
3617 if self.options.anycoverage:
3615 custom = os.path.join(
3618 custom = os.path.join(
3616 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3619 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3617 )
3620 )
3618 target = os.path.join(self._pythondir, b'sitecustomize.py')
3621 target = os.path.join(self._pythondir, b'sitecustomize.py')
3619 vlog('# Installing coverage trigger to %s' % target)
3622 vlog('# Installing coverage trigger to %s' % target)
3620 shutil.copyfile(custom, target)
3623 shutil.copyfile(custom, target)
3621 rc = os.path.join(self._testdir, b'.coveragerc')
3624 rc = os.path.join(self._testdir, b'.coveragerc')
3622 vlog('# Installing coverage rc to %s' % rc)
3625 vlog('# Installing coverage rc to %s' % rc)
3623 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3626 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3624 covdir = os.path.join(self._installdir, b'..', b'coverage')
3627 covdir = os.path.join(self._installdir, b'..', b'coverage')
3625 try:
3628 try:
3626 os.mkdir(covdir)
3629 os.mkdir(covdir)
3627 except OSError as e:
3630 except OSError as e:
3628 if e.errno != errno.EEXIST:
3631 if e.errno != errno.EEXIST:
3629 raise
3632 raise
3630
3633
3631 osenvironb[b'COVERAGE_DIR'] = covdir
3634 osenvironb[b'COVERAGE_DIR'] = covdir
3632
3635
3633 def _checkhglib(self, verb):
3636 def _checkhglib(self, verb):
3634 """Ensure that the 'mercurial' package imported by python is
3637 """Ensure that the 'mercurial' package imported by python is
3635 the one we expect it to be. If not, print a warning to stderr."""
3638 the one we expect it to be. If not, print a warning to stderr."""
3636 if (self._bindir == self._pythondir) and (
3639 if (self._bindir == self._pythondir) and (
3637 self._bindir != self._tmpbindir
3640 self._bindir != self._tmpbindir
3638 ):
3641 ):
3639 # The pythondir has been inferred from --with-hg flag.
3642 # The pythondir has been inferred from --with-hg flag.
3640 # We cannot expect anything sensible here.
3643 # We cannot expect anything sensible here.
3641 return
3644 return
3642 expecthg = os.path.join(self._pythondir, b'mercurial')
3645 expecthg = os.path.join(self._pythondir, b'mercurial')
3643 actualhg = self._gethgpath()
3646 actualhg = self._gethgpath()
3644 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3647 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3645 sys.stderr.write(
3648 sys.stderr.write(
3646 'warning: %s with unexpected mercurial lib: %s\n'
3649 'warning: %s with unexpected mercurial lib: %s\n'
3647 ' (expected %s)\n' % (verb, actualhg, expecthg)
3650 ' (expected %s)\n' % (verb, actualhg, expecthg)
3648 )
3651 )
3649
3652
3650 def _gethgpath(self):
3653 def _gethgpath(self):
3651 """Return the path to the mercurial package that is actually found by
3654 """Return the path to the mercurial package that is actually found by
3652 the current Python interpreter."""
3655 the current Python interpreter."""
3653 if self._hgpath is not None:
3656 if self._hgpath is not None:
3654 return self._hgpath
3657 return self._hgpath
3655
3658
3656 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3659 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3657 cmd = cmd % PYTHON
3660 cmd = cmd % PYTHON
3658 if PYTHON3:
3661 if PYTHON3:
3659 cmd = _bytes2sys(cmd)
3662 cmd = _bytes2sys(cmd)
3660
3663
3661 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3664 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3662 out, err = p.communicate()
3665 out, err = p.communicate()
3663
3666
3664 self._hgpath = out.strip()
3667 self._hgpath = out.strip()
3665
3668
3666 return self._hgpath
3669 return self._hgpath
3667
3670
3668 def _installchg(self):
3671 def _installchg(self):
3669 """Install chg into the test environment"""
3672 """Install chg into the test environment"""
3670 vlog('# Performing temporary installation of CHG')
3673 vlog('# Performing temporary installation of CHG')
3671 assert os.path.dirname(self._bindir) == self._installdir
3674 assert os.path.dirname(self._bindir) == self._installdir
3672 assert self._hgroot, 'must be called after _installhg()'
3675 assert self._hgroot, 'must be called after _installhg()'
3673 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3676 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3674 b'make': b'make', # TODO: switch by option or environment?
3677 b'make': b'make', # TODO: switch by option or environment?
3675 b'prefix': self._installdir,
3678 b'prefix': self._installdir,
3676 }
3679 }
3677 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3680 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3678 vlog("# Running", cmd)
3681 vlog("# Running", cmd)
3679 proc = subprocess.Popen(
3682 proc = subprocess.Popen(
3680 cmd,
3683 cmd,
3681 shell=True,
3684 shell=True,
3682 cwd=cwd,
3685 cwd=cwd,
3683 stdin=subprocess.PIPE,
3686 stdin=subprocess.PIPE,
3684 stdout=subprocess.PIPE,
3687 stdout=subprocess.PIPE,
3685 stderr=subprocess.STDOUT,
3688 stderr=subprocess.STDOUT,
3686 )
3689 )
3687 out, _err = proc.communicate()
3690 out, _err = proc.communicate()
3688 if proc.returncode != 0:
3691 if proc.returncode != 0:
3689 if PYTHON3:
3692 if PYTHON3:
3690 sys.stdout.buffer.write(out)
3693 sys.stdout.buffer.write(out)
3691 else:
3694 else:
3692 sys.stdout.write(out)
3695 sys.stdout.write(out)
3693 sys.exit(1)
3696 sys.exit(1)
3694
3697
3695 def _outputcoverage(self):
3698 def _outputcoverage(self):
3696 """Produce code coverage output."""
3699 """Produce code coverage output."""
3697 import coverage
3700 import coverage
3698
3701
3699 coverage = coverage.coverage
3702 coverage = coverage.coverage
3700
3703
3701 vlog('# Producing coverage report')
3704 vlog('# Producing coverage report')
3702 # chdir is the easiest way to get short, relative paths in the
3705 # chdir is the easiest way to get short, relative paths in the
3703 # output.
3706 # output.
3704 os.chdir(self._hgroot)
3707 os.chdir(self._hgroot)
3705 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3708 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3706 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3709 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3707
3710
3708 # Map install directory paths back to source directory.
3711 # Map install directory paths back to source directory.
3709 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3712 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3710
3713
3711 cov.combine()
3714 cov.combine()
3712
3715
3713 omit = [
3716 omit = [
3714 _bytes2sys(os.path.join(x, b'*'))
3717 _bytes2sys(os.path.join(x, b'*'))
3715 for x in [self._bindir, self._testdir]
3718 for x in [self._bindir, self._testdir]
3716 ]
3719 ]
3717 cov.report(ignore_errors=True, omit=omit)
3720 cov.report(ignore_errors=True, omit=omit)
3718
3721
3719 if self.options.htmlcov:
3722 if self.options.htmlcov:
3720 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3723 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3721 cov.html_report(directory=htmldir, omit=omit)
3724 cov.html_report(directory=htmldir, omit=omit)
3722 if self.options.annotate:
3725 if self.options.annotate:
3723 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3726 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3724 if not os.path.isdir(adir):
3727 if not os.path.isdir(adir):
3725 os.mkdir(adir)
3728 os.mkdir(adir)
3726 cov.annotate(directory=adir, omit=omit)
3729 cov.annotate(directory=adir, omit=omit)
3727
3730
3728 def _findprogram(self, program):
3731 def _findprogram(self, program):
3729 """Search PATH for a executable program"""
3732 """Search PATH for a executable program"""
3730 dpb = _sys2bytes(os.defpath)
3733 dpb = _sys2bytes(os.defpath)
3731 sepb = _sys2bytes(os.pathsep)
3734 sepb = _sys2bytes(os.pathsep)
3732 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3735 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3733 name = os.path.join(p, program)
3736 name = os.path.join(p, program)
3734 if os.name == 'nt' or os.access(name, os.X_OK):
3737 if os.name == 'nt' or os.access(name, os.X_OK):
3735 return _bytes2sys(name)
3738 return _bytes2sys(name)
3736 return None
3739 return None
3737
3740
3738 def _checktools(self):
3741 def _checktools(self):
3739 """Ensure tools required to run tests are present."""
3742 """Ensure tools required to run tests are present."""
3740 for p in self.REQUIREDTOOLS:
3743 for p in self.REQUIREDTOOLS:
3741 if os.name == 'nt' and not p.endswith(b'.exe'):
3744 if os.name == 'nt' and not p.endswith(b'.exe'):
3742 p += b'.exe'
3745 p += b'.exe'
3743 found = self._findprogram(p)
3746 found = self._findprogram(p)
3744 p = p.decode("utf-8")
3747 p = p.decode("utf-8")
3745 if found:
3748 if found:
3746 vlog("# Found prerequisite", p, "at", found)
3749 vlog("# Found prerequisite", p, "at", found)
3747 else:
3750 else:
3748 print("WARNING: Did not find prerequisite tool: %s " % p)
3751 print("WARNING: Did not find prerequisite tool: %s " % p)
3749
3752
3750
3753
3751 def aggregateexceptions(path):
3754 def aggregateexceptions(path):
3752 exceptioncounts = collections.Counter()
3755 exceptioncounts = collections.Counter()
3753 testsbyfailure = collections.defaultdict(set)
3756 testsbyfailure = collections.defaultdict(set)
3754 failuresbytest = collections.defaultdict(set)
3757 failuresbytest = collections.defaultdict(set)
3755
3758
3756 for f in os.listdir(path):
3759 for f in os.listdir(path):
3757 with open(os.path.join(path, f), 'rb') as fh:
3760 with open(os.path.join(path, f), 'rb') as fh:
3758 data = fh.read().split(b'\0')
3761 data = fh.read().split(b'\0')
3759 if len(data) != 5:
3762 if len(data) != 5:
3760 continue
3763 continue
3761
3764
3762 exc, mainframe, hgframe, hgline, testname = data
3765 exc, mainframe, hgframe, hgline, testname = data
3763 exc = exc.decode('utf-8')
3766 exc = exc.decode('utf-8')
3764 mainframe = mainframe.decode('utf-8')
3767 mainframe = mainframe.decode('utf-8')
3765 hgframe = hgframe.decode('utf-8')
3768 hgframe = hgframe.decode('utf-8')
3766 hgline = hgline.decode('utf-8')
3769 hgline = hgline.decode('utf-8')
3767 testname = testname.decode('utf-8')
3770 testname = testname.decode('utf-8')
3768
3771
3769 key = (hgframe, hgline, exc)
3772 key = (hgframe, hgline, exc)
3770 exceptioncounts[key] += 1
3773 exceptioncounts[key] += 1
3771 testsbyfailure[key].add(testname)
3774 testsbyfailure[key].add(testname)
3772 failuresbytest[testname].add(key)
3775 failuresbytest[testname].add(key)
3773
3776
3774 # Find test having fewest failures for each failure.
3777 # Find test having fewest failures for each failure.
3775 leastfailing = {}
3778 leastfailing = {}
3776 for key, tests in testsbyfailure.items():
3779 for key, tests in testsbyfailure.items():
3777 fewesttest = None
3780 fewesttest = None
3778 fewestcount = 99999999
3781 fewestcount = 99999999
3779 for test in sorted(tests):
3782 for test in sorted(tests):
3780 if len(failuresbytest[test]) < fewestcount:
3783 if len(failuresbytest[test]) < fewestcount:
3781 fewesttest = test
3784 fewesttest = test
3782 fewestcount = len(failuresbytest[test])
3785 fewestcount = len(failuresbytest[test])
3783
3786
3784 leastfailing[key] = (fewestcount, fewesttest)
3787 leastfailing[key] = (fewestcount, fewesttest)
3785
3788
3786 # Create a combined counter so we can sort by total occurrences and
3789 # Create a combined counter so we can sort by total occurrences and
3787 # impacted tests.
3790 # impacted tests.
3788 combined = {}
3791 combined = {}
3789 for key in exceptioncounts:
3792 for key in exceptioncounts:
3790 combined[key] = (
3793 combined[key] = (
3791 exceptioncounts[key],
3794 exceptioncounts[key],
3792 len(testsbyfailure[key]),
3795 len(testsbyfailure[key]),
3793 leastfailing[key][0],
3796 leastfailing[key][0],
3794 leastfailing[key][1],
3797 leastfailing[key][1],
3795 )
3798 )
3796
3799
3797 return {
3800 return {
3798 'exceptioncounts': exceptioncounts,
3801 'exceptioncounts': exceptioncounts,
3799 'total': sum(exceptioncounts.values()),
3802 'total': sum(exceptioncounts.values()),
3800 'combined': combined,
3803 'combined': combined,
3801 'leastfailing': leastfailing,
3804 'leastfailing': leastfailing,
3802 'byfailure': testsbyfailure,
3805 'byfailure': testsbyfailure,
3803 'bytest': failuresbytest,
3806 'bytest': failuresbytest,
3804 }
3807 }
3805
3808
3806
3809
3807 if __name__ == '__main__':
3810 if __name__ == '__main__':
3808 runner = TestRunner()
3811 runner = TestRunner()
3809
3812
3810 try:
3813 try:
3811 import msvcrt
3814 import msvcrt
3812
3815
3813 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3816 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3814 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3817 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3815 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3818 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3816 except ImportError:
3819 except ImportError:
3817 pass
3820 pass
3818
3821
3819 sys.exit(runner.run(sys.argv[1:]))
3822 sys.exit(runner.run(sys.argv[1:]))
@@ -1,1996 +1,1997 b''
1 Test template map files and styles
1 Test template map files and styles
2 ==================================
2 ==================================
3
3
4 $ hg init a
4 $ hg init a
5 $ cd a
5 $ cd a
6 $ echo a > a
6 $ echo a > a
7 $ hg add a
7 $ hg add a
8 $ echo line 1 > b
8 $ echo line 1 > b
9 $ echo line 2 >> b
9 $ echo line 2 >> b
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
10 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
11
11
12 $ hg add b
12 $ hg add b
13 $ echo other 1 > c
13 $ echo other 1 > c
14 $ echo other 2 >> c
14 $ echo other 2 >> c
15 $ echo >> c
15 $ echo >> c
16 $ echo other 3 >> c
16 $ echo other 3 >> c
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
17 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
18
18
19 $ hg add c
19 $ hg add c
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
20 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
21 $ echo c >> c
21 $ echo c >> c
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
22 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
23
23
24 $ echo foo > .hg/branch
24 $ echo foo > .hg/branch
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
25 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
26
26
27 $ hg co -q 3
27 $ hg co -q 3
28 $ echo other 4 >> d
28 $ echo other 4 >> d
29 $ hg add d
29 $ hg add d
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
30 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
31
31
32 $ hg merge -q foo
32 $ hg merge -q foo
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
33 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
34
34
35 Second branch starting at nullrev:
35 Second branch starting at nullrev:
36
36
37 $ hg update null
37 $ hg update null
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
38 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
39 $ echo second > second
39 $ echo second > second
40 $ hg add second
40 $ hg add second
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
41 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
42 created new head
42 created new head
43
43
44 $ echo third > third
44 $ echo third > third
45 $ hg add third
45 $ hg add third
46 $ hg mv second fourth
46 $ hg mv second fourth
47 $ hg commit -m third -d "2020-01-01 10:01"
47 $ hg commit -m third -d "2020-01-01 10:01"
48
48
49 Make sure user/global hgrc does not affect tests
49 Make sure user/global hgrc does not affect tests
50
50
51 $ echo '[command-templates]' > .hg/hgrc
51 $ echo '[command-templates]' > .hg/hgrc
52 $ echo 'log =' >> .hg/hgrc
52 $ echo 'log =' >> .hg/hgrc
53 $ echo '[ui]' > .hg/hgrc
53 $ echo '[ui]' > .hg/hgrc
54 $ echo 'style =' >> .hg/hgrc
54 $ echo 'style =' >> .hg/hgrc
55
55
56 Add some simple styles to settings
56 Add some simple styles to settings
57
57
58 $ cat <<'EOF' >> .hg/hgrc
58 $ cat <<'EOF' >> .hg/hgrc
59 > [templates]
59 > [templates]
60 > simple = "{rev}\n"
60 > simple = "{rev}\n"
61 > simple2 = {rev}\n
61 > simple2 = {rev}\n
62 > rev = "should not precede {rev} keyword\n"
62 > rev = "should not precede {rev} keyword\n"
63 > EOF
63 > EOF
64
64
65 $ hg log -l1 -Tsimple
65 $ hg log -l1 -Tsimple
66 8
66 8
67 $ hg log -l1 -Tsimple2
67 $ hg log -l1 -Tsimple2
68 8
68 8
69 $ hg log -l1 -Trev
69 $ hg log -l1 -Trev
70 should not precede 8 keyword
70 should not precede 8 keyword
71 $ hg log -l1 -T '{simple}'
71 $ hg log -l1 -T '{simple}'
72 8
72 8
73
73
74 Map file shouldn't see user templates:
74 Map file shouldn't see user templates:
75
75
76 $ cat <<EOF > tmpl
76 $ cat <<EOF > tmpl
77 > changeset = 'nothing expanded:{simple}\n'
77 > changeset = 'nothing expanded:{simple}\n'
78 > EOF
78 > EOF
79 $ hg log -l1 --style ./tmpl
79 $ hg log -l1 --style ./tmpl
80 nothing expanded:
80 nothing expanded:
81
81
82 Test templates and style maps in files:
82 Test templates and style maps in files:
83
83
84 $ echo "{rev}" > tmpl
84 $ echo "{rev}" > tmpl
85 $ hg log -l1 -T./tmpl
85 $ hg log -l1 -T./tmpl
86 8
86 8
87 $ hg log -l1 -Tblah/blah
87 $ hg log -l1 -Tblah/blah
88 blah/blah (no-eol)
88 blah/blah (no-eol)
89
89
90 $ printf 'changeset = "{rev}\\n"\n' > map-simple
90 $ printf 'changeset = "{rev}\\n"\n' > map-simple
91 $ hg log -l1 -T./map-simple
91 $ hg log -l1 -T./map-simple
92 8
92 8
93
93
94 a map file may have [templates] and [templatealias] sections:
94 a map file may have [templates] and [templatealias] sections:
95
95
96 $ cat <<'EOF' > map-simple
96 $ cat <<'EOF' > map-simple
97 > [templates]
97 > [templates]
98 > changeset = "{a}\n"
98 > changeset = "{a}\n"
99 > [templatealias]
99 > [templatealias]
100 > a = rev
100 > a = rev
101 > EOF
101 > EOF
102 $ hg log -l1 -T./map-simple
102 $ hg log -l1 -T./map-simple
103 8
103 8
104
104
105 so it can be included in hgrc
105 so it can be included in hgrc
106
106
107 $ cat <<EOF > myhgrc
107 $ cat <<EOF > myhgrc
108 > %include $HGRCPATH
108 > %include $HGRCPATH
109 > %include map-simple
109 > %include map-simple
110 > [templates]
110 > [templates]
111 > foo = "{changeset}"
111 > foo = "{changeset}"
112 > EOF
112 > EOF
113 $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
113 $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
114 8
114 8
115 $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
115 $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
116 8
116 8
117
117
118 Test template map inheritance
118 Test template map inheritance
119
119
120 $ echo "__base__ = map-cmdline.default" > map-simple
120 $ echo "__base__ = map-cmdline.default" > map-simple
121 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
121 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
122 $ hg log -l1 -T./map-simple
122 $ hg log -l1 -T./map-simple
123 changeset: ***8***
123 changeset: ***8***
124 tag: tip
124 tag: tip
125 user: test
125 user: test
126 date: Wed Jan 01 10:01:00 2020 +0000
126 date: Wed Jan 01 10:01:00 2020 +0000
127 summary: third
127 summary: third
128
128
129 Test map inheritance with non-existent base
129 Test map inheritance with non-existent base
130
130
131 $ echo "__base__ = non-existent" > map-base-nonexistent
131 $ echo "__base__ = non-existent" > map-base-nonexistent
132 $ hg log -l1 -T./map-base-nonexistent
132 $ hg log -l1 -T./map-base-nonexistent
133 abort: style '$TESTTMP/a/non-existent' not found
133 abort: style '$TESTTMP/a/non-existent' not found
134 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
134 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
135 [255]
135 [255]
136
136
137 Test map inheritance with directory as base
137 Test map inheritance with directory as base
138
138
139 $ mkdir somedir
139 $ mkdir somedir
140 $ echo "__base__ = somedir" > map-base-dir
140 $ echo "__base__ = somedir" > map-base-dir
141 $ hg log -l1 -T./map-base-dir
141 $ hg log -l1 -T./map-base-dir
142 abort: Is a directory: '$TESTTMP/a/somedir' (no-windows !)
142 abort: Is a directory: '$TESTTMP/a/somedir' (no-windows !)
143 abort: $TESTTMP/a/somedir: Access is denied (windows !)
143 abort: $TESTTMP/a/somedir: Access is denied (windows !)
144 [255]
144 [255]
145
145
146 Test including a built-in template map
146 Test including a built-in template map
147
147
148 $ cat <<'EOF' > map-include-builtin
148 $ cat <<'EOF' > map-include-builtin
149 > %include map-cmdline.default
149 > %include map-cmdline.default
150 > [templates]
150 > [templates]
151 > changeset = "{changeset_quiet}\n"
151 > changeset = "{changeset_quiet}\n"
152 > EOF
152 > EOF
153 $ hg log -l1 -T./map-include-builtin
153 $ hg log -l1 -T./map-include-builtin
154 8:95c24699272e
154 8:95c24699272e
155
155
156
156
157 Test including a nonexistent template map
157 Test including a nonexistent template map
158 BROKEN: This should probably be an error just like the bad __base__ above
158 BROKEN: This should probably be an error just like the bad __base__ above
159
159
160 $ cat <<'EOF' > map-include-nonexistent
160 $ cat <<'EOF' > map-include-nonexistent
161 > %include nonexistent
161 > %include nonexistent
162 > [templates]
162 > [templates]
163 > changeset = "test\n"
163 > changeset = "test\n"
164 > EOF
164 > EOF
165 $ hg log -l1 -T./map-include-nonexistent
165 $ hg log -l1 -T./map-include-nonexistent
166 test
166 test
167
167
168 Test including a directory as template map
168 Test including a directory as template map
169 BROKEN: This should probably be an error just like the bad __base__ above
169 BROKEN: This should probably be an error just like the bad __base__ above
170
170
171 $ cat <<'EOF' > map-include-dir
171 $ cat <<'EOF' > map-include-dir
172 > %include somedir
172 > %include somedir
173 > [templates]
173 > [templates]
174 > changeset = "test\n"
174 > changeset = "test\n"
175 > EOF
175 > EOF
176 $ hg log -l1 -T./map-include-dir
176 $ hg log -l1 -T./map-include-dir
177 test
177 test
178
178
179 Test docheader, docfooter and separator in template map
179 Test docheader, docfooter and separator in template map
180
180
181 $ cat <<'EOF' > map-myjson
181 $ cat <<'EOF' > map-myjson
182 > docheader = '\{\n'
182 > docheader = '\{\n'
183 > docfooter = '\n}\n'
183 > docfooter = '\n}\n'
184 > separator = ',\n'
184 > separator = ',\n'
185 > changeset = ' {dict(rev, node|short)|json}'
185 > changeset = ' {dict(rev, node|short)|json}'
186 > EOF
186 > EOF
187 $ hg log -l2 -T./map-myjson
187 $ hg log -l2 -T./map-myjson
188 {
188 {
189 {"node": "95c24699272e", "rev": 8},
189 {"node": "95c24699272e", "rev": 8},
190 {"node": "29114dbae42b", "rev": 7}
190 {"node": "29114dbae42b", "rev": 7}
191 }
191 }
192
192
193 Test docheader, docfooter and separator in [templates] section
193 Test docheader, docfooter and separator in [templates] section
194
194
195 $ cat <<'EOF' >> .hg/hgrc
195 $ cat <<'EOF' >> .hg/hgrc
196 > [templates]
196 > [templates]
197 > myjson = ' {dict(rev, node|short)|json}'
197 > myjson = ' {dict(rev, node|short)|json}'
198 > myjson:docheader = '\{\n'
198 > myjson:docheader = '\{\n'
199 > myjson:docfooter = '\n}\n'
199 > myjson:docfooter = '\n}\n'
200 > myjson:separator = ',\n'
200 > myjson:separator = ',\n'
201 > :docheader = 'should not be selected as a docheader for literal templates\n'
201 > :docheader = 'should not be selected as a docheader for literal templates\n'
202 > EOF
202 > EOF
203 $ hg log -l2 -Tmyjson
203 $ hg log -l2 -Tmyjson
204 {
204 {
205 {"node": "95c24699272e", "rev": 8},
205 {"node": "95c24699272e", "rev": 8},
206 {"node": "29114dbae42b", "rev": 7}
206 {"node": "29114dbae42b", "rev": 7}
207 }
207 }
208 $ hg log -l1 -T'{rev}\n'
208 $ hg log -l1 -T'{rev}\n'
209 8
209 8
210
210
211 Template should precede style option
211 Template should precede style option
212
212
213 $ hg log -l1 --style default -T '{rev}\n'
213 $ hg log -l1 --style default -T '{rev}\n'
214 8
214 8
215
215
216 Add a commit with empty description, to ensure that the templates
216 Add a commit with empty description, to ensure that the templates
217 below will omit the description line.
217 below will omit the description line.
218
218
219 $ echo c >> c
219 $ echo c >> c
220 $ hg add c
220 $ hg add c
221 $ hg commit -qm ' '
221 $ hg commit -qm ' '
222
222
223 Default style is like normal output. Phases style should be the same
223 Default style is like normal output. Phases style should be the same
224 as default style, except for extra phase lines.
224 as default style, except for extra phase lines.
225
225
226 $ hg log > log.out
226 $ hg log > log.out
227 $ hg log --style default > style.out
227 $ hg log --style default > style.out
228 $ cmp log.out style.out || diff -u log.out style.out
228 $ cmp log.out style.out || diff -u log.out style.out
229 $ hg log -T phases > phases.out
229 $ hg log -T phases > phases.out
230 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
230 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
231 +phase: draft
231 +phase: draft
232 +phase: draft
232 +phase: draft
233 +phase: draft
233 +phase: draft
234 +phase: draft
234 +phase: draft
235 +phase: draft
235 +phase: draft
236 +phase: draft
236 +phase: draft
237 +phase: draft
237 +phase: draft
238 +phase: draft
238 +phase: draft
239 +phase: draft
239 +phase: draft
240 +phase: draft
240 +phase: draft
241
241
242 $ hg log -v > log.out
242 $ hg log -v > log.out
243 $ hg log -v --style default > style.out
243 $ hg log -v --style default > style.out
244 $ cmp log.out style.out || diff -u log.out style.out
244 $ cmp log.out style.out || diff -u log.out style.out
245 $ hg log -v -T phases > phases.out
245 $ hg log -v -T phases > phases.out
246 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
246 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
247 +phase: draft
247 +phase: draft
248 +phase: draft
248 +phase: draft
249 +phase: draft
249 +phase: draft
250 +phase: draft
250 +phase: draft
251 +phase: draft
251 +phase: draft
252 +phase: draft
252 +phase: draft
253 +phase: draft
253 +phase: draft
254 +phase: draft
254 +phase: draft
255 +phase: draft
255 +phase: draft
256 +phase: draft
256 +phase: draft
257
257
258 $ hg log -q > log.out
258 $ hg log -q > log.out
259 $ hg log -q --style default > style.out
259 $ hg log -q --style default > style.out
260 $ cmp log.out style.out || diff -u log.out style.out
260 $ cmp log.out style.out || diff -u log.out style.out
261 $ hg log -q -T phases > phases.out
261 $ hg log -q -T phases > phases.out
262 $ cmp log.out phases.out || diff -u log.out phases.out
262 $ cmp log.out phases.out || diff -u log.out phases.out
263
263
264 $ hg log --debug > log.out
264 $ hg log --debug > log.out
265 $ hg log --debug --style default > style.out
265 $ hg log --debug --style default > style.out
266 $ cmp log.out style.out || diff -u log.out style.out
266 $ cmp log.out style.out || diff -u log.out style.out
267 $ hg log --debug -T phases > phases.out
267 $ hg log --debug -T phases > phases.out
268 $ cmp log.out phases.out || diff -u log.out phases.out
268 $ cmp log.out phases.out || diff -u log.out phases.out
269
269
270 Default style of working-directory revision should also be the same (but
270 Default style of working-directory revision should also be the same (but
271 date may change while running tests):
271 date may change while running tests):
272
272
273 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
273 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
274 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
274 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
275 $ cmp log.out style.out || diff -u log.out style.out
275 $ cmp log.out style.out || diff -u log.out style.out
276
276
277 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
277 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
278 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
278 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
279 $ cmp log.out style.out || diff -u log.out style.out
279 $ cmp log.out style.out || diff -u log.out style.out
280
280
281 $ hg log -r 'wdir()' -q > log.out
281 $ hg log -r 'wdir()' -q > log.out
282 $ hg log -r 'wdir()' -q --style default > style.out
282 $ hg log -r 'wdir()' -q --style default > style.out
283 $ cmp log.out style.out || diff -u log.out style.out
283 $ cmp log.out style.out || diff -u log.out style.out
284
284
285 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
285 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
286 $ hg log -r 'wdir()' --debug --style default \
286 $ hg log -r 'wdir()' --debug --style default \
287 > | sed 's|^date:.*|date:|' > style.out
287 > | sed 's|^date:.*|date:|' > style.out
288 $ cmp log.out style.out || diff -u log.out style.out
288 $ cmp log.out style.out || diff -u log.out style.out
289
289
290 Default style should also preserve color information (issue2866):
290 Default style should also preserve color information (issue2866):
291
291
292 $ cp $HGRCPATH $HGRCPATH-bak
292 $ cp $HGRCPATH $HGRCPATH-bak
293 $ cat <<EOF >> $HGRCPATH
293 $ cat <<EOF >> $HGRCPATH
294 > [extensions]
294 > [extensions]
295 > color=
295 > color=
296 > EOF
296 > EOF
297
297
298 $ hg --color=debug log > log.out
298 $ hg --color=debug log > log.out
299 $ hg --color=debug log --style default > style.out
299 $ hg --color=debug log --style default > style.out
300 $ cmp log.out style.out || diff -u log.out style.out
300 $ cmp log.out style.out || diff -u log.out style.out
301 $ hg --color=debug log -T phases > phases.out
301 $ hg --color=debug log -T phases > phases.out
302 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
302 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
303 +[log.phase|phase: draft]
303 +[log.phase|phase: draft]
304 +[log.phase|phase: draft]
304 +[log.phase|phase: draft]
305 +[log.phase|phase: draft]
305 +[log.phase|phase: draft]
306 +[log.phase|phase: draft]
306 +[log.phase|phase: draft]
307 +[log.phase|phase: draft]
307 +[log.phase|phase: draft]
308 +[log.phase|phase: draft]
308 +[log.phase|phase: draft]
309 +[log.phase|phase: draft]
309 +[log.phase|phase: draft]
310 +[log.phase|phase: draft]
310 +[log.phase|phase: draft]
311 +[log.phase|phase: draft]
311 +[log.phase|phase: draft]
312 +[log.phase|phase: draft]
312 +[log.phase|phase: draft]
313
313
314 $ hg --color=debug -v log > log.out
314 $ hg --color=debug -v log > log.out
315 $ hg --color=debug -v log --style default > style.out
315 $ hg --color=debug -v log --style default > style.out
316 $ cmp log.out style.out || diff -u log.out style.out
316 $ cmp log.out style.out || diff -u log.out style.out
317 $ hg --color=debug -v log -T phases > phases.out
317 $ hg --color=debug -v log -T phases > phases.out
318 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
318 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
319 +[log.phase|phase: draft]
319 +[log.phase|phase: draft]
320 +[log.phase|phase: draft]
320 +[log.phase|phase: draft]
321 +[log.phase|phase: draft]
321 +[log.phase|phase: draft]
322 +[log.phase|phase: draft]
322 +[log.phase|phase: draft]
323 +[log.phase|phase: draft]
323 +[log.phase|phase: draft]
324 +[log.phase|phase: draft]
324 +[log.phase|phase: draft]
325 +[log.phase|phase: draft]
325 +[log.phase|phase: draft]
326 +[log.phase|phase: draft]
326 +[log.phase|phase: draft]
327 +[log.phase|phase: draft]
327 +[log.phase|phase: draft]
328 +[log.phase|phase: draft]
328 +[log.phase|phase: draft]
329
329
330 $ hg --color=debug -q log > log.out
330 $ hg --color=debug -q log > log.out
331 $ hg --color=debug -q log --style default > style.out
331 $ hg --color=debug -q log --style default > style.out
332 $ cmp log.out style.out || diff -u log.out style.out
332 $ cmp log.out style.out || diff -u log.out style.out
333 $ hg --color=debug -q log -T phases > phases.out
333 $ hg --color=debug -q log -T phases > phases.out
334 $ cmp log.out phases.out || diff -u log.out phases.out
334 $ cmp log.out phases.out || diff -u log.out phases.out
335
335
336 $ hg --color=debug --debug log > log.out
336 $ hg --color=debug --debug log > log.out
337 $ hg --color=debug --debug log --style default > style.out
337 $ hg --color=debug --debug log --style default > style.out
338 $ cmp log.out style.out || diff -u log.out style.out
338 $ cmp log.out style.out || diff -u log.out style.out
339 $ hg --color=debug --debug log -T phases > phases.out
339 $ hg --color=debug --debug log -T phases > phases.out
340 $ cmp log.out phases.out || diff -u log.out phases.out
340 $ cmp log.out phases.out || diff -u log.out phases.out
341
341
342 $ mv $HGRCPATH-bak $HGRCPATH
342 $ mv $HGRCPATH-bak $HGRCPATH
343
343
344 Remove commit with empty commit message, so as to not pollute further
344 Remove commit with empty commit message, so as to not pollute further
345 tests.
345 tests.
346
346
347 $ hg --config extensions.strip= strip -q .
347 $ hg --config extensions.strip= strip -q .
348
348
349 Revision with no copies (used to print a traceback):
349 Revision with no copies (used to print a traceback):
350
350
351 $ hg tip -v --template '\n'
351 $ hg tip -v --template '\n'
352
352
353
353
354 Compact style works:
354 Compact style works:
355
355
356 $ hg log -Tcompact
356 $ hg log -Tcompact
357 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
357 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
358 third
358 third
359
359
360 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
360 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
361 second
361 second
362
362
363 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
363 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
364 merge
364 merge
365
365
366 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
366 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
367 new head
367 new head
368
368
369 4 bbe44766e73d 1970-01-17 04:53 +0000 person
369 4 bbe44766e73d 1970-01-17 04:53 +0000 person
370 new branch
370 new branch
371
371
372 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
372 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
373 no user, no domain
373 no user, no domain
374
374
375 2 97054abb4ab8 1970-01-14 21:20 +0000 other
375 2 97054abb4ab8 1970-01-14 21:20 +0000 other
376 no person
376 no person
377
377
378 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
378 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
379 other 1
379 other 1
380
380
381 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
381 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
382 line 1
382 line 1
383
383
384
384
385 $ hg log -v --style compact
385 $ hg log -v --style compact
386 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
386 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
387 third
387 third
388
388
389 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
389 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
390 second
390 second
391
391
392 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
392 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
393 merge
393 merge
394
394
395 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
395 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
396 new head
396 new head
397
397
398 4 bbe44766e73d 1970-01-17 04:53 +0000 person
398 4 bbe44766e73d 1970-01-17 04:53 +0000 person
399 new branch
399 new branch
400
400
401 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
401 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
402 no user, no domain
402 no user, no domain
403
403
404 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
404 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
405 no person
405 no person
406
406
407 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
407 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
408 other 1
408 other 1
409 other 2
409 other 2
410
410
411 other 3
411 other 3
412
412
413 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
413 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
414 line 1
414 line 1
415 line 2
415 line 2
416
416
417
417
418 $ hg log --debug --style compact
418 $ hg log --debug --style compact
419 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
419 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
420 third
420 third
421
421
422 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
422 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
423 second
423 second
424
424
425 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
425 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
426 merge
426 merge
427
427
428 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
428 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
429 new head
429 new head
430
430
431 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
431 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
432 new branch
432 new branch
433
433
434 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
434 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
435 no user, no domain
435 no user, no domain
436
436
437 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
437 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
438 no person
438 no person
439
439
440 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
440 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
441 other 1
441 other 1
442 other 2
442 other 2
443
443
444 other 3
444 other 3
445
445
446 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
446 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
447 line 1
447 line 1
448 line 2
448 line 2
449
449
450
450
451 Test xml styles:
451 Test xml styles:
452
452
453 $ hg log --style xml -r 'not all()'
453 $ hg log --style xml -r 'not all()'
454 <?xml version="1.0"?>
454 <?xml version="1.0"?>
455 <log>
455 <log>
456 </log>
456 </log>
457
457
458 $ hg log --style xml
458 $ hg log --style xml
459 <?xml version="1.0"?>
459 <?xml version="1.0"?>
460 <log>
460 <log>
461 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
461 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
462 <tag>tip</tag>
462 <tag>tip</tag>
463 <author email="test">test</author>
463 <author email="test">test</author>
464 <date>2020-01-01T10:01:00+00:00</date>
464 <date>2020-01-01T10:01:00+00:00</date>
465 <msg xml:space="preserve">third</msg>
465 <msg xml:space="preserve">third</msg>
466 </logentry>
466 </logentry>
467 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
467 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
468 <parent revision="-1" node="0000000000000000000000000000000000000000" />
468 <parent revision="-1" node="0000000000000000000000000000000000000000" />
469 <author email="user@hostname">User Name</author>
469 <author email="user@hostname">User Name</author>
470 <date>1970-01-12T13:46:40+00:00</date>
470 <date>1970-01-12T13:46:40+00:00</date>
471 <msg xml:space="preserve">second</msg>
471 <msg xml:space="preserve">second</msg>
472 </logentry>
472 </logentry>
473 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
473 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
474 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
474 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
475 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
475 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
476 <author email="person">person</author>
476 <author email="person">person</author>
477 <date>1970-01-18T08:40:01+00:00</date>
477 <date>1970-01-18T08:40:01+00:00</date>
478 <msg xml:space="preserve">merge</msg>
478 <msg xml:space="preserve">merge</msg>
479 </logentry>
479 </logentry>
480 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
480 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
481 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
481 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
482 <author email="person">person</author>
482 <author email="person">person</author>
483 <date>1970-01-18T08:40:00+00:00</date>
483 <date>1970-01-18T08:40:00+00:00</date>
484 <msg xml:space="preserve">new head</msg>
484 <msg xml:space="preserve">new head</msg>
485 </logentry>
485 </logentry>
486 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
486 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
487 <branch>foo</branch>
487 <branch>foo</branch>
488 <author email="person">person</author>
488 <author email="person">person</author>
489 <date>1970-01-17T04:53:20+00:00</date>
489 <date>1970-01-17T04:53:20+00:00</date>
490 <msg xml:space="preserve">new branch</msg>
490 <msg xml:space="preserve">new branch</msg>
491 </logentry>
491 </logentry>
492 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
492 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
493 <author email="person">person</author>
493 <author email="person">person</author>
494 <date>1970-01-16T01:06:40+00:00</date>
494 <date>1970-01-16T01:06:40+00:00</date>
495 <msg xml:space="preserve">no user, no domain</msg>
495 <msg xml:space="preserve">no user, no domain</msg>
496 </logentry>
496 </logentry>
497 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
497 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
498 <author email="other@place">other</author>
498 <author email="other@place">other</author>
499 <date>1970-01-14T21:20:00+00:00</date>
499 <date>1970-01-14T21:20:00+00:00</date>
500 <msg xml:space="preserve">no person</msg>
500 <msg xml:space="preserve">no person</msg>
501 </logentry>
501 </logentry>
502 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
502 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
503 <author email="other@place">A. N. Other</author>
503 <author email="other@place">A. N. Other</author>
504 <date>1970-01-13T17:33:20+00:00</date>
504 <date>1970-01-13T17:33:20+00:00</date>
505 <msg xml:space="preserve">other 1
505 <msg xml:space="preserve">other 1
506 other 2
506 other 2
507
507
508 other 3</msg>
508 other 3</msg>
509 </logentry>
509 </logentry>
510 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
510 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
511 <author email="user@hostname">User Name</author>
511 <author email="user@hostname">User Name</author>
512 <date>1970-01-12T13:46:40+00:00</date>
512 <date>1970-01-12T13:46:40+00:00</date>
513 <msg xml:space="preserve">line 1
513 <msg xml:space="preserve">line 1
514 line 2</msg>
514 line 2</msg>
515 </logentry>
515 </logentry>
516 </log>
516 </log>
517
517
518 $ hg log -v --style xml
518 $ hg log -v --style xml
519 <?xml version="1.0"?>
519 <?xml version="1.0"?>
520 <log>
520 <log>
521 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
521 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
522 <tag>tip</tag>
522 <tag>tip</tag>
523 <author email="test">test</author>
523 <author email="test">test</author>
524 <date>2020-01-01T10:01:00+00:00</date>
524 <date>2020-01-01T10:01:00+00:00</date>
525 <msg xml:space="preserve">third</msg>
525 <msg xml:space="preserve">third</msg>
526 <paths>
526 <paths>
527 <path action="A">fourth</path>
527 <path action="A">fourth</path>
528 <path action="A">third</path>
528 <path action="A">third</path>
529 <path action="R">second</path>
529 <path action="R">second</path>
530 </paths>
530 </paths>
531 <copies>
531 <copies>
532 <copy source="second">fourth</copy>
532 <copy source="second">fourth</copy>
533 </copies>
533 </copies>
534 </logentry>
534 </logentry>
535 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
535 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
536 <parent revision="-1" node="0000000000000000000000000000000000000000" />
536 <parent revision="-1" node="0000000000000000000000000000000000000000" />
537 <author email="user@hostname">User Name</author>
537 <author email="user@hostname">User Name</author>
538 <date>1970-01-12T13:46:40+00:00</date>
538 <date>1970-01-12T13:46:40+00:00</date>
539 <msg xml:space="preserve">second</msg>
539 <msg xml:space="preserve">second</msg>
540 <paths>
540 <paths>
541 <path action="A">second</path>
541 <path action="A">second</path>
542 </paths>
542 </paths>
543 </logentry>
543 </logentry>
544 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
544 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
545 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
545 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
546 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
546 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
547 <author email="person">person</author>
547 <author email="person">person</author>
548 <date>1970-01-18T08:40:01+00:00</date>
548 <date>1970-01-18T08:40:01+00:00</date>
549 <msg xml:space="preserve">merge</msg>
549 <msg xml:space="preserve">merge</msg>
550 <paths>
550 <paths>
551 </paths>
551 </paths>
552 </logentry>
552 </logentry>
553 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
553 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
554 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
554 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
555 <author email="person">person</author>
555 <author email="person">person</author>
556 <date>1970-01-18T08:40:00+00:00</date>
556 <date>1970-01-18T08:40:00+00:00</date>
557 <msg xml:space="preserve">new head</msg>
557 <msg xml:space="preserve">new head</msg>
558 <paths>
558 <paths>
559 <path action="A">d</path>
559 <path action="A">d</path>
560 </paths>
560 </paths>
561 </logentry>
561 </logentry>
562 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
562 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
563 <branch>foo</branch>
563 <branch>foo</branch>
564 <author email="person">person</author>
564 <author email="person">person</author>
565 <date>1970-01-17T04:53:20+00:00</date>
565 <date>1970-01-17T04:53:20+00:00</date>
566 <msg xml:space="preserve">new branch</msg>
566 <msg xml:space="preserve">new branch</msg>
567 <paths>
567 <paths>
568 </paths>
568 </paths>
569 </logentry>
569 </logentry>
570 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
570 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
571 <author email="person">person</author>
571 <author email="person">person</author>
572 <date>1970-01-16T01:06:40+00:00</date>
572 <date>1970-01-16T01:06:40+00:00</date>
573 <msg xml:space="preserve">no user, no domain</msg>
573 <msg xml:space="preserve">no user, no domain</msg>
574 <paths>
574 <paths>
575 <path action="M">c</path>
575 <path action="M">c</path>
576 </paths>
576 </paths>
577 </logentry>
577 </logentry>
578 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
578 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
579 <author email="other@place">other</author>
579 <author email="other@place">other</author>
580 <date>1970-01-14T21:20:00+00:00</date>
580 <date>1970-01-14T21:20:00+00:00</date>
581 <msg xml:space="preserve">no person</msg>
581 <msg xml:space="preserve">no person</msg>
582 <paths>
582 <paths>
583 <path action="A">c</path>
583 <path action="A">c</path>
584 </paths>
584 </paths>
585 </logentry>
585 </logentry>
586 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
586 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
587 <author email="other@place">A. N. Other</author>
587 <author email="other@place">A. N. Other</author>
588 <date>1970-01-13T17:33:20+00:00</date>
588 <date>1970-01-13T17:33:20+00:00</date>
589 <msg xml:space="preserve">other 1
589 <msg xml:space="preserve">other 1
590 other 2
590 other 2
591
591
592 other 3</msg>
592 other 3</msg>
593 <paths>
593 <paths>
594 <path action="A">b</path>
594 <path action="A">b</path>
595 </paths>
595 </paths>
596 </logentry>
596 </logentry>
597 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
597 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
598 <author email="user@hostname">User Name</author>
598 <author email="user@hostname">User Name</author>
599 <date>1970-01-12T13:46:40+00:00</date>
599 <date>1970-01-12T13:46:40+00:00</date>
600 <msg xml:space="preserve">line 1
600 <msg xml:space="preserve">line 1
601 line 2</msg>
601 line 2</msg>
602 <paths>
602 <paths>
603 <path action="A">a</path>
603 <path action="A">a</path>
604 </paths>
604 </paths>
605 </logentry>
605 </logentry>
606 </log>
606 </log>
607
607
608 $ hg log --debug --style xml
608 $ hg log --debug --style xml
609 <?xml version="1.0"?>
609 <?xml version="1.0"?>
610 <log>
610 <log>
611 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
611 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
612 <tag>tip</tag>
612 <tag>tip</tag>
613 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
613 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
614 <parent revision="-1" node="0000000000000000000000000000000000000000" />
614 <parent revision="-1" node="0000000000000000000000000000000000000000" />
615 <author email="test">test</author>
615 <author email="test">test</author>
616 <date>2020-01-01T10:01:00+00:00</date>
616 <date>2020-01-01T10:01:00+00:00</date>
617 <msg xml:space="preserve">third</msg>
617 <msg xml:space="preserve">third</msg>
618 <paths>
618 <paths>
619 <path action="A">fourth</path>
619 <path action="A">fourth</path>
620 <path action="A">third</path>
620 <path action="A">third</path>
621 <path action="R">second</path>
621 <path action="R">second</path>
622 </paths>
622 </paths>
623 <copies>
623 <copies>
624 <copy source="second">fourth</copy>
624 <copy source="second">fourth</copy>
625 </copies>
625 </copies>
626 <extra key="branch">default</extra>
626 <extra key="branch">default</extra>
627 </logentry>
627 </logentry>
628 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
628 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
629 <parent revision="-1" node="0000000000000000000000000000000000000000" />
629 <parent revision="-1" node="0000000000000000000000000000000000000000" />
630 <parent revision="-1" node="0000000000000000000000000000000000000000" />
630 <parent revision="-1" node="0000000000000000000000000000000000000000" />
631 <author email="user@hostname">User Name</author>
631 <author email="user@hostname">User Name</author>
632 <date>1970-01-12T13:46:40+00:00</date>
632 <date>1970-01-12T13:46:40+00:00</date>
633 <msg xml:space="preserve">second</msg>
633 <msg xml:space="preserve">second</msg>
634 <paths>
634 <paths>
635 <path action="A">second</path>
635 <path action="A">second</path>
636 </paths>
636 </paths>
637 <extra key="branch">default</extra>
637 <extra key="branch">default</extra>
638 </logentry>
638 </logentry>
639 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
639 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
640 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
640 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
641 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
641 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
642 <author email="person">person</author>
642 <author email="person">person</author>
643 <date>1970-01-18T08:40:01+00:00</date>
643 <date>1970-01-18T08:40:01+00:00</date>
644 <msg xml:space="preserve">merge</msg>
644 <msg xml:space="preserve">merge</msg>
645 <paths>
645 <paths>
646 </paths>
646 </paths>
647 <extra key="branch">default</extra>
647 <extra key="branch">default</extra>
648 </logentry>
648 </logentry>
649 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
649 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
650 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
650 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
651 <parent revision="-1" node="0000000000000000000000000000000000000000" />
651 <parent revision="-1" node="0000000000000000000000000000000000000000" />
652 <author email="person">person</author>
652 <author email="person">person</author>
653 <date>1970-01-18T08:40:00+00:00</date>
653 <date>1970-01-18T08:40:00+00:00</date>
654 <msg xml:space="preserve">new head</msg>
654 <msg xml:space="preserve">new head</msg>
655 <paths>
655 <paths>
656 <path action="A">d</path>
656 <path action="A">d</path>
657 </paths>
657 </paths>
658 <extra key="branch">default</extra>
658 <extra key="branch">default</extra>
659 </logentry>
659 </logentry>
660 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
660 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
661 <branch>foo</branch>
661 <branch>foo</branch>
662 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
662 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
663 <parent revision="-1" node="0000000000000000000000000000000000000000" />
663 <parent revision="-1" node="0000000000000000000000000000000000000000" />
664 <author email="person">person</author>
664 <author email="person">person</author>
665 <date>1970-01-17T04:53:20+00:00</date>
665 <date>1970-01-17T04:53:20+00:00</date>
666 <msg xml:space="preserve">new branch</msg>
666 <msg xml:space="preserve">new branch</msg>
667 <paths>
667 <paths>
668 </paths>
668 </paths>
669 <extra key="branch">foo</extra>
669 <extra key="branch">foo</extra>
670 </logentry>
670 </logentry>
671 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
671 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
672 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
672 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
673 <parent revision="-1" node="0000000000000000000000000000000000000000" />
673 <parent revision="-1" node="0000000000000000000000000000000000000000" />
674 <author email="person">person</author>
674 <author email="person">person</author>
675 <date>1970-01-16T01:06:40+00:00</date>
675 <date>1970-01-16T01:06:40+00:00</date>
676 <msg xml:space="preserve">no user, no domain</msg>
676 <msg xml:space="preserve">no user, no domain</msg>
677 <paths>
677 <paths>
678 <path action="M">c</path>
678 <path action="M">c</path>
679 </paths>
679 </paths>
680 <extra key="branch">default</extra>
680 <extra key="branch">default</extra>
681 </logentry>
681 </logentry>
682 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
682 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
683 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
683 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
684 <parent revision="-1" node="0000000000000000000000000000000000000000" />
684 <parent revision="-1" node="0000000000000000000000000000000000000000" />
685 <author email="other@place">other</author>
685 <author email="other@place">other</author>
686 <date>1970-01-14T21:20:00+00:00</date>
686 <date>1970-01-14T21:20:00+00:00</date>
687 <msg xml:space="preserve">no person</msg>
687 <msg xml:space="preserve">no person</msg>
688 <paths>
688 <paths>
689 <path action="A">c</path>
689 <path action="A">c</path>
690 </paths>
690 </paths>
691 <extra key="branch">default</extra>
691 <extra key="branch">default</extra>
692 </logentry>
692 </logentry>
693 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
693 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
694 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
694 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
695 <parent revision="-1" node="0000000000000000000000000000000000000000" />
695 <parent revision="-1" node="0000000000000000000000000000000000000000" />
696 <author email="other@place">A. N. Other</author>
696 <author email="other@place">A. N. Other</author>
697 <date>1970-01-13T17:33:20+00:00</date>
697 <date>1970-01-13T17:33:20+00:00</date>
698 <msg xml:space="preserve">other 1
698 <msg xml:space="preserve">other 1
699 other 2
699 other 2
700
700
701 other 3</msg>
701 other 3</msg>
702 <paths>
702 <paths>
703 <path action="A">b</path>
703 <path action="A">b</path>
704 </paths>
704 </paths>
705 <extra key="branch">default</extra>
705 <extra key="branch">default</extra>
706 </logentry>
706 </logentry>
707 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
707 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
708 <parent revision="-1" node="0000000000000000000000000000000000000000" />
708 <parent revision="-1" node="0000000000000000000000000000000000000000" />
709 <parent revision="-1" node="0000000000000000000000000000000000000000" />
709 <parent revision="-1" node="0000000000000000000000000000000000000000" />
710 <author email="user@hostname">User Name</author>
710 <author email="user@hostname">User Name</author>
711 <date>1970-01-12T13:46:40+00:00</date>
711 <date>1970-01-12T13:46:40+00:00</date>
712 <msg xml:space="preserve">line 1
712 <msg xml:space="preserve">line 1
713 line 2</msg>
713 line 2</msg>
714 <paths>
714 <paths>
715 <path action="A">a</path>
715 <path action="A">a</path>
716 </paths>
716 </paths>
717 <extra key="branch">default</extra>
717 <extra key="branch">default</extra>
718 </logentry>
718 </logentry>
719 </log>
719 </log>
720
720
721
721
722 test CBOR style:
722 test CBOR style:
723
723
724 $ cat <<'EOF' > "$TESTTMP/decodecborarray.py"
724 $ cat <<'EOF' > "$TESTTMP/decodecborarray.py"
725 > from __future__ import absolute_import
725 > from __future__ import absolute_import
726 > from mercurial import (
726 > from mercurial import (
727 > dispatch,
727 > dispatch,
728 > )
728 > )
729 > from mercurial.utils import (
729 > from mercurial.utils import (
730 > cborutil,
730 > cborutil,
731 > procutil,
731 > procutil,
732 > stringutil,
732 > stringutil,
733 > )
733 > )
734 > dispatch.initstdio()
734 > dispatch.initstdio()
735 > data = procutil.stdin.read()
735 > data = procutil.stdin.read()
736 > # our CBOR decoder doesn't support parsing indefinite-length arrays,
736 > # our CBOR decoder doesn't support parsing indefinite-length arrays,
737 > # but the log output is indefinite stream by nature.
737 > # but the log output is indefinite stream by nature.
738 > assert data[:1] == cborutil.BEGIN_INDEFINITE_ARRAY
738 > assert data[:1] == cborutil.BEGIN_INDEFINITE_ARRAY
739 > assert data[-1:] == cborutil.BREAK
739 > assert data[-1:] == cborutil.BREAK
740 > items = cborutil.decodeall(data[1:-1])
740 > items = cborutil.decodeall(data[1:-1])
741 > procutil.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
741 > procutil.stdout.write(stringutil.pprint(items, indent=1) + b'\n')
742 > EOF
742 > EOF
743
743
744 $ hg log -k nosuch -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
744 $ hg log -k nosuch -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
745 []
745 []
746
746
747 $ hg log -qr0:1 -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
747 $ hg log -qr0:1 -Tcbor | "$PYTHON" "$TESTTMP/decodecborarray.py"
748 [
748 [
749 {
749 {
750 'node': '1e4e1b8f71e05681d422154f5421e385fec3454f',
750 'node': '1e4e1b8f71e05681d422154f5421e385fec3454f',
751 'rev': 0
751 'rev': 0
752 },
752 },
753 {
753 {
754 'node': 'b608e9d1a3f0273ccf70fb85fd6866b3482bf965',
754 'node': 'b608e9d1a3f0273ccf70fb85fd6866b3482bf965',
755 'rev': 1
755 'rev': 1
756 }
756 }
757 ]
757 ]
758
758
759 $ hg log -vpr . -Tcbor --stat | "$PYTHON" "$TESTTMP/decodecborarray.py"
759 $ hg log -vpr . -Tcbor --stat | "$PYTHON" "$TESTTMP/decodecborarray.py"
760 [
760 [
761 {
761 {
762 'bookmarks': [],
762 'bookmarks': [],
763 'branch': 'default',
763 'branch': 'default',
764 'date': [
764 'date': [
765 1577872860,
765 1577872860,
766 0
766 0
767 ],
767 ],
768 'desc': 'third',
768 'desc': 'third',
769 'diff': 'diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n',
769 'diff': 'diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n',
770 'diffstat': ' fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n',
770 'diffstat': ' fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n',
771 'files': [
771 'files': [
772 'fourth',
772 'fourth',
773 'second',
773 'second',
774 'third'
774 'third'
775 ],
775 ],
776 'node': '95c24699272ef57d062b8bccc32c878bf841784a',
776 'node': '95c24699272ef57d062b8bccc32c878bf841784a',
777 'parents': [
777 'parents': [
778 '29114dbae42b9f078cf2714dbe3a86bba8ec7453'
778 '29114dbae42b9f078cf2714dbe3a86bba8ec7453'
779 ],
779 ],
780 'phase': 'draft',
780 'phase': 'draft',
781 'rev': 8,
781 'rev': 8,
782 'tags': [
782 'tags': [
783 'tip'
783 'tip'
784 ],
784 ],
785 'user': 'test'
785 'user': 'test'
786 }
786 }
787 ]
787 ]
788
788
789 $ hg log -r . -T'cbor(rev, node|short)' | "$PYTHON" "$TESTTMP/decodecborarray.py"
789 $ hg log -r . -T'cbor(rev, node|short)' | "$PYTHON" "$TESTTMP/decodecborarray.py"
790 [
790 [
791 {
791 {
792 'node': '95c24699272e',
792 'node': '95c24699272e',
793 'rev': 8
793 'rev': 8
794 }
794 }
795 ]
795 ]
796
796
797 $ hg log -r . -T'cbor()' | "$PYTHON" "$TESTTMP/decodecborarray.py"
797 $ hg log -r . -T'cbor()' | "$PYTHON" "$TESTTMP/decodecborarray.py"
798 [
798 [
799 {}
799 {}
800 ]
800 ]
801
801
802 Test JSON style:
802 Test JSON style:
803
803
804 $ hg log -k nosuch -Tjson
804 $ hg log -k nosuch -Tjson
805 [
805 [
806 ]
806 ]
807
807
808 $ hg log -qr . -Tjson
808 $ hg log -qr . -Tjson
809 [
809 [
810 {
810 {
811 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
811 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
812 "rev": 8
812 "rev": 8
813 }
813 }
814 ]
814 ]
815
815
816 $ hg log -vpr . -Tjson --stat
816 $ hg log -vpr . -Tjson --stat
817 [
817 [
818 {
818 {
819 "bookmarks": [],
819 "bookmarks": [],
820 "branch": "default",
820 "branch": "default",
821 "date": [1577872860, 0],
821 "date": [1577872860, 0],
822 "desc": "third",
822 "desc": "third",
823 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n",
823 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n",
824 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
824 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
825 "files": ["fourth", "second", "third"],
825 "files": ["fourth", "second", "third"],
826 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
826 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
827 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
827 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
828 "phase": "draft",
828 "phase": "draft",
829 "rev": 8,
829 "rev": 8,
830 "tags": ["tip"],
830 "tags": ["tip"],
831 "user": "test"
831 "user": "test"
832 }
832 }
833 ]
833 ]
834
834
835 honor --git but not format-breaking diffopts
835 honor --git but not format-breaking diffopts
836 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
836 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
837 [
837 [
838 {
838 {
839 "bookmarks": [],
839 "bookmarks": [],
840 "branch": "default",
840 "branch": "default",
841 "date": [1577872860, 0],
841 "date": [1577872860, 0],
842 "desc": "third",
842 "desc": "third",
843 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n",
843 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n",
844 "files": ["fourth", "second", "third"],
844 "files": ["fourth", "second", "third"],
845 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
845 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
846 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
846 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
847 "phase": "draft",
847 "phase": "draft",
848 "rev": 8,
848 "rev": 8,
849 "tags": ["tip"],
849 "tags": ["tip"],
850 "user": "test"
850 "user": "test"
851 }
851 }
852 ]
852 ]
853
853
854 $ hg log -T json
854 $ hg log -T json
855 [
855 [
856 {
856 {
857 "bookmarks": [],
857 "bookmarks": [],
858 "branch": "default",
858 "branch": "default",
859 "date": [1577872860, 0],
859 "date": [1577872860, 0],
860 "desc": "third",
860 "desc": "third",
861 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
861 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
862 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
862 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
863 "phase": "draft",
863 "phase": "draft",
864 "rev": 8,
864 "rev": 8,
865 "tags": ["tip"],
865 "tags": ["tip"],
866 "user": "test"
866 "user": "test"
867 },
867 },
868 {
868 {
869 "bookmarks": [],
869 "bookmarks": [],
870 "branch": "default",
870 "branch": "default",
871 "date": [1000000, 0],
871 "date": [1000000, 0],
872 "desc": "second",
872 "desc": "second",
873 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
873 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
874 "parents": ["0000000000000000000000000000000000000000"],
874 "parents": ["0000000000000000000000000000000000000000"],
875 "phase": "draft",
875 "phase": "draft",
876 "rev": 7,
876 "rev": 7,
877 "tags": [],
877 "tags": [],
878 "user": "User Name <user@hostname>"
878 "user": "User Name <user@hostname>"
879 },
879 },
880 {
880 {
881 "bookmarks": [],
881 "bookmarks": [],
882 "branch": "default",
882 "branch": "default",
883 "date": [1500001, 0],
883 "date": [1500001, 0],
884 "desc": "merge",
884 "desc": "merge",
885 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
885 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
886 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
886 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
887 "phase": "draft",
887 "phase": "draft",
888 "rev": 6,
888 "rev": 6,
889 "tags": [],
889 "tags": [],
890 "user": "person"
890 "user": "person"
891 },
891 },
892 {
892 {
893 "bookmarks": [],
893 "bookmarks": [],
894 "branch": "default",
894 "branch": "default",
895 "date": [1500000, 0],
895 "date": [1500000, 0],
896 "desc": "new head",
896 "desc": "new head",
897 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
897 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
898 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
898 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
899 "phase": "draft",
899 "phase": "draft",
900 "rev": 5,
900 "rev": 5,
901 "tags": [],
901 "tags": [],
902 "user": "person"
902 "user": "person"
903 },
903 },
904 {
904 {
905 "bookmarks": [],
905 "bookmarks": [],
906 "branch": "foo",
906 "branch": "foo",
907 "date": [1400000, 0],
907 "date": [1400000, 0],
908 "desc": "new branch",
908 "desc": "new branch",
909 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
909 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
910 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
910 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
911 "phase": "draft",
911 "phase": "draft",
912 "rev": 4,
912 "rev": 4,
913 "tags": [],
913 "tags": [],
914 "user": "person"
914 "user": "person"
915 },
915 },
916 {
916 {
917 "bookmarks": [],
917 "bookmarks": [],
918 "branch": "default",
918 "branch": "default",
919 "date": [1300000, 0],
919 "date": [1300000, 0],
920 "desc": "no user, no domain",
920 "desc": "no user, no domain",
921 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
921 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
922 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
922 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
923 "phase": "draft",
923 "phase": "draft",
924 "rev": 3,
924 "rev": 3,
925 "tags": [],
925 "tags": [],
926 "user": "person"
926 "user": "person"
927 },
927 },
928 {
928 {
929 "bookmarks": [],
929 "bookmarks": [],
930 "branch": "default",
930 "branch": "default",
931 "date": [1200000, 0],
931 "date": [1200000, 0],
932 "desc": "no person",
932 "desc": "no person",
933 "node": "97054abb4ab824450e9164180baf491ae0078465",
933 "node": "97054abb4ab824450e9164180baf491ae0078465",
934 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
934 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
935 "phase": "draft",
935 "phase": "draft",
936 "rev": 2,
936 "rev": 2,
937 "tags": [],
937 "tags": [],
938 "user": "other@place"
938 "user": "other@place"
939 },
939 },
940 {
940 {
941 "bookmarks": [],
941 "bookmarks": [],
942 "branch": "default",
942 "branch": "default",
943 "date": [1100000, 0],
943 "date": [1100000, 0],
944 "desc": "other 1\nother 2\n\nother 3",
944 "desc": "other 1\nother 2\n\nother 3",
945 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
945 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
946 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
946 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
947 "phase": "draft",
947 "phase": "draft",
948 "rev": 1,
948 "rev": 1,
949 "tags": [],
949 "tags": [],
950 "user": "A. N. Other <other@place>"
950 "user": "A. N. Other <other@place>"
951 },
951 },
952 {
952 {
953 "bookmarks": [],
953 "bookmarks": [],
954 "branch": "default",
954 "branch": "default",
955 "date": [1000000, 0],
955 "date": [1000000, 0],
956 "desc": "line 1\nline 2",
956 "desc": "line 1\nline 2",
957 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
957 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
958 "parents": ["0000000000000000000000000000000000000000"],
958 "parents": ["0000000000000000000000000000000000000000"],
959 "phase": "draft",
959 "phase": "draft",
960 "rev": 0,
960 "rev": 0,
961 "tags": [],
961 "tags": [],
962 "user": "User Name <user@hostname>"
962 "user": "User Name <user@hostname>"
963 }
963 }
964 ]
964 ]
965
965
966 $ hg heads -v -Tjson
966 $ hg heads -v -Tjson
967 [
967 [
968 {
968 {
969 "bookmarks": [],
969 "bookmarks": [],
970 "branch": "default",
970 "branch": "default",
971 "date": [1577872860, 0],
971 "date": [1577872860, 0],
972 "desc": "third",
972 "desc": "third",
973 "files": ["fourth", "second", "third"],
973 "files": ["fourth", "second", "third"],
974 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
974 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
975 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
975 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
976 "phase": "draft",
976 "phase": "draft",
977 "rev": 8,
977 "rev": 8,
978 "tags": ["tip"],
978 "tags": ["tip"],
979 "user": "test"
979 "user": "test"
980 },
980 },
981 {
981 {
982 "bookmarks": [],
982 "bookmarks": [],
983 "branch": "default",
983 "branch": "default",
984 "date": [1500001, 0],
984 "date": [1500001, 0],
985 "desc": "merge",
985 "desc": "merge",
986 "files": [],
986 "files": [],
987 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
987 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
988 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
988 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
989 "phase": "draft",
989 "phase": "draft",
990 "rev": 6,
990 "rev": 6,
991 "tags": [],
991 "tags": [],
992 "user": "person"
992 "user": "person"
993 },
993 },
994 {
994 {
995 "bookmarks": [],
995 "bookmarks": [],
996 "branch": "foo",
996 "branch": "foo",
997 "date": [1400000, 0],
997 "date": [1400000, 0],
998 "desc": "new branch",
998 "desc": "new branch",
999 "files": [],
999 "files": [],
1000 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1000 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1001 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1001 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1002 "phase": "draft",
1002 "phase": "draft",
1003 "rev": 4,
1003 "rev": 4,
1004 "tags": [],
1004 "tags": [],
1005 "user": "person"
1005 "user": "person"
1006 }
1006 }
1007 ]
1007 ]
1008
1008
1009 $ hg log --debug -Tjson
1009 $ hg log --debug -Tjson
1010 [
1010 [
1011 {
1011 {
1012 "added": ["fourth", "third"],
1012 "added": ["fourth", "third"],
1013 "bookmarks": [],
1013 "bookmarks": [],
1014 "branch": "default",
1014 "branch": "default",
1015 "date": [1577872860, 0],
1015 "date": [1577872860, 0],
1016 "desc": "third",
1016 "desc": "third",
1017 "extra": {"branch": "default"},
1017 "extra": {"branch": "default"},
1018 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
1018 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
1019 "modified": [],
1019 "modified": [],
1020 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
1020 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
1021 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
1021 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
1022 "phase": "draft",
1022 "phase": "draft",
1023 "removed": ["second"],
1023 "removed": ["second"],
1024 "rev": 8,
1024 "rev": 8,
1025 "tags": ["tip"],
1025 "tags": ["tip"],
1026 "user": "test"
1026 "user": "test"
1027 },
1027 },
1028 {
1028 {
1029 "added": ["second"],
1029 "added": ["second"],
1030 "bookmarks": [],
1030 "bookmarks": [],
1031 "branch": "default",
1031 "branch": "default",
1032 "date": [1000000, 0],
1032 "date": [1000000, 0],
1033 "desc": "second",
1033 "desc": "second",
1034 "extra": {"branch": "default"},
1034 "extra": {"branch": "default"},
1035 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
1035 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
1036 "modified": [],
1036 "modified": [],
1037 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
1037 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
1038 "parents": ["0000000000000000000000000000000000000000"],
1038 "parents": ["0000000000000000000000000000000000000000"],
1039 "phase": "draft",
1039 "phase": "draft",
1040 "removed": [],
1040 "removed": [],
1041 "rev": 7,
1041 "rev": 7,
1042 "tags": [],
1042 "tags": [],
1043 "user": "User Name <user@hostname>"
1043 "user": "User Name <user@hostname>"
1044 },
1044 },
1045 {
1045 {
1046 "added": [],
1046 "added": [],
1047 "bookmarks": [],
1047 "bookmarks": [],
1048 "branch": "default",
1048 "branch": "default",
1049 "date": [1500001, 0],
1049 "date": [1500001, 0],
1050 "desc": "merge",
1050 "desc": "merge",
1051 "extra": {"branch": "default"},
1051 "extra": {"branch": "default"},
1052 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1052 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1053 "modified": [],
1053 "modified": [],
1054 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
1054 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
1055 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1055 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1056 "phase": "draft",
1056 "phase": "draft",
1057 "removed": [],
1057 "removed": [],
1058 "rev": 6,
1058 "rev": 6,
1059 "tags": [],
1059 "tags": [],
1060 "user": "person"
1060 "user": "person"
1061 },
1061 },
1062 {
1062 {
1063 "added": ["d"],
1063 "added": ["d"],
1064 "bookmarks": [],
1064 "bookmarks": [],
1065 "branch": "default",
1065 "branch": "default",
1066 "date": [1500000, 0],
1066 "date": [1500000, 0],
1067 "desc": "new head",
1067 "desc": "new head",
1068 "extra": {"branch": "default"},
1068 "extra": {"branch": "default"},
1069 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1069 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1070 "modified": [],
1070 "modified": [],
1071 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1071 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1072 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1072 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1073 "phase": "draft",
1073 "phase": "draft",
1074 "removed": [],
1074 "removed": [],
1075 "rev": 5,
1075 "rev": 5,
1076 "tags": [],
1076 "tags": [],
1077 "user": "person"
1077 "user": "person"
1078 },
1078 },
1079 {
1079 {
1080 "added": [],
1080 "added": [],
1081 "bookmarks": [],
1081 "bookmarks": [],
1082 "branch": "foo",
1082 "branch": "foo",
1083 "date": [1400000, 0],
1083 "date": [1400000, 0],
1084 "desc": "new branch",
1084 "desc": "new branch",
1085 "extra": {"branch": "foo"},
1085 "extra": {"branch": "foo"},
1086 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1086 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1087 "modified": [],
1087 "modified": [],
1088 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1088 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1089 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1089 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1090 "phase": "draft",
1090 "phase": "draft",
1091 "removed": [],
1091 "removed": [],
1092 "rev": 4,
1092 "rev": 4,
1093 "tags": [],
1093 "tags": [],
1094 "user": "person"
1094 "user": "person"
1095 },
1095 },
1096 {
1096 {
1097 "added": [],
1097 "added": [],
1098 "bookmarks": [],
1098 "bookmarks": [],
1099 "branch": "default",
1099 "branch": "default",
1100 "date": [1300000, 0],
1100 "date": [1300000, 0],
1101 "desc": "no user, no domain",
1101 "desc": "no user, no domain",
1102 "extra": {"branch": "default"},
1102 "extra": {"branch": "default"},
1103 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1103 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1104 "modified": ["c"],
1104 "modified": ["c"],
1105 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1105 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1106 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1106 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1107 "phase": "draft",
1107 "phase": "draft",
1108 "removed": [],
1108 "removed": [],
1109 "rev": 3,
1109 "rev": 3,
1110 "tags": [],
1110 "tags": [],
1111 "user": "person"
1111 "user": "person"
1112 },
1112 },
1113 {
1113 {
1114 "added": ["c"],
1114 "added": ["c"],
1115 "bookmarks": [],
1115 "bookmarks": [],
1116 "branch": "default",
1116 "branch": "default",
1117 "date": [1200000, 0],
1117 "date": [1200000, 0],
1118 "desc": "no person",
1118 "desc": "no person",
1119 "extra": {"branch": "default"},
1119 "extra": {"branch": "default"},
1120 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1120 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1121 "modified": [],
1121 "modified": [],
1122 "node": "97054abb4ab824450e9164180baf491ae0078465",
1122 "node": "97054abb4ab824450e9164180baf491ae0078465",
1123 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1123 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1124 "phase": "draft",
1124 "phase": "draft",
1125 "removed": [],
1125 "removed": [],
1126 "rev": 2,
1126 "rev": 2,
1127 "tags": [],
1127 "tags": [],
1128 "user": "other@place"
1128 "user": "other@place"
1129 },
1129 },
1130 {
1130 {
1131 "added": ["b"],
1131 "added": ["b"],
1132 "bookmarks": [],
1132 "bookmarks": [],
1133 "branch": "default",
1133 "branch": "default",
1134 "date": [1100000, 0],
1134 "date": [1100000, 0],
1135 "desc": "other 1\nother 2\n\nother 3",
1135 "desc": "other 1\nother 2\n\nother 3",
1136 "extra": {"branch": "default"},
1136 "extra": {"branch": "default"},
1137 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1137 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1138 "modified": [],
1138 "modified": [],
1139 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1139 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1140 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1140 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1141 "phase": "draft",
1141 "phase": "draft",
1142 "removed": [],
1142 "removed": [],
1143 "rev": 1,
1143 "rev": 1,
1144 "tags": [],
1144 "tags": [],
1145 "user": "A. N. Other <other@place>"
1145 "user": "A. N. Other <other@place>"
1146 },
1146 },
1147 {
1147 {
1148 "added": ["a"],
1148 "added": ["a"],
1149 "bookmarks": [],
1149 "bookmarks": [],
1150 "branch": "default",
1150 "branch": "default",
1151 "date": [1000000, 0],
1151 "date": [1000000, 0],
1152 "desc": "line 1\nline 2",
1152 "desc": "line 1\nline 2",
1153 "extra": {"branch": "default"},
1153 "extra": {"branch": "default"},
1154 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1154 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1155 "modified": [],
1155 "modified": [],
1156 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1156 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1157 "parents": ["0000000000000000000000000000000000000000"],
1157 "parents": ["0000000000000000000000000000000000000000"],
1158 "phase": "draft",
1158 "phase": "draft",
1159 "removed": [],
1159 "removed": [],
1160 "rev": 0,
1160 "rev": 0,
1161 "tags": [],
1161 "tags": [],
1162 "user": "User Name <user@hostname>"
1162 "user": "User Name <user@hostname>"
1163 }
1163 }
1164 ]
1164 ]
1165
1165
1166 $ hg log -l2 -T'json(rev, parents)'
1166 $ hg log -l2 -T'json(rev, parents)'
1167 [
1167 [
1168 {"parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], "rev": 8},
1168 {"parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], "rev": 8},
1169 {"parents": ["0000000000000000000000000000000000000000"], "rev": 7}
1169 {"parents": ["0000000000000000000000000000000000000000"], "rev": 7}
1170 ]
1170 ]
1171
1171
1172 $ hg log -qr. -T'json(rev, parents)'
1172 $ hg log -qr. -T'json(rev, parents)'
1173 [
1173 [
1174 {"parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], "rev": 8}
1174 {"parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], "rev": 8}
1175 ]
1175 ]
1176
1176
1177 $ hg log -r. -T'json(diff)'
1177 $ hg log -r. -T'json(diff)'
1178 [
1178 [
1179 {"diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"}
1179 {"diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"}
1180 ]
1180 ]
1181
1181
1182 $ hg log -r. -T'json(diffstat)'
1182 $ hg log -r. -T'json(diffstat)'
1183 [
1183 [
1184 {"diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n"}
1184 {"diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n"}
1185 ]
1185 ]
1186
1186
1187 $ hg log -r. -T'json(manifest)'
1187 $ hg log -r. -T'json(manifest)'
1188 [
1188 [
1189 {"manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64"}
1189 {"manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64"}
1190 ]
1190 ]
1191
1191
1192 $ hg log -r. -T'json(extra)'
1192 $ hg log -r. -T'json(extra)'
1193 [
1193 [
1194 {"extra": {"branch": "default"}}
1194 {"extra": {"branch": "default"}}
1195 ]
1195 ]
1196
1196
1197 $ hg log -r3 -T'json(modified)'
1197 $ hg log -r3 -T'json(modified)'
1198 [
1198 [
1199 {"modified": ["c"]}
1199 {"modified": ["c"]}
1200 ]
1200 ]
1201
1201
1202 $ hg log -r. -T'json(added)'
1202 $ hg log -r. -T'json(added)'
1203 [
1203 [
1204 {"added": ["fourth", "third"]}
1204 {"added": ["fourth", "third"]}
1205 ]
1205 ]
1206
1206
1207 $ hg log -r. -T'json(removed)'
1207 $ hg log -r. -T'json(removed)'
1208 [
1208 [
1209 {"removed": ["second"]}
1209 {"removed": ["second"]}
1210 ]
1210 ]
1211
1211
1212 $ hg log -r. -T'json(files)'
1212 $ hg log -r. -T'json(files)'
1213 [
1213 [
1214 {"files": ["fourth", "second", "third"]}
1214 {"files": ["fourth", "second", "third"]}
1215 ]
1215 ]
1216
1216
1217 --copies is the exception. copies dict is built only when --copies switch
1217 --copies is the exception. copies dict is built only when --copies switch
1218 is on:
1218 is on:
1219
1219
1220 $ hg log -r'.^:' -T'json(copies)' --copies
1220 $ hg log -r'.^:' -T'json(copies)' --copies
1221 [
1221 [
1222 {"copies": {}},
1222 {"copies": {}},
1223 {"copies": {"fourth": "second"}}
1223 {"copies": {"fourth": "second"}}
1224 ]
1224 ]
1225
1225
1226 $ hg log -r. -T'json()'
1226 $ hg log -r. -T'json()'
1227 [
1227 [
1228 {}
1228 {}
1229 ]
1229 ]
1230
1230
1231 Other unsupported formatter styles:
1231 Other unsupported formatter styles:
1232
1232
1233 $ hg log -qr . -Tpickle
1233 $ hg log -qr . -Tpickle
1234 abort: "pickle" not in template map
1234 abort: "pickle" not in template map
1235 [255]
1235 [255]
1236 $ hg log -qr . -Tdebug
1236 $ hg log -qr . -Tdebug
1237 abort: "debug" not in template map
1237 abort: "debug" not in template map
1238 [255]
1238 [255]
1239
1239
1240 Unparsable function-style references:
1240 Unparsable function-style references:
1241
1241
1242 $ hg log -qr . -T'json(-)'
1242 $ hg log -qr . -T'json(-)'
1243 hg: parse error at 6: not a prefix: )
1243 hg: parse error at 6: not a prefix: )
1244 (json(-)
1244 (json(-)
1245 ^ here)
1245 ^ here)
1246 [10]
1246 [10]
1247
1247
1248 For backward compatibility, the following examples are not parsed as
1248 For backward compatibility, the following examples are not parsed as
1249 function-style references:
1249 function-style references:
1250
1250
1251 $ hg log -qr . -T'cbor(rev'
1251 $ hg log -qr . -T'cbor(rev'
1252 cbor(rev (no-eol)
1252 cbor(rev (no-eol)
1253 $ hg log -qr . -T'json (rev)'
1253 $ hg log -qr . -T'json (rev)'
1254 json (rev) (no-eol)
1254 json (rev) (no-eol)
1255 $ hg log -qr . -T'json(x="{rev}")'
1255 $ hg log -qr . -T'json(x="{rev}")'
1256 json(x="8") (no-eol)
1256 json(x="8") (no-eol)
1257
1257
1258 Error if style not readable:
1258 Error if style not readable:
1259
1259
1260 #if unix-permissions no-root
1260 #if unix-permissions no-root
1261 $ touch q
1261 $ touch q
1262 $ chmod 0 q
1262 $ chmod 0 q
1263 $ hg log --style ./q
1263 $ hg log --style ./q
1264 abort: Permission denied: './q'
1264 abort: Permission denied: './q'
1265 [255]
1265 [255]
1266 #endif
1266 #endif
1267
1267
1268 Error if no style:
1268 Error if no style:
1269
1269
1270 $ hg log --style notexist
1270 $ hg log --style notexist
1271 abort: style 'notexist' not found
1271 abort: style 'notexist' not found
1272 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1272 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1273 [255]
1273 [255]
1274
1274
1275 $ hg log -T list
1275 $ hg log -T list
1276 available styles: bisect, changelog, compact, default, phases, show, status, xml
1276 available styles: bisect, changelog, compact, default, phases, show, status, xml
1277 abort: specify a template
1277 abort: specify a template
1278 [255]
1278 [255]
1279
1279
1280 Error if style is a directory:
1280 Error if style is a directory:
1281
1281
1282 $ hg log --style somedir
1282 $ hg log --style somedir
1283 abort: Is a directory: 'somedir' (no-windows !)
1283 abort: Is a directory: 'somedir' (no-windows !)
1284 abort: somedir: Access is denied (windows !)
1284 abort: somedir: Access is denied (windows !)
1285 [255]
1285 [255]
1286
1286
1287 Error if style is a directory whose name is a built-in style:
1287 Error if style is a directory whose name is a built-in style:
1288
1288
1289 $ hg log --style coal
1289 $ hg log --style coal
1290 abort: style 'coal' not found
1290 abort: style 'coal' not found
1291 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1291 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1292 [255]
1292 [255]
1293
1293
1294 Error if style missing key:
1294 Error if style missing key:
1295
1295
1296 $ echo 'q = q' > t
1296 $ echo 'q = q' > t
1297 $ hg log --style ./t
1297 $ hg log --style ./t
1298 abort: "changeset" not in template map
1298 abort: "changeset" not in template map
1299 [255]
1299 [255]
1300
1300
1301 Error if style missing value:
1301 Error if style missing value:
1302
1302
1303 $ echo 'changeset =' > t
1303 $ echo 'changeset =' > t
1304 $ hg log --style t
1304 $ hg log --style t
1305 hg: parse error at t:1: missing value
1305 hg: parse error at t:1: missing value
1306 [10]
1306 [10]
1307
1307
1308 Error if include fails:
1308 Error if include fails:
1309
1309
1310 $ echo 'changeset = q' >> t
1310 $ echo 'changeset = q' >> t
1311 #if unix-permissions no-root
1311 #if unix-permissions no-root
1312 $ hg log --style ./t
1312 $ hg log --style ./t
1313 abort: template file ./q: Permission denied
1313 abort: template file ./q: Permission denied
1314 [255]
1314 [255]
1315 $ rm -f q
1315 $ rm -f q
1316 #endif
1316 #endif
1317
1317
1318 Include works:
1318 Include works:
1319
1319
1320 $ echo '{rev}' > q
1320 $ echo '{rev}' > q
1321 $ hg log --style ./t
1321 $ hg log --style ./t
1322 8
1322 8
1323 7
1323 7
1324 6
1324 6
1325 5
1325 5
1326 4
1326 4
1327 3
1327 3
1328 2
1328 2
1329 1
1329 1
1330 0
1330 0
1331
1331
1332 $ hg phase -r 5 --public
1332 $ hg phase -r 5 --public
1333 $ hg phase -r 7 --secret --force
1333 $ hg phase -r 7 --secret --force
1334
1334
1335 Missing non-standard names give no error (backward compatibility):
1335 Missing non-standard names give no error (backward compatibility):
1336
1336
1337 $ echo "changeset = '{c}'" > t
1337 $ echo "changeset = '{c}'" > t
1338 $ hg log --style ./t
1338 $ hg log --style ./t
1339
1339
1340 Defining non-standard name works:
1340 Defining non-standard name works:
1341
1341
1342 $ cat <<EOF > t
1342 $ cat <<EOF > t
1343 > changeset = '{c}'
1343 > changeset = '{c}'
1344 > c = q
1344 > c = q
1345 > EOF
1345 > EOF
1346 $ hg log --style ./t
1346 $ hg log --style ./t
1347 8
1347 8
1348 7
1348 7
1349 6
1349 6
1350 5
1350 5
1351 4
1351 4
1352 3
1352 3
1353 2
1353 2
1354 1
1354 1
1355 0
1355 0
1356
1356
1357 ui.style works:
1357 ui.style works:
1358
1358
1359 $ echo '[ui]' > .hg/hgrc
1359 $ echo '[ui]' > .hg/hgrc
1360 $ echo 'style = t' >> .hg/hgrc
1360 $ echo 'style = t' >> .hg/hgrc
1361 $ hg log
1361 $ hg log
1362 8
1362 8
1363 7
1363 7
1364 6
1364 6
1365 5
1365 5
1366 4
1366 4
1367 3
1367 3
1368 2
1368 2
1369 1
1369 1
1370 0
1370 0
1371
1371
1372 Issue338:
1372 Issue338:
1373
1373
1374 $ hg log --style=changelog > changelog
1374 $ hg log --style=changelog > changelog
1375
1375
1376 $ cat changelog
1376 $ cat changelog
1377 2020-01-01 test <test>
1377 2020-01-01 test <test>
1378
1378
1379 * fourth, second, third:
1379 * fourth, second, third:
1380 third
1380 third
1381 [95c24699272e] [tip]
1381 [95c24699272e] [tip]
1382
1382
1383 1970-01-12 User Name <user@hostname>
1383 1970-01-12 User Name <user@hostname>
1384
1384
1385 * second:
1385 * second:
1386 second
1386 second
1387 [29114dbae42b]
1387 [29114dbae42b]
1388
1388
1389 1970-01-18 person <person>
1389 1970-01-18 person <person>
1390
1390
1391 * merge
1391 * merge
1392 [d41e714fe50d]
1392 [d41e714fe50d]
1393
1393
1394 * d:
1394 * d:
1395 new head
1395 new head
1396 [13207e5a10d9]
1396 [13207e5a10d9]
1397
1397
1398 1970-01-17 person <person>
1398 1970-01-17 person <person>
1399
1399
1400 * new branch
1400 * new branch
1401 [bbe44766e73d] <foo>
1401 [bbe44766e73d] <foo>
1402
1402
1403 1970-01-16 person <person>
1403 1970-01-16 person <person>
1404
1404
1405 * c:
1405 * c:
1406 no user, no domain
1406 no user, no domain
1407 [10e46f2dcbf4]
1407 [10e46f2dcbf4]
1408
1408
1409 1970-01-14 other <other@place>
1409 1970-01-14 other <other@place>
1410
1410
1411 * c:
1411 * c:
1412 no person
1412 no person
1413 [97054abb4ab8]
1413 [97054abb4ab8]
1414
1414
1415 1970-01-13 A. N. Other <other@place>
1415 1970-01-13 A. N. Other <other@place>
1416
1416
1417 * b:
1417 * b:
1418 other 1 other 2
1418 other 1 other 2
1419
1419
1420 other 3
1420 other 3
1421 [b608e9d1a3f0]
1421 [b608e9d1a3f0]
1422
1422
1423 1970-01-12 User Name <user@hostname>
1423 1970-01-12 User Name <user@hostname>
1424
1424
1425 * a:
1425 * a:
1426 line 1 line 2
1426 line 1 line 2
1427 [1e4e1b8f71e0]
1427 [1e4e1b8f71e0]
1428
1428
1429
1429
1430 Issue2130: xml output for 'hg heads' is malformed
1430 Issue2130: xml output for 'hg heads' is malformed
1431
1431
1432 $ hg heads --style changelog
1432 $ hg heads --style changelog
1433 2020-01-01 test <test>
1433 2020-01-01 test <test>
1434
1434
1435 * fourth, second, third:
1435 * fourth, second, third:
1436 third
1436 third
1437 [95c24699272e] [tip]
1437 [95c24699272e] [tip]
1438
1438
1439 1970-01-18 person <person>
1439 1970-01-18 person <person>
1440
1440
1441 * merge
1441 * merge
1442 [d41e714fe50d]
1442 [d41e714fe50d]
1443
1443
1444 1970-01-17 person <person>
1444 1970-01-17 person <person>
1445
1445
1446 * new branch
1446 * new branch
1447 [bbe44766e73d] <foo>
1447 [bbe44766e73d] <foo>
1448
1448
1449
1449
1450 Add a dummy commit to make up for the instability of the above:
1450 Add a dummy commit to make up for the instability of the above:
1451
1451
1452 $ echo a > a
1452 $ echo a > a
1453 $ hg add a
1453 $ hg add a
1454 $ hg ci -m future
1454 $ hg ci -m future
1455
1455
1456 Add a commit that does all possible modifications at once
1456 Add a commit that does all possible modifications at once
1457
1457
1458 $ echo modify >> third
1458 $ echo modify >> third
1459 $ touch b
1459 $ touch b
1460 $ hg add b
1460 $ hg add b
1461 $ hg mv fourth fifth
1461 $ hg mv fourth fifth
1462 $ hg rm a
1462 $ hg rm a
1463 $ hg ci -m "Modify, add, remove, rename"
1463 $ hg ci -m "Modify, add, remove, rename"
1464
1464
1465 Check the status template
1465 Check the status template
1466
1466
1467 $ cat <<EOF >> $HGRCPATH
1467 $ cat <<EOF >> $HGRCPATH
1468 > [extensions]
1468 > [extensions]
1469 > color=
1469 > color=
1470 > EOF
1470 > EOF
1471
1471
1472 $ hg log -T status -r 10
1472 $ hg log -T status -r 10
1473 changeset: 10:0f9759ec227a
1473 changeset: 10:0f9759ec227a
1474 tag: tip
1474 tag: tip
1475 user: test
1475 user: test
1476 date: Thu Jan 01 00:00:00 1970 +0000
1476 date: Thu Jan 01 00:00:00 1970 +0000
1477 summary: Modify, add, remove, rename
1477 summary: Modify, add, remove, rename
1478 files:
1478 files:
1479 M third
1479 M third
1480 A b
1480 A b
1481 A fifth
1481 A fifth
1482 R a
1482 R a
1483 R fourth
1483 R fourth
1484
1484
1485 $ hg log -T status -C -r 10
1485 $ hg log -T status -C -r 10
1486 changeset: 10:0f9759ec227a
1486 changeset: 10:0f9759ec227a
1487 tag: tip
1487 tag: tip
1488 user: test
1488 user: test
1489 date: Thu Jan 01 00:00:00 1970 +0000
1489 date: Thu Jan 01 00:00:00 1970 +0000
1490 summary: Modify, add, remove, rename
1490 summary: Modify, add, remove, rename
1491 files:
1491 files:
1492 M third
1492 M third
1493 A b
1493 A b
1494 A fifth
1494 A fifth
1495 fourth
1495 fourth
1496 R a
1496 R a
1497 R fourth
1497 R fourth
1498
1498
1499 $ hg log -T status -C -r 10 -v
1499 $ hg log -T status -C -r 10 -v
1500 changeset: 10:0f9759ec227a
1500 changeset: 10:0f9759ec227a
1501 tag: tip
1501 tag: tip
1502 user: test
1502 user: test
1503 date: Thu Jan 01 00:00:00 1970 +0000
1503 date: Thu Jan 01 00:00:00 1970 +0000
1504 description:
1504 description:
1505 Modify, add, remove, rename
1505 Modify, add, remove, rename
1506
1506
1507 files:
1507 files:
1508 M third
1508 M third
1509 A b
1509 A b
1510 A fifth
1510 A fifth
1511 fourth
1511 fourth
1512 R a
1512 R a
1513 R fourth
1513 R fourth
1514
1514
1515 $ hg log -T status -C -r 10 --debug
1515 $ hg log -T status -C -r 10 --debug
1516 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
1516 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
1517 tag: tip
1517 tag: tip
1518 phase: secret
1518 phase: secret
1519 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
1519 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
1520 parent: -1:0000000000000000000000000000000000000000
1520 parent: -1:0000000000000000000000000000000000000000
1521 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
1521 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
1522 user: test
1522 user: test
1523 date: Thu Jan 01 00:00:00 1970 +0000
1523 date: Thu Jan 01 00:00:00 1970 +0000
1524 extra: branch=default
1524 extra: branch=default
1525 description:
1525 description:
1526 Modify, add, remove, rename
1526 Modify, add, remove, rename
1527
1527
1528 files:
1528 files:
1529 M third
1529 M third
1530 A b
1530 A b
1531 A fifth
1531 A fifth
1532 fourth
1532 fourth
1533 R a
1533 R a
1534 R fourth
1534 R fourth
1535
1535
1536 $ hg log -T status -C -r 10 --quiet
1536 $ hg log -T status -C -r 10 --quiet
1537 10:0f9759ec227a
1537 10:0f9759ec227a
1538 $ hg --color=debug log -T status -r 10
1538 $ hg --color=debug log -T status -r 10
1539 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1539 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1540 [log.tag|tag: tip]
1540 [log.tag|tag: tip]
1541 [log.user|user: test]
1541 [log.user|user: test]
1542 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1542 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1543 [log.summary|summary: Modify, add, remove, rename]
1543 [log.summary|summary: Modify, add, remove, rename]
1544 [ui.note log.files|files:]
1544 [ui.note log.files|files:]
1545 [status.modified|M third]
1545 [status.modified|M third]
1546 [status.added|A b]
1546 [status.added|A b]
1547 [status.added|A fifth]
1547 [status.added|A fifth]
1548 [status.removed|R a]
1548 [status.removed|R a]
1549 [status.removed|R fourth]
1549 [status.removed|R fourth]
1550
1550
1551 $ hg --color=debug log -T status -C -r 10
1551 $ hg --color=debug log -T status -C -r 10
1552 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1552 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1553 [log.tag|tag: tip]
1553 [log.tag|tag: tip]
1554 [log.user|user: test]
1554 [log.user|user: test]
1555 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1555 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1556 [log.summary|summary: Modify, add, remove, rename]
1556 [log.summary|summary: Modify, add, remove, rename]
1557 [ui.note log.files|files:]
1557 [ui.note log.files|files:]
1558 [status.modified|M third]
1558 [status.modified|M third]
1559 [status.added|A b]
1559 [status.added|A b]
1560 [status.added|A fifth]
1560 [status.added|A fifth]
1561 [status.copied| fourth]
1561 [status.copied| fourth]
1562 [status.removed|R a]
1562 [status.removed|R a]
1563 [status.removed|R fourth]
1563 [status.removed|R fourth]
1564
1564
1565 $ hg --color=debug log -T status -C -r 10 -v
1565 $ hg --color=debug log -T status -C -r 10 -v
1566 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1566 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
1567 [log.tag|tag: tip]
1567 [log.tag|tag: tip]
1568 [log.user|user: test]
1568 [log.user|user: test]
1569 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1569 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1570 [ui.note log.description|description:]
1570 [ui.note log.description|description:]
1571 [ui.note log.description|Modify, add, remove, rename]
1571 [ui.note log.description|Modify, add, remove, rename]
1572
1572
1573 [ui.note log.files|files:]
1573 [ui.note log.files|files:]
1574 [status.modified|M third]
1574 [status.modified|M third]
1575 [status.added|A b]
1575 [status.added|A b]
1576 [status.added|A fifth]
1576 [status.added|A fifth]
1577 [status.copied| fourth]
1577 [status.copied| fourth]
1578 [status.removed|R a]
1578 [status.removed|R a]
1579 [status.removed|R fourth]
1579 [status.removed|R fourth]
1580
1580
1581 $ hg --color=debug log -T status -C -r 10 --debug
1581 $ hg --color=debug log -T status -C -r 10 --debug
1582 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
1582 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
1583 [log.tag|tag: tip]
1583 [log.tag|tag: tip]
1584 [log.phase|phase: secret]
1584 [log.phase|phase: secret]
1585 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
1585 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
1586 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1586 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1587 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
1587 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
1588 [log.user|user: test]
1588 [log.user|user: test]
1589 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1589 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
1590 [ui.debug log.extra|extra: branch=default]
1590 [ui.debug log.extra|extra: branch=default]
1591 [ui.note log.description|description:]
1591 [ui.note log.description|description:]
1592 [ui.note log.description|Modify, add, remove, rename]
1592 [ui.note log.description|Modify, add, remove, rename]
1593
1593
1594 [ui.note log.files|files:]
1594 [ui.note log.files|files:]
1595 [status.modified|M third]
1595 [status.modified|M third]
1596 [status.added|A b]
1596 [status.added|A b]
1597 [status.added|A fifth]
1597 [status.added|A fifth]
1598 [status.copied| fourth]
1598 [status.copied| fourth]
1599 [status.removed|R a]
1599 [status.removed|R a]
1600 [status.removed|R fourth]
1600 [status.removed|R fourth]
1601
1601
1602 $ hg --color=debug log -T status -C -r 10 --quiet
1602 $ hg --color=debug log -T status -C -r 10 --quiet
1603 [log.node|10:0f9759ec227a]
1603 [log.node|10:0f9759ec227a]
1604
1604
1605 Check the bisect template
1605 Check the bisect template
1606
1606
1607 $ hg bisect -g 1
1607 $ hg bisect -g 1
1608 $ hg bisect -b 3 --noupdate
1608 $ hg bisect -b 3 --noupdate
1609 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
1609 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
1610 $ hg log -T bisect -r 0:4
1610 $ hg log -T bisect -r 0:4
1611 changeset: 0:1e4e1b8f71e0
1611 changeset: 0:1e4e1b8f71e0
1612 bisect: good (implicit)
1612 bisect: good (implicit)
1613 user: User Name <user@hostname>
1613 user: User Name <user@hostname>
1614 date: Mon Jan 12 13:46:40 1970 +0000
1614 date: Mon Jan 12 13:46:40 1970 +0000
1615 summary: line 1
1615 summary: line 1
1616
1616
1617 changeset: 1:b608e9d1a3f0
1617 changeset: 1:b608e9d1a3f0
1618 bisect: good
1618 bisect: good
1619 user: A. N. Other <other@place>
1619 user: A. N. Other <other@place>
1620 date: Tue Jan 13 17:33:20 1970 +0000
1620 date: Tue Jan 13 17:33:20 1970 +0000
1621 summary: other 1
1621 summary: other 1
1622
1622
1623 changeset: 2:97054abb4ab8
1623 changeset: 2:97054abb4ab8
1624 bisect: untested
1624 bisect: untested
1625 user: other@place
1625 user: other@place
1626 date: Wed Jan 14 21:20:00 1970 +0000
1626 date: Wed Jan 14 21:20:00 1970 +0000
1627 summary: no person
1627 summary: no person
1628
1628
1629 changeset: 3:10e46f2dcbf4
1629 changeset: 3:10e46f2dcbf4
1630 bisect: bad
1630 bisect: bad
1631 user: person
1631 user: person
1632 date: Fri Jan 16 01:06:40 1970 +0000
1632 date: Fri Jan 16 01:06:40 1970 +0000
1633 summary: no user, no domain
1633 summary: no user, no domain
1634
1634
1635 changeset: 4:bbe44766e73d
1635 changeset: 4:bbe44766e73d
1636 bisect: bad (implicit)
1636 bisect: bad (implicit)
1637 branch: foo
1637 branch: foo
1638 user: person
1638 user: person
1639 date: Sat Jan 17 04:53:20 1970 +0000
1639 date: Sat Jan 17 04:53:20 1970 +0000
1640 summary: new branch
1640 summary: new branch
1641
1641
1642 $ hg log --debug -T bisect -r 0:4
1642 $ hg log --debug -T bisect -r 0:4
1643 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1643 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1644 bisect: good (implicit)
1644 bisect: good (implicit)
1645 phase: public
1645 phase: public
1646 parent: -1:0000000000000000000000000000000000000000
1646 parent: -1:0000000000000000000000000000000000000000
1647 parent: -1:0000000000000000000000000000000000000000
1647 parent: -1:0000000000000000000000000000000000000000
1648 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1648 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1649 user: User Name <user@hostname>
1649 user: User Name <user@hostname>
1650 date: Mon Jan 12 13:46:40 1970 +0000
1650 date: Mon Jan 12 13:46:40 1970 +0000
1651 files+: a
1651 files+: a
1652 extra: branch=default
1652 extra: branch=default
1653 description:
1653 description:
1654 line 1
1654 line 1
1655 line 2
1655 line 2
1656
1656
1657
1657
1658 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1658 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1659 bisect: good
1659 bisect: good
1660 phase: public
1660 phase: public
1661 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1661 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
1662 parent: -1:0000000000000000000000000000000000000000
1662 parent: -1:0000000000000000000000000000000000000000
1663 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1663 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1664 user: A. N. Other <other@place>
1664 user: A. N. Other <other@place>
1665 date: Tue Jan 13 17:33:20 1970 +0000
1665 date: Tue Jan 13 17:33:20 1970 +0000
1666 files+: b
1666 files+: b
1667 extra: branch=default
1667 extra: branch=default
1668 description:
1668 description:
1669 other 1
1669 other 1
1670 other 2
1670 other 2
1671
1671
1672 other 3
1672 other 3
1673
1673
1674
1674
1675 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
1675 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
1676 bisect: untested
1676 bisect: untested
1677 phase: public
1677 phase: public
1678 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1678 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1679 parent: -1:0000000000000000000000000000000000000000
1679 parent: -1:0000000000000000000000000000000000000000
1680 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1680 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1681 user: other@place
1681 user: other@place
1682 date: Wed Jan 14 21:20:00 1970 +0000
1682 date: Wed Jan 14 21:20:00 1970 +0000
1683 files+: c
1683 files+: c
1684 extra: branch=default
1684 extra: branch=default
1685 description:
1685 description:
1686 no person
1686 no person
1687
1687
1688
1688
1689 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1689 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1690 bisect: bad
1690 bisect: bad
1691 phase: public
1691 phase: public
1692 parent: 2:97054abb4ab824450e9164180baf491ae0078465
1692 parent: 2:97054abb4ab824450e9164180baf491ae0078465
1693 parent: -1:0000000000000000000000000000000000000000
1693 parent: -1:0000000000000000000000000000000000000000
1694 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1694 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1695 user: person
1695 user: person
1696 date: Fri Jan 16 01:06:40 1970 +0000
1696 date: Fri Jan 16 01:06:40 1970 +0000
1697 files: c
1697 files: c
1698 extra: branch=default
1698 extra: branch=default
1699 description:
1699 description:
1700 no user, no domain
1700 no user, no domain
1701
1701
1702
1702
1703 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1703 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1704 bisect: bad (implicit)
1704 bisect: bad (implicit)
1705 branch: foo
1705 branch: foo
1706 phase: draft
1706 phase: draft
1707 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1707 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
1708 parent: -1:0000000000000000000000000000000000000000
1708 parent: -1:0000000000000000000000000000000000000000
1709 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1709 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1710 user: person
1710 user: person
1711 date: Sat Jan 17 04:53:20 1970 +0000
1711 date: Sat Jan 17 04:53:20 1970 +0000
1712 extra: branch=foo
1712 extra: branch=foo
1713 description:
1713 description:
1714 new branch
1714 new branch
1715
1715
1716
1716
1717 $ hg log -v -T bisect -r 0:4
1717 $ hg log -v -T bisect -r 0:4
1718 changeset: 0:1e4e1b8f71e0
1718 changeset: 0:1e4e1b8f71e0
1719 bisect: good (implicit)
1719 bisect: good (implicit)
1720 user: User Name <user@hostname>
1720 user: User Name <user@hostname>
1721 date: Mon Jan 12 13:46:40 1970 +0000
1721 date: Mon Jan 12 13:46:40 1970 +0000
1722 files: a
1722 files: a
1723 description:
1723 description:
1724 line 1
1724 line 1
1725 line 2
1725 line 2
1726
1726
1727
1727
1728 changeset: 1:b608e9d1a3f0
1728 changeset: 1:b608e9d1a3f0
1729 bisect: good
1729 bisect: good
1730 user: A. N. Other <other@place>
1730 user: A. N. Other <other@place>
1731 date: Tue Jan 13 17:33:20 1970 +0000
1731 date: Tue Jan 13 17:33:20 1970 +0000
1732 files: b
1732 files: b
1733 description:
1733 description:
1734 other 1
1734 other 1
1735 other 2
1735 other 2
1736
1736
1737 other 3
1737 other 3
1738
1738
1739
1739
1740 changeset: 2:97054abb4ab8
1740 changeset: 2:97054abb4ab8
1741 bisect: untested
1741 bisect: untested
1742 user: other@place
1742 user: other@place
1743 date: Wed Jan 14 21:20:00 1970 +0000
1743 date: Wed Jan 14 21:20:00 1970 +0000
1744 files: c
1744 files: c
1745 description:
1745 description:
1746 no person
1746 no person
1747
1747
1748
1748
1749 changeset: 3:10e46f2dcbf4
1749 changeset: 3:10e46f2dcbf4
1750 bisect: bad
1750 bisect: bad
1751 user: person
1751 user: person
1752 date: Fri Jan 16 01:06:40 1970 +0000
1752 date: Fri Jan 16 01:06:40 1970 +0000
1753 files: c
1753 files: c
1754 description:
1754 description:
1755 no user, no domain
1755 no user, no domain
1756
1756
1757
1757
1758 changeset: 4:bbe44766e73d
1758 changeset: 4:bbe44766e73d
1759 bisect: bad (implicit)
1759 bisect: bad (implicit)
1760 branch: foo
1760 branch: foo
1761 user: person
1761 user: person
1762 date: Sat Jan 17 04:53:20 1970 +0000
1762 date: Sat Jan 17 04:53:20 1970 +0000
1763 description:
1763 description:
1764 new branch
1764 new branch
1765
1765
1766
1766
1767 $ hg --color=debug log -T bisect -r 0:4
1767 $ hg --color=debug log -T bisect -r 0:4
1768 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1768 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1769 [log.bisect bisect.good|bisect: good (implicit)]
1769 [log.bisect bisect.good|bisect: good (implicit)]
1770 [log.user|user: User Name <user@hostname>]
1770 [log.user|user: User Name <user@hostname>]
1771 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1771 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1772 [log.summary|summary: line 1]
1772 [log.summary|summary: line 1]
1773
1773
1774 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1774 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1775 [log.bisect bisect.good|bisect: good]
1775 [log.bisect bisect.good|bisect: good]
1776 [log.user|user: A. N. Other <other@place>]
1776 [log.user|user: A. N. Other <other@place>]
1777 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1777 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1778 [log.summary|summary: other 1]
1778 [log.summary|summary: other 1]
1779
1779
1780 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1780 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1781 [log.bisect bisect.untested|bisect: untested]
1781 [log.bisect bisect.untested|bisect: untested]
1782 [log.user|user: other@place]
1782 [log.user|user: other@place]
1783 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1783 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1784 [log.summary|summary: no person]
1784 [log.summary|summary: no person]
1785
1785
1786 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1786 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1787 [log.bisect bisect.bad|bisect: bad]
1787 [log.bisect bisect.bad|bisect: bad]
1788 [log.user|user: person]
1788 [log.user|user: person]
1789 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1789 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1790 [log.summary|summary: no user, no domain]
1790 [log.summary|summary: no user, no domain]
1791
1791
1792 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1792 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1793 [log.bisect bisect.bad|bisect: bad (implicit)]
1793 [log.bisect bisect.bad|bisect: bad (implicit)]
1794 [log.branch|branch: foo]
1794 [log.branch|branch: foo]
1795 [log.user|user: person]
1795 [log.user|user: person]
1796 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1796 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1797 [log.summary|summary: new branch]
1797 [log.summary|summary: new branch]
1798
1798
1799 $ hg --color=debug log --debug -T bisect -r 0:4
1799 $ hg --color=debug log --debug -T bisect -r 0:4
1800 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1800 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1801 [log.bisect bisect.good|bisect: good (implicit)]
1801 [log.bisect bisect.good|bisect: good (implicit)]
1802 [log.phase|phase: public]
1802 [log.phase|phase: public]
1803 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1803 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1804 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1804 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1805 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
1805 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
1806 [log.user|user: User Name <user@hostname>]
1806 [log.user|user: User Name <user@hostname>]
1807 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1807 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1808 [ui.debug log.files|files+: a]
1808 [ui.debug log.files|files+: a]
1809 [ui.debug log.extra|extra: branch=default]
1809 [ui.debug log.extra|extra: branch=default]
1810 [ui.note log.description|description:]
1810 [ui.note log.description|description:]
1811 [ui.note log.description|line 1
1811 [ui.note log.description|line 1
1812 line 2]
1812 line 2]
1813
1813
1814
1814
1815 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1815 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1816 [log.bisect bisect.good|bisect: good]
1816 [log.bisect bisect.good|bisect: good]
1817 [log.phase|phase: public]
1817 [log.phase|phase: public]
1818 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1818 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
1819 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1819 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1820 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
1820 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
1821 [log.user|user: A. N. Other <other@place>]
1821 [log.user|user: A. N. Other <other@place>]
1822 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1822 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1823 [ui.debug log.files|files+: b]
1823 [ui.debug log.files|files+: b]
1824 [ui.debug log.extra|extra: branch=default]
1824 [ui.debug log.extra|extra: branch=default]
1825 [ui.note log.description|description:]
1825 [ui.note log.description|description:]
1826 [ui.note log.description|other 1
1826 [ui.note log.description|other 1
1827 other 2
1827 other 2
1828
1828
1829 other 3]
1829 other 3]
1830
1830
1831
1831
1832 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
1832 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
1833 [log.bisect bisect.untested|bisect: untested]
1833 [log.bisect bisect.untested|bisect: untested]
1834 [log.phase|phase: public]
1834 [log.phase|phase: public]
1835 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1835 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
1836 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1836 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1837 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
1837 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
1838 [log.user|user: other@place]
1838 [log.user|user: other@place]
1839 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1839 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1840 [ui.debug log.files|files+: c]
1840 [ui.debug log.files|files+: c]
1841 [ui.debug log.extra|extra: branch=default]
1841 [ui.debug log.extra|extra: branch=default]
1842 [ui.note log.description|description:]
1842 [ui.note log.description|description:]
1843 [ui.note log.description|no person]
1843 [ui.note log.description|no person]
1844
1844
1845
1845
1846 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1846 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1847 [log.bisect bisect.bad|bisect: bad]
1847 [log.bisect bisect.bad|bisect: bad]
1848 [log.phase|phase: public]
1848 [log.phase|phase: public]
1849 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
1849 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
1850 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1850 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1851 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1851 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1852 [log.user|user: person]
1852 [log.user|user: person]
1853 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1853 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1854 [ui.debug log.files|files: c]
1854 [ui.debug log.files|files: c]
1855 [ui.debug log.extra|extra: branch=default]
1855 [ui.debug log.extra|extra: branch=default]
1856 [ui.note log.description|description:]
1856 [ui.note log.description|description:]
1857 [ui.note log.description|no user, no domain]
1857 [ui.note log.description|no user, no domain]
1858
1858
1859
1859
1860 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
1860 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
1861 [log.bisect bisect.bad|bisect: bad (implicit)]
1861 [log.bisect bisect.bad|bisect: bad (implicit)]
1862 [log.branch|branch: foo]
1862 [log.branch|branch: foo]
1863 [log.phase|phase: draft]
1863 [log.phase|phase: draft]
1864 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1864 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
1865 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1865 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
1866 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1866 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
1867 [log.user|user: person]
1867 [log.user|user: person]
1868 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1868 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1869 [ui.debug log.extra|extra: branch=foo]
1869 [ui.debug log.extra|extra: branch=foo]
1870 [ui.note log.description|description:]
1870 [ui.note log.description|description:]
1871 [ui.note log.description|new branch]
1871 [ui.note log.description|new branch]
1872
1872
1873
1873
1874 $ hg --color=debug log -v -T bisect -r 0:4
1874 $ hg --color=debug log -v -T bisect -r 0:4
1875 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1875 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
1876 [log.bisect bisect.good|bisect: good (implicit)]
1876 [log.bisect bisect.good|bisect: good (implicit)]
1877 [log.user|user: User Name <user@hostname>]
1877 [log.user|user: User Name <user@hostname>]
1878 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1878 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
1879 [ui.note log.files|files: a]
1879 [ui.note log.files|files: a]
1880 [ui.note log.description|description:]
1880 [ui.note log.description|description:]
1881 [ui.note log.description|line 1
1881 [ui.note log.description|line 1
1882 line 2]
1882 line 2]
1883
1883
1884
1884
1885 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1885 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
1886 [log.bisect bisect.good|bisect: good]
1886 [log.bisect bisect.good|bisect: good]
1887 [log.user|user: A. N. Other <other@place>]
1887 [log.user|user: A. N. Other <other@place>]
1888 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1888 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
1889 [ui.note log.files|files: b]
1889 [ui.note log.files|files: b]
1890 [ui.note log.description|description:]
1890 [ui.note log.description|description:]
1891 [ui.note log.description|other 1
1891 [ui.note log.description|other 1
1892 other 2
1892 other 2
1893
1893
1894 other 3]
1894 other 3]
1895
1895
1896
1896
1897 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1897 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
1898 [log.bisect bisect.untested|bisect: untested]
1898 [log.bisect bisect.untested|bisect: untested]
1899 [log.user|user: other@place]
1899 [log.user|user: other@place]
1900 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1900 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
1901 [ui.note log.files|files: c]
1901 [ui.note log.files|files: c]
1902 [ui.note log.description|description:]
1902 [ui.note log.description|description:]
1903 [ui.note log.description|no person]
1903 [ui.note log.description|no person]
1904
1904
1905
1905
1906 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1906 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
1907 [log.bisect bisect.bad|bisect: bad]
1907 [log.bisect bisect.bad|bisect: bad]
1908 [log.user|user: person]
1908 [log.user|user: person]
1909 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1909 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
1910 [ui.note log.files|files: c]
1910 [ui.note log.files|files: c]
1911 [ui.note log.description|description:]
1911 [ui.note log.description|description:]
1912 [ui.note log.description|no user, no domain]
1912 [ui.note log.description|no user, no domain]
1913
1913
1914
1914
1915 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1915 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
1916 [log.bisect bisect.bad|bisect: bad (implicit)]
1916 [log.bisect bisect.bad|bisect: bad (implicit)]
1917 [log.branch|branch: foo]
1917 [log.branch|branch: foo]
1918 [log.user|user: person]
1918 [log.user|user: person]
1919 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1919 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
1920 [ui.note log.description|description:]
1920 [ui.note log.description|description:]
1921 [ui.note log.description|new branch]
1921 [ui.note log.description|new branch]
1922
1922
1923
1923
1924 $ hg bisect --reset
1924 $ hg bisect --reset
1925
1925
1926 $ cd ..
1926 $ cd ..
1927
1927
1928 Set up latesttag repository:
1928 Set up latesttag repository:
1929
1929
1930 $ hg init latesttag
1930 $ hg init latesttag
1931 $ cd latesttag
1931 $ cd latesttag
1932
1932
1933 $ echo a > file
1933 $ echo a > file
1934 $ hg ci -Am a -d '0 0'
1934 $ hg ci -Am a -d '0 0'
1935 adding file
1935 adding file
1936
1936
1937 $ echo b >> file
1937 $ echo b >> file
1938 $ hg ci -m b -d '1 0'
1938 $ hg ci -m b -d '1 0'
1939
1939
1940 $ echo c >> head1
1940 $ echo c >> head1
1941 $ hg ci -Am h1c -d '2 0'
1941 $ hg ci -Am h1c -d '2 0'
1942 adding head1
1942 adding head1
1943
1943
1944 $ hg update -q 1
1944 $ hg update -q 1
1945 $ echo d >> head2
1945 $ echo d >> head2
1946 $ hg ci -Am h2d -d '3 0'
1946 $ hg ci -Am h2d -d '3 0'
1947 adding head2
1947 adding head2
1948 created new head
1948 created new head
1949
1949
1950 $ echo e >> head2
1950 $ echo e >> head2
1951 $ hg ci -m h2e -d '4 0'
1951 $ hg ci -m h2e -d '4 0'
1952
1952
1953 $ hg merge -q
1953 $ hg merge -q
1954 $ hg ci -m merge -d '5 -3600'
1954 $ hg ci -m merge -d '5 -3600'
1955
1955
1956 $ hg tag -r 1 -m t1 -d '6 0' t1
1956 $ hg tag -r 1 -m t1 -d '6 0' t1
1957 $ hg tag -r 2 -m t2 -d '7 0' t2
1957 $ hg tag -r 2 -m t2 -d '7 0' t2
1958 $ hg tag -r 3 -m t3 -d '8 0' t3
1958 $ hg tag -r 3 -m t3 -d '8 0' t3
1959 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
1959 $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
1960 $ hg tag -r 5 -m t5 -d '9 0' t5
1960 $ hg tag -r 5 -m t5 -d '9 0' t5
1961 $ hg tag -r 3 -m at3 -d '10 0' at3
1961 $ hg tag -r 3 -m at3 -d '10 0' at3
1962
1962
1963 $ cd ..
1963 $ cd ..
1964
1964
1965 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1965 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1966 if it is a relative path
1966 if it is a relative path
1967
1967
1968 $ mkdir -p home/styles
1968 $ mkdir -p home/styles
1969
1969
1970 $ cat > home/styles/teststyle <<EOF
1970 $ cat > home/styles/teststyle <<EOF
1971 > changeset = 'test {rev}:{node|short}\n'
1971 > changeset = 'test {rev}:{node|short}\n'
1972 > EOF
1972 > EOF
1973
1973
1974 $ HOME=`pwd`/home; export HOME
1974 $ HOME=`pwd`/home; export HOME
1975 $ USERPROFILE=`pwd`/home; export USERPROFILE
1975
1976
1976 $ cat > latesttag/.hg/hgrc <<EOF
1977 $ cat > latesttag/.hg/hgrc <<EOF
1977 > [ui]
1978 > [ui]
1978 > style = ~/styles/teststyle
1979 > style = ~/styles/teststyle
1979 > EOF
1980 > EOF
1980
1981
1981 $ hg -R latesttag tip
1982 $ hg -R latesttag tip
1982 test 11:97e5943b523a
1983 test 11:97e5943b523a
1983
1984
1984 Test recursive showlist template (issue1989):
1985 Test recursive showlist template (issue1989):
1985
1986
1986 $ cat > style1989 <<EOF
1987 $ cat > style1989 <<EOF
1987 > changeset = '{file_mods}{manifest}{extras}'
1988 > changeset = '{file_mods}{manifest}{extras}'
1988 > file_mod = 'M|{author|person}\n'
1989 > file_mod = 'M|{author|person}\n'
1989 > manifest = '{rev},{author}\n'
1990 > manifest = '{rev},{author}\n'
1990 > extra = '{key}: {author}\n'
1991 > extra = '{key}: {author}\n'
1991 > EOF
1992 > EOF
1992
1993
1993 $ hg -R latesttag log -r tip --style=style1989
1994 $ hg -R latesttag log -r tip --style=style1989
1994 M|test
1995 M|test
1995 11,
1996 11,
1996 branch: test
1997 branch: test
General Comments 0
You need to be logged in to leave comments. Login now