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