##// END OF EJS Templates
run-tests: fix whitelist/blacklist with directories on Windows...
Matt Harbison -
r47984:dff19fe2 stable
parent child Browse files
Show More
@@ -1,3915 +1,3916 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import contextlib
50 import contextlib
51 import difflib
51 import difflib
52 import distutils.version as version
52 import distutils.version as version
53 import errno
53 import errno
54 import json
54 import json
55 import multiprocessing
55 import multiprocessing
56 import os
56 import os
57 import platform
57 import platform
58 import random
58 import random
59 import re
59 import re
60 import shutil
60 import shutil
61 import signal
61 import signal
62 import socket
62 import socket
63 import subprocess
63 import subprocess
64 import sys
64 import sys
65 import sysconfig
65 import sysconfig
66 import tempfile
66 import tempfile
67 import threading
67 import threading
68 import time
68 import time
69 import unittest
69 import unittest
70 import uuid
70 import uuid
71 import xml.dom.minidom as minidom
71 import xml.dom.minidom as minidom
72
72
73 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 PYTHON3:
265 elif PYTHON3:
266 # TODO: make a proper exception handler after dropping py2. This
266 # TODO: make a proper exception handler after dropping py2. This
267 # works because socket.error is an alias for OSError on py3,
267 # works because socket.error is an alias for OSError on py3,
268 # which is also the baseclass of PermissionError.
268 # which is also the baseclass of PermissionError.
269 if isinstance(exc, PermissionError):
269 if isinstance(exc, PermissionError):
270 return False
270 return False
271 if exc.errno not in (
271 if exc.errno not in (
272 errno.EADDRINUSE,
272 errno.EADDRINUSE,
273 errno.EADDRNOTAVAIL,
273 errno.EADDRNOTAVAIL,
274 errno.EPROTONOSUPPORT,
274 errno.EPROTONOSUPPORT,
275 ):
275 ):
276 raise
276 raise
277 return False
277 return False
278
278
279
279
280 closefds = os.name == 'posix'
280 closefds = os.name == 'posix'
281
281
282
282
283 def Popen4(cmd, wd, timeout, env=None):
283 def Popen4(cmd, wd, timeout, env=None):
284 processlock.acquire()
284 processlock.acquire()
285 p = subprocess.Popen(
285 p = subprocess.Popen(
286 _bytes2sys(cmd),
286 _bytes2sys(cmd),
287 shell=True,
287 shell=True,
288 bufsize=-1,
288 bufsize=-1,
289 cwd=_bytes2sys(wd),
289 cwd=_bytes2sys(wd),
290 env=env,
290 env=env,
291 close_fds=closefds,
291 close_fds=closefds,
292 stdin=subprocess.PIPE,
292 stdin=subprocess.PIPE,
293 stdout=subprocess.PIPE,
293 stdout=subprocess.PIPE,
294 stderr=subprocess.STDOUT,
294 stderr=subprocess.STDOUT,
295 )
295 )
296 processlock.release()
296 processlock.release()
297
297
298 p.fromchild = p.stdout
298 p.fromchild = p.stdout
299 p.tochild = p.stdin
299 p.tochild = p.stdin
300 p.childerr = p.stderr
300 p.childerr = p.stderr
301
301
302 p.timeout = False
302 p.timeout = False
303 if timeout:
303 if timeout:
304
304
305 def t():
305 def t():
306 start = time.time()
306 start = time.time()
307 while time.time() - start < timeout and p.returncode is None:
307 while time.time() - start < timeout and p.returncode is None:
308 time.sleep(0.1)
308 time.sleep(0.1)
309 p.timeout = True
309 p.timeout = True
310 vlog('# Timout reached for process %d' % p.pid)
310 vlog('# Timout reached for process %d' % p.pid)
311 if p.returncode is None:
311 if p.returncode is None:
312 terminate(p)
312 terminate(p)
313
313
314 threading.Thread(target=t).start()
314 threading.Thread(target=t).start()
315
315
316 return p
316 return p
317
317
318
318
319 if sys.executable:
319 if sys.executable:
320 sysexecutable = sys.executable
320 sysexecutable = sys.executable
321 elif os.environ.get('PYTHONEXECUTABLE'):
321 elif os.environ.get('PYTHONEXECUTABLE'):
322 sysexecutable = os.environ['PYTHONEXECUTABLE']
322 sysexecutable = os.environ['PYTHONEXECUTABLE']
323 elif os.environ.get('PYTHON'):
323 elif os.environ.get('PYTHON'):
324 sysexecutable = os.environ['PYTHON']
324 sysexecutable = os.environ['PYTHON']
325 else:
325 else:
326 raise AssertionError('Could not find Python interpreter')
326 raise AssertionError('Could not find Python interpreter')
327
327
328 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
328 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
329 IMPL_PATH = b'PYTHONPATH'
329 IMPL_PATH = b'PYTHONPATH'
330 if 'java' in sys.platform:
330 if 'java' in sys.platform:
331 IMPL_PATH = b'JYTHONPATH'
331 IMPL_PATH = b'JYTHONPATH'
332
332
333 default_defaults = {
333 default_defaults = {
334 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
334 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
335 'timeout': ('HGTEST_TIMEOUT', 360),
335 'timeout': ('HGTEST_TIMEOUT', 360),
336 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
336 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
337 'port': ('HGTEST_PORT', 20059),
337 'port': ('HGTEST_PORT', 20059),
338 'shell': ('HGTEST_SHELL', 'sh'),
338 'shell': ('HGTEST_SHELL', 'sh'),
339 }
339 }
340
340
341 defaults = default_defaults.copy()
341 defaults = default_defaults.copy()
342
342
343
343
344 def canonpath(path):
344 def canonpath(path):
345 return os.path.realpath(os.path.expanduser(path))
345 return os.path.realpath(os.path.expanduser(path))
346
346
347
347
348 def parselistfiles(files, listtype, warn=True):
348 def parselistfiles(files, listtype, warn=True):
349 entries = dict()
349 entries = dict()
350 for filename in files:
350 for filename in files:
351 try:
351 try:
352 path = os.path.expanduser(os.path.expandvars(filename))
352 path = os.path.expanduser(os.path.expandvars(filename))
353 f = open(path, "rb")
353 f = open(path, "rb")
354 except IOError as err:
354 except IOError as err:
355 if err.errno != errno.ENOENT:
355 if err.errno != errno.ENOENT:
356 raise
356 raise
357 if warn:
357 if warn:
358 print("warning: no such %s file: %s" % (listtype, filename))
358 print("warning: no such %s file: %s" % (listtype, filename))
359 continue
359 continue
360
360
361 for line in f.readlines():
361 for line in f.readlines():
362 line = line.split(b'#', 1)[0].strip()
362 line = line.split(b'#', 1)[0].strip()
363 if line:
363 if line:
364 entries[line] = filename
364 # Ensure path entries are compatible with os.path.relpath()
365 entries[os.path.normpath(line)] = filename
365
366
366 f.close()
367 f.close()
367 return entries
368 return entries
368
369
369
370
370 def parsettestcases(path):
371 def parsettestcases(path):
371 """read a .t test file, return a set of test case names
372 """read a .t test file, return a set of test case names
372
373
373 If path does not exist, return an empty set.
374 If path does not exist, return an empty set.
374 """
375 """
375 cases = []
376 cases = []
376 try:
377 try:
377 with open(path, 'rb') as f:
378 with open(path, 'rb') as f:
378 for l in f:
379 for l in f:
379 if l.startswith(b'#testcases '):
380 if l.startswith(b'#testcases '):
380 cases.append(sorted(l[11:].split()))
381 cases.append(sorted(l[11:].split()))
381 except IOError as ex:
382 except IOError as ex:
382 if ex.errno != errno.ENOENT:
383 if ex.errno != errno.ENOENT:
383 raise
384 raise
384 return cases
385 return cases
385
386
386
387
387 def getparser():
388 def getparser():
388 """Obtain the OptionParser used by the CLI."""
389 """Obtain the OptionParser used by the CLI."""
389 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
390 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
390
391
391 selection = parser.add_argument_group('Test Selection')
392 selection = parser.add_argument_group('Test Selection')
392 selection.add_argument(
393 selection.add_argument(
393 '--allow-slow-tests',
394 '--allow-slow-tests',
394 action='store_true',
395 action='store_true',
395 help='allow extremely slow tests',
396 help='allow extremely slow tests',
396 )
397 )
397 selection.add_argument(
398 selection.add_argument(
398 "--blacklist",
399 "--blacklist",
399 action="append",
400 action="append",
400 help="skip tests listed in the specified blacklist file",
401 help="skip tests listed in the specified blacklist file",
401 )
402 )
402 selection.add_argument(
403 selection.add_argument(
403 "--changed",
404 "--changed",
404 help="run tests that are changed in parent rev or working directory",
405 help="run tests that are changed in parent rev or working directory",
405 )
406 )
406 selection.add_argument(
407 selection.add_argument(
407 "-k", "--keywords", help="run tests matching keywords"
408 "-k", "--keywords", help="run tests matching keywords"
408 )
409 )
409 selection.add_argument(
410 selection.add_argument(
410 "-r", "--retest", action="store_true", help="retest failed tests"
411 "-r", "--retest", action="store_true", help="retest failed tests"
411 )
412 )
412 selection.add_argument(
413 selection.add_argument(
413 "--test-list",
414 "--test-list",
414 action="append",
415 action="append",
415 help="read tests to run from the specified file",
416 help="read tests to run from the specified file",
416 )
417 )
417 selection.add_argument(
418 selection.add_argument(
418 "--whitelist",
419 "--whitelist",
419 action="append",
420 action="append",
420 help="always run tests listed in the specified whitelist file",
421 help="always run tests listed in the specified whitelist file",
421 )
422 )
422 selection.add_argument(
423 selection.add_argument(
423 'tests', metavar='TESTS', nargs='*', help='Tests to run'
424 'tests', metavar='TESTS', nargs='*', help='Tests to run'
424 )
425 )
425
426
426 harness = parser.add_argument_group('Test Harness Behavior')
427 harness = parser.add_argument_group('Test Harness Behavior')
427 harness.add_argument(
428 harness.add_argument(
428 '--bisect-repo',
429 '--bisect-repo',
429 metavar='bisect_repo',
430 metavar='bisect_repo',
430 help=(
431 help=(
431 "Path of a repo to bisect. Use together with " "--known-good-rev"
432 "Path of a repo to bisect. Use together with " "--known-good-rev"
432 ),
433 ),
433 )
434 )
434 harness.add_argument(
435 harness.add_argument(
435 "-d",
436 "-d",
436 "--debug",
437 "--debug",
437 action="store_true",
438 action="store_true",
438 help="debug mode: write output of test scripts to console"
439 help="debug mode: write output of test scripts to console"
439 " rather than capturing and diffing it (disables timeout)",
440 " rather than capturing and diffing it (disables timeout)",
440 )
441 )
441 harness.add_argument(
442 harness.add_argument(
442 "-f",
443 "-f",
443 "--first",
444 "--first",
444 action="store_true",
445 action="store_true",
445 help="exit on the first test failure",
446 help="exit on the first test failure",
446 )
447 )
447 harness.add_argument(
448 harness.add_argument(
448 "-i",
449 "-i",
449 "--interactive",
450 "--interactive",
450 action="store_true",
451 action="store_true",
451 help="prompt to accept changed output",
452 help="prompt to accept changed output",
452 )
453 )
453 harness.add_argument(
454 harness.add_argument(
454 "-j",
455 "-j",
455 "--jobs",
456 "--jobs",
456 type=int,
457 type=int,
457 help="number of jobs to run in parallel"
458 help="number of jobs to run in parallel"
458 " (default: $%s or %d)" % defaults['jobs'],
459 " (default: $%s or %d)" % defaults['jobs'],
459 )
460 )
460 harness.add_argument(
461 harness.add_argument(
461 "--keep-tmpdir",
462 "--keep-tmpdir",
462 action="store_true",
463 action="store_true",
463 help="keep temporary directory after running tests",
464 help="keep temporary directory after running tests",
464 )
465 )
465 harness.add_argument(
466 harness.add_argument(
466 '--known-good-rev',
467 '--known-good-rev',
467 metavar="known_good_rev",
468 metavar="known_good_rev",
468 help=(
469 help=(
469 "Automatically bisect any failures using this "
470 "Automatically bisect any failures using this "
470 "revision as a known-good revision."
471 "revision as a known-good revision."
471 ),
472 ),
472 )
473 )
473 harness.add_argument(
474 harness.add_argument(
474 "--list-tests",
475 "--list-tests",
475 action="store_true",
476 action="store_true",
476 help="list tests instead of running them",
477 help="list tests instead of running them",
477 )
478 )
478 harness.add_argument(
479 harness.add_argument(
479 "--loop", action="store_true", help="loop tests repeatedly"
480 "--loop", action="store_true", help="loop tests repeatedly"
480 )
481 )
481 harness.add_argument(
482 harness.add_argument(
482 '--random', action="store_true", help='run tests in random order'
483 '--random', action="store_true", help='run tests in random order'
483 )
484 )
484 harness.add_argument(
485 harness.add_argument(
485 '--order-by-runtime',
486 '--order-by-runtime',
486 action="store_true",
487 action="store_true",
487 help='run slowest tests first, according to .testtimes',
488 help='run slowest tests first, according to .testtimes',
488 )
489 )
489 harness.add_argument(
490 harness.add_argument(
490 "-p",
491 "-p",
491 "--port",
492 "--port",
492 type=int,
493 type=int,
493 help="port on which servers should listen"
494 help="port on which servers should listen"
494 " (default: $%s or %d)" % defaults['port'],
495 " (default: $%s or %d)" % defaults['port'],
495 )
496 )
496 harness.add_argument(
497 harness.add_argument(
497 '--profile-runner',
498 '--profile-runner',
498 action='store_true',
499 action='store_true',
499 help='run statprof on run-tests',
500 help='run statprof on run-tests',
500 )
501 )
501 harness.add_argument(
502 harness.add_argument(
502 "-R", "--restart", action="store_true", help="restart at last error"
503 "-R", "--restart", action="store_true", help="restart at last error"
503 )
504 )
504 harness.add_argument(
505 harness.add_argument(
505 "--runs-per-test",
506 "--runs-per-test",
506 type=int,
507 type=int,
507 dest="runs_per_test",
508 dest="runs_per_test",
508 help="run each test N times (default=1)",
509 help="run each test N times (default=1)",
509 default=1,
510 default=1,
510 )
511 )
511 harness.add_argument(
512 harness.add_argument(
512 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
513 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
513 )
514 )
514 harness.add_argument(
515 harness.add_argument(
515 '--showchannels', action='store_true', help='show scheduling channels'
516 '--showchannels', action='store_true', help='show scheduling channels'
516 )
517 )
517 harness.add_argument(
518 harness.add_argument(
518 "--slowtimeout",
519 "--slowtimeout",
519 type=int,
520 type=int,
520 help="kill errant slow tests after SLOWTIMEOUT seconds"
521 help="kill errant slow tests after SLOWTIMEOUT seconds"
521 " (default: $%s or %d)" % defaults['slowtimeout'],
522 " (default: $%s or %d)" % defaults['slowtimeout'],
522 )
523 )
523 harness.add_argument(
524 harness.add_argument(
524 "-t",
525 "-t",
525 "--timeout",
526 "--timeout",
526 type=int,
527 type=int,
527 help="kill errant tests after TIMEOUT seconds"
528 help="kill errant tests after TIMEOUT seconds"
528 " (default: $%s or %d)" % defaults['timeout'],
529 " (default: $%s or %d)" % defaults['timeout'],
529 )
530 )
530 harness.add_argument(
531 harness.add_argument(
531 "--tmpdir",
532 "--tmpdir",
532 help="run tests in the given temporary directory"
533 help="run tests in the given temporary directory"
533 " (implies --keep-tmpdir)",
534 " (implies --keep-tmpdir)",
534 )
535 )
535 harness.add_argument(
536 harness.add_argument(
536 "-v", "--verbose", action="store_true", help="output verbose messages"
537 "-v", "--verbose", action="store_true", help="output verbose messages"
537 )
538 )
538
539
539 hgconf = parser.add_argument_group('Mercurial Configuration')
540 hgconf = parser.add_argument_group('Mercurial Configuration')
540 hgconf.add_argument(
541 hgconf.add_argument(
541 "--chg",
542 "--chg",
542 action="store_true",
543 action="store_true",
543 help="install and use chg wrapper in place of hg",
544 help="install and use chg wrapper in place of hg",
544 )
545 )
545 hgconf.add_argument(
546 hgconf.add_argument(
546 "--chg-debug",
547 "--chg-debug",
547 action="store_true",
548 action="store_true",
548 help="show chg debug logs",
549 help="show chg debug logs",
549 )
550 )
550 hgconf.add_argument(
551 hgconf.add_argument(
551 "--rhg",
552 "--rhg",
552 action="store_true",
553 action="store_true",
553 help="install and use rhg Rust implementation in place of hg",
554 help="install and use rhg Rust implementation in place of hg",
554 )
555 )
555 hgconf.add_argument("--compiler", help="compiler to build with")
556 hgconf.add_argument("--compiler", help="compiler to build with")
556 hgconf.add_argument(
557 hgconf.add_argument(
557 '--extra-config-opt',
558 '--extra-config-opt',
558 action="append",
559 action="append",
559 default=[],
560 default=[],
560 help='set the given config opt in the test hgrc',
561 help='set the given config opt in the test hgrc',
561 )
562 )
562 hgconf.add_argument(
563 hgconf.add_argument(
563 "-l",
564 "-l",
564 "--local",
565 "--local",
565 action="store_true",
566 action="store_true",
566 help="shortcut for --with-hg=<testdir>/../hg, "
567 help="shortcut for --with-hg=<testdir>/../hg, "
567 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
568 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
568 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
569 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
569 )
570 )
570 hgconf.add_argument(
571 hgconf.add_argument(
571 "--ipv6",
572 "--ipv6",
572 action="store_true",
573 action="store_true",
573 help="prefer IPv6 to IPv4 for network related tests",
574 help="prefer IPv6 to IPv4 for network related tests",
574 )
575 )
575 hgconf.add_argument(
576 hgconf.add_argument(
576 "--pure",
577 "--pure",
577 action="store_true",
578 action="store_true",
578 help="use pure Python code instead of C extensions",
579 help="use pure Python code instead of C extensions",
579 )
580 )
580 hgconf.add_argument(
581 hgconf.add_argument(
581 "--rust",
582 "--rust",
582 action="store_true",
583 action="store_true",
583 help="use Rust code alongside C extensions",
584 help="use Rust code alongside C extensions",
584 )
585 )
585 hgconf.add_argument(
586 hgconf.add_argument(
586 "--no-rust",
587 "--no-rust",
587 action="store_true",
588 action="store_true",
588 help="do not use Rust code even if compiled",
589 help="do not use Rust code even if compiled",
589 )
590 )
590 hgconf.add_argument(
591 hgconf.add_argument(
591 "--with-chg",
592 "--with-chg",
592 metavar="CHG",
593 metavar="CHG",
593 help="use specified chg wrapper in place of hg",
594 help="use specified chg wrapper in place of hg",
594 )
595 )
595 hgconf.add_argument(
596 hgconf.add_argument(
596 "--with-rhg",
597 "--with-rhg",
597 metavar="RHG",
598 metavar="RHG",
598 help="use specified rhg Rust implementation in place of hg",
599 help="use specified rhg Rust implementation in place of hg",
599 )
600 )
600 hgconf.add_argument(
601 hgconf.add_argument(
601 "--with-hg",
602 "--with-hg",
602 metavar="HG",
603 metavar="HG",
603 help="test using specified hg script rather than a "
604 help="test using specified hg script rather than a "
604 "temporary installation",
605 "temporary installation",
605 )
606 )
606
607
607 reporting = parser.add_argument_group('Results Reporting')
608 reporting = parser.add_argument_group('Results Reporting')
608 reporting.add_argument(
609 reporting.add_argument(
609 "-C",
610 "-C",
610 "--annotate",
611 "--annotate",
611 action="store_true",
612 action="store_true",
612 help="output files annotated with coverage",
613 help="output files annotated with coverage",
613 )
614 )
614 reporting.add_argument(
615 reporting.add_argument(
615 "--color",
616 "--color",
616 choices=["always", "auto", "never"],
617 choices=["always", "auto", "never"],
617 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
618 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
618 help="colorisation: always|auto|never (default: auto)",
619 help="colorisation: always|auto|never (default: auto)",
619 )
620 )
620 reporting.add_argument(
621 reporting.add_argument(
621 "-c",
622 "-c",
622 "--cover",
623 "--cover",
623 action="store_true",
624 action="store_true",
624 help="print a test coverage report",
625 help="print a test coverage report",
625 )
626 )
626 reporting.add_argument(
627 reporting.add_argument(
627 '--exceptions',
628 '--exceptions',
628 action='store_true',
629 action='store_true',
629 help='log all exceptions and generate an exception report',
630 help='log all exceptions and generate an exception report',
630 )
631 )
631 reporting.add_argument(
632 reporting.add_argument(
632 "-H",
633 "-H",
633 "--htmlcov",
634 "--htmlcov",
634 action="store_true",
635 action="store_true",
635 help="create an HTML report of the coverage of the files",
636 help="create an HTML report of the coverage of the files",
636 )
637 )
637 reporting.add_argument(
638 reporting.add_argument(
638 "--json",
639 "--json",
639 action="store_true",
640 action="store_true",
640 help="store test result data in 'report.json' file",
641 help="store test result data in 'report.json' file",
641 )
642 )
642 reporting.add_argument(
643 reporting.add_argument(
643 "--outputdir",
644 "--outputdir",
644 help="directory to write error logs to (default=test directory)",
645 help="directory to write error logs to (default=test directory)",
645 )
646 )
646 reporting.add_argument(
647 reporting.add_argument(
647 "-n", "--nodiff", action="store_true", help="skip showing test changes"
648 "-n", "--nodiff", action="store_true", help="skip showing test changes"
648 )
649 )
649 reporting.add_argument(
650 reporting.add_argument(
650 "-S",
651 "-S",
651 "--noskips",
652 "--noskips",
652 action="store_true",
653 action="store_true",
653 help="don't report skip tests verbosely",
654 help="don't report skip tests verbosely",
654 )
655 )
655 reporting.add_argument(
656 reporting.add_argument(
656 "--time", action="store_true", help="time how long each test takes"
657 "--time", action="store_true", help="time how long each test takes"
657 )
658 )
658 reporting.add_argument("--view", help="external diff viewer")
659 reporting.add_argument("--view", help="external diff viewer")
659 reporting.add_argument(
660 reporting.add_argument(
660 "--xunit", help="record xunit results at specified path"
661 "--xunit", help="record xunit results at specified path"
661 )
662 )
662
663
663 for option, (envvar, default) in defaults.items():
664 for option, (envvar, default) in defaults.items():
664 defaults[option] = type(default)(os.environ.get(envvar, default))
665 defaults[option] = type(default)(os.environ.get(envvar, default))
665 parser.set_defaults(**defaults)
666 parser.set_defaults(**defaults)
666
667
667 return parser
668 return parser
668
669
669
670
670 def parseargs(args, parser):
671 def parseargs(args, parser):
671 """Parse arguments with our OptionParser and validate results."""
672 """Parse arguments with our OptionParser and validate results."""
672 options = parser.parse_args(args)
673 options = parser.parse_args(args)
673
674
674 # jython is always pure
675 # jython is always pure
675 if 'java' in sys.platform or '__pypy__' in sys.modules:
676 if 'java' in sys.platform or '__pypy__' in sys.modules:
676 options.pure = True
677 options.pure = True
677
678
678 if platform.python_implementation() != 'CPython' and options.rust:
679 if platform.python_implementation() != 'CPython' and options.rust:
679 parser.error('Rust extensions are only available with CPython')
680 parser.error('Rust extensions are only available with CPython')
680
681
681 if options.pure and options.rust:
682 if options.pure and options.rust:
682 parser.error('--rust cannot be used with --pure')
683 parser.error('--rust cannot be used with --pure')
683
684
684 if options.rust and options.no_rust:
685 if options.rust and options.no_rust:
685 parser.error('--rust cannot be used with --no-rust')
686 parser.error('--rust cannot be used with --no-rust')
686
687
687 if options.local:
688 if options.local:
688 if options.with_hg or options.with_rhg or options.with_chg:
689 if options.with_hg or options.with_rhg or options.with_chg:
689 parser.error(
690 parser.error(
690 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
691 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
691 )
692 )
692 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
693 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
693 reporootdir = os.path.dirname(testdir)
694 reporootdir = os.path.dirname(testdir)
694 pathandattrs = [(b'hg', 'with_hg')]
695 pathandattrs = [(b'hg', 'with_hg')]
695 if options.chg:
696 if options.chg:
696 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
697 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
697 if options.rhg:
698 if options.rhg:
698 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
699 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
699 for relpath, attr in pathandattrs:
700 for relpath, attr in pathandattrs:
700 binpath = os.path.join(reporootdir, relpath)
701 binpath = os.path.join(reporootdir, relpath)
701 if os.name != 'nt' and not os.access(binpath, os.X_OK):
702 if os.name != 'nt' and not os.access(binpath, os.X_OK):
702 parser.error(
703 parser.error(
703 '--local specified, but %r not found or '
704 '--local specified, but %r not found or '
704 'not executable' % binpath
705 'not executable' % binpath
705 )
706 )
706 setattr(options, attr, _bytes2sys(binpath))
707 setattr(options, attr, _bytes2sys(binpath))
707
708
708 if options.with_hg:
709 if options.with_hg:
709 options.with_hg = canonpath(_sys2bytes(options.with_hg))
710 options.with_hg = canonpath(_sys2bytes(options.with_hg))
710 if not (
711 if not (
711 os.path.isfile(options.with_hg)
712 os.path.isfile(options.with_hg)
712 and os.access(options.with_hg, os.X_OK)
713 and os.access(options.with_hg, os.X_OK)
713 ):
714 ):
714 parser.error('--with-hg must specify an executable hg script')
715 parser.error('--with-hg must specify an executable hg script')
715 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
716 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
716 sys.stderr.write('warning: --with-hg should specify an hg script\n')
717 sys.stderr.write('warning: --with-hg should specify an hg script\n')
717 sys.stderr.flush()
718 sys.stderr.flush()
718
719
719 if (options.chg or options.with_chg) and os.name == 'nt':
720 if (options.chg or options.with_chg) and os.name == 'nt':
720 parser.error('chg does not work on %s' % os.name)
721 parser.error('chg does not work on %s' % os.name)
721 if (options.rhg or options.with_rhg) and os.name == 'nt':
722 if (options.rhg or options.with_rhg) and os.name == 'nt':
722 parser.error('rhg does not work on %s' % os.name)
723 parser.error('rhg does not work on %s' % os.name)
723 if options.with_chg:
724 if options.with_chg:
724 options.chg = False # no installation to temporary location
725 options.chg = False # no installation to temporary location
725 options.with_chg = canonpath(_sys2bytes(options.with_chg))
726 options.with_chg = canonpath(_sys2bytes(options.with_chg))
726 if not (
727 if not (
727 os.path.isfile(options.with_chg)
728 os.path.isfile(options.with_chg)
728 and os.access(options.with_chg, os.X_OK)
729 and os.access(options.with_chg, os.X_OK)
729 ):
730 ):
730 parser.error('--with-chg must specify a chg executable')
731 parser.error('--with-chg must specify a chg executable')
731 if options.with_rhg:
732 if options.with_rhg:
732 options.rhg = False # no installation to temporary location
733 options.rhg = False # no installation to temporary location
733 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
734 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
734 if not (
735 if not (
735 os.path.isfile(options.with_rhg)
736 os.path.isfile(options.with_rhg)
736 and os.access(options.with_rhg, os.X_OK)
737 and os.access(options.with_rhg, os.X_OK)
737 ):
738 ):
738 parser.error('--with-rhg must specify a rhg executable')
739 parser.error('--with-rhg must specify a rhg executable')
739 if options.chg and options.with_hg:
740 if options.chg and options.with_hg:
740 # chg shares installation location with hg
741 # chg shares installation location with hg
741 parser.error(
742 parser.error(
742 '--chg does not work when --with-hg is specified '
743 '--chg does not work when --with-hg is specified '
743 '(use --with-chg instead)'
744 '(use --with-chg instead)'
744 )
745 )
745 if options.rhg and options.with_hg:
746 if options.rhg and options.with_hg:
746 # rhg shares installation location with hg
747 # rhg shares installation location with hg
747 parser.error(
748 parser.error(
748 '--rhg does not work when --with-hg is specified '
749 '--rhg does not work when --with-hg is specified '
749 '(use --with-rhg instead)'
750 '(use --with-rhg instead)'
750 )
751 )
751 if options.rhg and options.chg:
752 if options.rhg and options.chg:
752 parser.error('--rhg and --chg do not work together')
753 parser.error('--rhg and --chg do not work together')
753
754
754 if options.color == 'always' and not pygmentspresent:
755 if options.color == 'always' and not pygmentspresent:
755 sys.stderr.write(
756 sys.stderr.write(
756 'warning: --color=always ignored because '
757 'warning: --color=always ignored because '
757 'pygments is not installed\n'
758 'pygments is not installed\n'
758 )
759 )
759
760
760 if options.bisect_repo and not options.known_good_rev:
761 if options.bisect_repo and not options.known_good_rev:
761 parser.error("--bisect-repo cannot be used without --known-good-rev")
762 parser.error("--bisect-repo cannot be used without --known-good-rev")
762
763
763 global useipv6
764 global useipv6
764 if options.ipv6:
765 if options.ipv6:
765 useipv6 = checksocketfamily('AF_INET6')
766 useipv6 = checksocketfamily('AF_INET6')
766 else:
767 else:
767 # only use IPv6 if IPv4 is unavailable and IPv6 is available
768 # only use IPv6 if IPv4 is unavailable and IPv6 is available
768 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
769 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
769 'AF_INET6'
770 'AF_INET6'
770 )
771 )
771
772
772 options.anycoverage = options.cover or options.annotate or options.htmlcov
773 options.anycoverage = options.cover or options.annotate or options.htmlcov
773 if options.anycoverage:
774 if options.anycoverage:
774 try:
775 try:
775 import coverage
776 import coverage
776
777
777 covver = version.StrictVersion(coverage.__version__).version
778 covver = version.StrictVersion(coverage.__version__).version
778 if covver < (3, 3):
779 if covver < (3, 3):
779 parser.error('coverage options require coverage 3.3 or later')
780 parser.error('coverage options require coverage 3.3 or later')
780 except ImportError:
781 except ImportError:
781 parser.error('coverage options now require the coverage package')
782 parser.error('coverage options now require the coverage package')
782
783
783 if options.anycoverage and options.local:
784 if options.anycoverage and options.local:
784 # this needs some path mangling somewhere, I guess
785 # this needs some path mangling somewhere, I guess
785 parser.error(
786 parser.error(
786 "sorry, coverage options do not work when --local " "is specified"
787 "sorry, coverage options do not work when --local " "is specified"
787 )
788 )
788
789
789 if options.anycoverage and options.with_hg:
790 if options.anycoverage and options.with_hg:
790 parser.error(
791 parser.error(
791 "sorry, coverage options do not work when --with-hg " "is specified"
792 "sorry, coverage options do not work when --with-hg " "is specified"
792 )
793 )
793
794
794 global verbose
795 global verbose
795 if options.verbose:
796 if options.verbose:
796 verbose = ''
797 verbose = ''
797
798
798 if options.tmpdir:
799 if options.tmpdir:
799 options.tmpdir = canonpath(options.tmpdir)
800 options.tmpdir = canonpath(options.tmpdir)
800
801
801 if options.jobs < 1:
802 if options.jobs < 1:
802 parser.error('--jobs must be positive')
803 parser.error('--jobs must be positive')
803 if options.interactive and options.debug:
804 if options.interactive and options.debug:
804 parser.error("-i/--interactive and -d/--debug are incompatible")
805 parser.error("-i/--interactive and -d/--debug are incompatible")
805 if options.debug:
806 if options.debug:
806 if options.timeout != defaults['timeout']:
807 if options.timeout != defaults['timeout']:
807 sys.stderr.write('warning: --timeout option ignored with --debug\n')
808 sys.stderr.write('warning: --timeout option ignored with --debug\n')
808 if options.slowtimeout != defaults['slowtimeout']:
809 if options.slowtimeout != defaults['slowtimeout']:
809 sys.stderr.write(
810 sys.stderr.write(
810 'warning: --slowtimeout option ignored with --debug\n'
811 'warning: --slowtimeout option ignored with --debug\n'
811 )
812 )
812 options.timeout = 0
813 options.timeout = 0
813 options.slowtimeout = 0
814 options.slowtimeout = 0
814
815
815 if options.blacklist:
816 if options.blacklist:
816 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
817 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
817 if options.whitelist:
818 if options.whitelist:
818 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
819 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
819 else:
820 else:
820 options.whitelisted = {}
821 options.whitelisted = {}
821
822
822 if options.showchannels:
823 if options.showchannels:
823 options.nodiff = True
824 options.nodiff = True
824
825
825 return options
826 return options
826
827
827
828
828 def rename(src, dst):
829 def rename(src, dst):
829 """Like os.rename(), trade atomicity and opened files friendliness
830 """Like os.rename(), trade atomicity and opened files friendliness
830 for existing destination support.
831 for existing destination support.
831 """
832 """
832 shutil.copy(src, dst)
833 shutil.copy(src, dst)
833 os.remove(src)
834 os.remove(src)
834
835
835
836
836 def makecleanable(path):
837 def makecleanable(path):
837 """Try to fix directory permission recursively so that the entire tree
838 """Try to fix directory permission recursively so that the entire tree
838 can be deleted"""
839 can be deleted"""
839 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
840 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
840 for d in dirnames:
841 for d in dirnames:
841 p = os.path.join(dirpath, d)
842 p = os.path.join(dirpath, d)
842 try:
843 try:
843 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
844 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
844 except OSError:
845 except OSError:
845 pass
846 pass
846
847
847
848
848 _unified_diff = difflib.unified_diff
849 _unified_diff = difflib.unified_diff
849 if PYTHON3:
850 if PYTHON3:
850 import functools
851 import functools
851
852
852 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
853 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
853
854
854
855
855 def getdiff(expected, output, ref, err):
856 def getdiff(expected, output, ref, err):
856 servefail = False
857 servefail = False
857 lines = []
858 lines = []
858 for line in _unified_diff(expected, output, ref, err):
859 for line in _unified_diff(expected, output, ref, err):
859 if line.startswith(b'+++') or line.startswith(b'---'):
860 if line.startswith(b'+++') or line.startswith(b'---'):
860 line = line.replace(b'\\', b'/')
861 line = line.replace(b'\\', b'/')
861 if line.endswith(b' \n'):
862 if line.endswith(b' \n'):
862 line = line[:-2] + b'\n'
863 line = line[:-2] + b'\n'
863 lines.append(line)
864 lines.append(line)
864 if not servefail and line.startswith(
865 if not servefail and line.startswith(
865 b'+ abort: child process failed to start'
866 b'+ abort: child process failed to start'
866 ):
867 ):
867 servefail = True
868 servefail = True
868
869
869 return servefail, lines
870 return servefail, lines
870
871
871
872
872 verbose = False
873 verbose = False
873
874
874
875
875 def vlog(*msg):
876 def vlog(*msg):
876 """Log only when in verbose mode."""
877 """Log only when in verbose mode."""
877 if verbose is False:
878 if verbose is False:
878 return
879 return
879
880
880 return log(*msg)
881 return log(*msg)
881
882
882
883
883 # Bytes that break XML even in a CDATA block: control characters 0-31
884 # Bytes that break XML even in a CDATA block: control characters 0-31
884 # sans \t, \n and \r
885 # sans \t, \n and \r
885 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
886 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
886
887
887 # Match feature conditionalized output lines in the form, capturing the feature
888 # Match feature conditionalized output lines in the form, capturing the feature
888 # list in group 2, and the preceeding line output in group 1:
889 # list in group 2, and the preceeding line output in group 1:
889 #
890 #
890 # output..output (feature !)\n
891 # output..output (feature !)\n
891 optline = re.compile(br'(.*) \((.+?) !\)\n$')
892 optline = re.compile(br'(.*) \((.+?) !\)\n$')
892
893
893
894
894 def cdatasafe(data):
895 def cdatasafe(data):
895 """Make a string safe to include in a CDATA block.
896 """Make a string safe to include in a CDATA block.
896
897
897 Certain control characters are illegal in a CDATA block, and
898 Certain control characters are illegal in a CDATA block, and
898 there's no way to include a ]]> in a CDATA either. This function
899 there's no way to include a ]]> in a CDATA either. This function
899 replaces illegal bytes with ? and adds a space between the ]] so
900 replaces illegal bytes with ? and adds a space between the ]] so
900 that it won't break the CDATA block.
901 that it won't break the CDATA block.
901 """
902 """
902 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
903 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
903
904
904
905
905 def log(*msg):
906 def log(*msg):
906 """Log something to stdout.
907 """Log something to stdout.
907
908
908 Arguments are strings to print.
909 Arguments are strings to print.
909 """
910 """
910 with iolock:
911 with iolock:
911 if verbose:
912 if verbose:
912 print(verbose, end=' ')
913 print(verbose, end=' ')
913 for m in msg:
914 for m in msg:
914 print(m, end=' ')
915 print(m, end=' ')
915 print()
916 print()
916 sys.stdout.flush()
917 sys.stdout.flush()
917
918
918
919
919 def highlightdiff(line, color):
920 def highlightdiff(line, color):
920 if not color:
921 if not color:
921 return line
922 return line
922 assert pygmentspresent
923 assert pygmentspresent
923 return pygments.highlight(
924 return pygments.highlight(
924 line.decode('latin1'), difflexer, terminal256formatter
925 line.decode('latin1'), difflexer, terminal256formatter
925 ).encode('latin1')
926 ).encode('latin1')
926
927
927
928
928 def highlightmsg(msg, color):
929 def highlightmsg(msg, color):
929 if not color:
930 if not color:
930 return msg
931 return msg
931 assert pygmentspresent
932 assert pygmentspresent
932 return pygments.highlight(msg, runnerlexer, runnerformatter)
933 return pygments.highlight(msg, runnerlexer, runnerformatter)
933
934
934
935
935 def terminate(proc):
936 def terminate(proc):
936 """Terminate subprocess"""
937 """Terminate subprocess"""
937 vlog('# Terminating process %d' % proc.pid)
938 vlog('# Terminating process %d' % proc.pid)
938 try:
939 try:
939 proc.terminate()
940 proc.terminate()
940 except OSError:
941 except OSError:
941 pass
942 pass
942
943
943
944
944 def killdaemons(pidfile):
945 def killdaemons(pidfile):
945 import killdaemons as killmod
946 import killdaemons as killmod
946
947
947 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
948 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
948
949
949
950
950 class Test(unittest.TestCase):
951 class Test(unittest.TestCase):
951 """Encapsulates a single, runnable test.
952 """Encapsulates a single, runnable test.
952
953
953 While this class conforms to the unittest.TestCase API, it differs in that
954 While this class conforms to the unittest.TestCase API, it differs in that
954 instances need to be instantiated manually. (Typically, unittest.TestCase
955 instances need to be instantiated manually. (Typically, unittest.TestCase
955 classes are instantiated automatically by scanning modules.)
956 classes are instantiated automatically by scanning modules.)
956 """
957 """
957
958
958 # Status code reserved for skipped tests (used by hghave).
959 # Status code reserved for skipped tests (used by hghave).
959 SKIPPED_STATUS = 80
960 SKIPPED_STATUS = 80
960
961
961 def __init__(
962 def __init__(
962 self,
963 self,
963 path,
964 path,
964 outputdir,
965 outputdir,
965 tmpdir,
966 tmpdir,
966 keeptmpdir=False,
967 keeptmpdir=False,
967 debug=False,
968 debug=False,
968 first=False,
969 first=False,
969 timeout=None,
970 timeout=None,
970 startport=None,
971 startport=None,
971 extraconfigopts=None,
972 extraconfigopts=None,
972 shell=None,
973 shell=None,
973 hgcommand=None,
974 hgcommand=None,
974 slowtimeout=None,
975 slowtimeout=None,
975 usechg=False,
976 usechg=False,
976 chgdebug=False,
977 chgdebug=False,
977 useipv6=False,
978 useipv6=False,
978 ):
979 ):
979 """Create a test from parameters.
980 """Create a test from parameters.
980
981
981 path is the full path to the file defining the test.
982 path is the full path to the file defining the test.
982
983
983 tmpdir is the main temporary directory to use for this test.
984 tmpdir is the main temporary directory to use for this test.
984
985
985 keeptmpdir determines whether to keep the test's temporary directory
986 keeptmpdir determines whether to keep the test's temporary directory
986 after execution. It defaults to removal (False).
987 after execution. It defaults to removal (False).
987
988
988 debug mode will make the test execute verbosely, with unfiltered
989 debug mode will make the test execute verbosely, with unfiltered
989 output.
990 output.
990
991
991 timeout controls the maximum run time of the test. It is ignored when
992 timeout controls the maximum run time of the test. It is ignored when
992 debug is True. See slowtimeout for tests with #require slow.
993 debug is True. See slowtimeout for tests with #require slow.
993
994
994 slowtimeout overrides timeout if the test has #require slow.
995 slowtimeout overrides timeout if the test has #require slow.
995
996
996 startport controls the starting port number to use for this test. Each
997 startport controls the starting port number to use for this test. Each
997 test will reserve 3 port numbers for execution. It is the caller's
998 test will reserve 3 port numbers for execution. It is the caller's
998 responsibility to allocate a non-overlapping port range to Test
999 responsibility to allocate a non-overlapping port range to Test
999 instances.
1000 instances.
1000
1001
1001 extraconfigopts is an iterable of extra hgrc config options. Values
1002 extraconfigopts is an iterable of extra hgrc config options. Values
1002 must have the form "key=value" (something understood by hgrc). Values
1003 must have the form "key=value" (something understood by hgrc). Values
1003 of the form "foo.key=value" will result in "[foo] key=value".
1004 of the form "foo.key=value" will result in "[foo] key=value".
1004
1005
1005 shell is the shell to execute tests in.
1006 shell is the shell to execute tests in.
1006 """
1007 """
1007 if timeout is None:
1008 if timeout is None:
1008 timeout = defaults['timeout']
1009 timeout = defaults['timeout']
1009 if startport is None:
1010 if startport is None:
1010 startport = defaults['port']
1011 startport = defaults['port']
1011 if slowtimeout is None:
1012 if slowtimeout is None:
1012 slowtimeout = defaults['slowtimeout']
1013 slowtimeout = defaults['slowtimeout']
1013 self.path = path
1014 self.path = path
1014 self.relpath = os.path.relpath(path)
1015 self.relpath = os.path.relpath(path)
1015 self.bname = os.path.basename(path)
1016 self.bname = os.path.basename(path)
1016 self.name = _bytes2sys(self.bname)
1017 self.name = _bytes2sys(self.bname)
1017 self._testdir = os.path.dirname(path)
1018 self._testdir = os.path.dirname(path)
1018 self._outputdir = outputdir
1019 self._outputdir = outputdir
1019 self._tmpname = os.path.basename(path)
1020 self._tmpname = os.path.basename(path)
1020 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1021 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1021
1022
1022 self._threadtmp = tmpdir
1023 self._threadtmp = tmpdir
1023 self._keeptmpdir = keeptmpdir
1024 self._keeptmpdir = keeptmpdir
1024 self._debug = debug
1025 self._debug = debug
1025 self._first = first
1026 self._first = first
1026 self._timeout = timeout
1027 self._timeout = timeout
1027 self._slowtimeout = slowtimeout
1028 self._slowtimeout = slowtimeout
1028 self._startport = startport
1029 self._startport = startport
1029 self._extraconfigopts = extraconfigopts or []
1030 self._extraconfigopts = extraconfigopts or []
1030 self._shell = _sys2bytes(shell)
1031 self._shell = _sys2bytes(shell)
1031 self._hgcommand = hgcommand or b'hg'
1032 self._hgcommand = hgcommand or b'hg'
1032 self._usechg = usechg
1033 self._usechg = usechg
1033 self._chgdebug = chgdebug
1034 self._chgdebug = chgdebug
1034 self._useipv6 = useipv6
1035 self._useipv6 = useipv6
1035
1036
1036 self._aborted = False
1037 self._aborted = False
1037 self._daemonpids = []
1038 self._daemonpids = []
1038 self._finished = None
1039 self._finished = None
1039 self._ret = None
1040 self._ret = None
1040 self._out = None
1041 self._out = None
1041 self._skipped = None
1042 self._skipped = None
1042 self._testtmp = None
1043 self._testtmp = None
1043 self._chgsockdir = None
1044 self._chgsockdir = None
1044
1045
1045 self._refout = self.readrefout()
1046 self._refout = self.readrefout()
1046
1047
1047 def readrefout(self):
1048 def readrefout(self):
1048 """read reference output"""
1049 """read reference output"""
1049 # If we're not in --debug mode and reference output file exists,
1050 # If we're not in --debug mode and reference output file exists,
1050 # check test output against it.
1051 # check test output against it.
1051 if self._debug:
1052 if self._debug:
1052 return None # to match "out is None"
1053 return None # to match "out is None"
1053 elif os.path.exists(self.refpath):
1054 elif os.path.exists(self.refpath):
1054 with open(self.refpath, 'rb') as f:
1055 with open(self.refpath, 'rb') as f:
1055 return f.read().splitlines(True)
1056 return f.read().splitlines(True)
1056 else:
1057 else:
1057 return []
1058 return []
1058
1059
1059 # needed to get base class __repr__ running
1060 # needed to get base class __repr__ running
1060 @property
1061 @property
1061 def _testMethodName(self):
1062 def _testMethodName(self):
1062 return self.name
1063 return self.name
1063
1064
1064 def __str__(self):
1065 def __str__(self):
1065 return self.name
1066 return self.name
1066
1067
1067 def shortDescription(self):
1068 def shortDescription(self):
1068 return self.name
1069 return self.name
1069
1070
1070 def setUp(self):
1071 def setUp(self):
1071 """Tasks to perform before run()."""
1072 """Tasks to perform before run()."""
1072 self._finished = False
1073 self._finished = False
1073 self._ret = None
1074 self._ret = None
1074 self._out = None
1075 self._out = None
1075 self._skipped = None
1076 self._skipped = None
1076
1077
1077 try:
1078 try:
1078 os.mkdir(self._threadtmp)
1079 os.mkdir(self._threadtmp)
1079 except OSError as e:
1080 except OSError as e:
1080 if e.errno != errno.EEXIST:
1081 if e.errno != errno.EEXIST:
1081 raise
1082 raise
1082
1083
1083 name = self._tmpname
1084 name = self._tmpname
1084 self._testtmp = os.path.join(self._threadtmp, name)
1085 self._testtmp = os.path.join(self._threadtmp, name)
1085 os.mkdir(self._testtmp)
1086 os.mkdir(self._testtmp)
1086
1087
1087 # Remove any previous output files.
1088 # Remove any previous output files.
1088 if os.path.exists(self.errpath):
1089 if os.path.exists(self.errpath):
1089 try:
1090 try:
1090 os.remove(self.errpath)
1091 os.remove(self.errpath)
1091 except OSError as e:
1092 except OSError as e:
1092 # We might have raced another test to clean up a .err
1093 # We might have raced another test to clean up a .err
1093 # file, so ignore ENOENT when removing a previous .err
1094 # file, so ignore ENOENT when removing a previous .err
1094 # file.
1095 # file.
1095 if e.errno != errno.ENOENT:
1096 if e.errno != errno.ENOENT:
1096 raise
1097 raise
1097
1098
1098 if self._usechg:
1099 if self._usechg:
1099 self._chgsockdir = os.path.join(
1100 self._chgsockdir = os.path.join(
1100 self._threadtmp, b'%s.chgsock' % name
1101 self._threadtmp, b'%s.chgsock' % name
1101 )
1102 )
1102 os.mkdir(self._chgsockdir)
1103 os.mkdir(self._chgsockdir)
1103
1104
1104 def run(self, result):
1105 def run(self, result):
1105 """Run this test and report results against a TestResult instance."""
1106 """Run this test and report results against a TestResult instance."""
1106 # This function is extremely similar to unittest.TestCase.run(). Once
1107 # This function is extremely similar to unittest.TestCase.run(). Once
1107 # we require Python 2.7 (or at least its version of unittest), this
1108 # we require Python 2.7 (or at least its version of unittest), this
1108 # function can largely go away.
1109 # function can largely go away.
1109 self._result = result
1110 self._result = result
1110 result.startTest(self)
1111 result.startTest(self)
1111 try:
1112 try:
1112 try:
1113 try:
1113 self.setUp()
1114 self.setUp()
1114 except (KeyboardInterrupt, SystemExit):
1115 except (KeyboardInterrupt, SystemExit):
1115 self._aborted = True
1116 self._aborted = True
1116 raise
1117 raise
1117 except Exception:
1118 except Exception:
1118 result.addError(self, sys.exc_info())
1119 result.addError(self, sys.exc_info())
1119 return
1120 return
1120
1121
1121 success = False
1122 success = False
1122 try:
1123 try:
1123 self.runTest()
1124 self.runTest()
1124 except KeyboardInterrupt:
1125 except KeyboardInterrupt:
1125 self._aborted = True
1126 self._aborted = True
1126 raise
1127 raise
1127 except unittest.SkipTest as e:
1128 except unittest.SkipTest as e:
1128 result.addSkip(self, str(e))
1129 result.addSkip(self, str(e))
1129 # The base class will have already counted this as a
1130 # The base class will have already counted this as a
1130 # test we "ran", but we want to exclude skipped tests
1131 # test we "ran", but we want to exclude skipped tests
1131 # from those we count towards those run.
1132 # from those we count towards those run.
1132 result.testsRun -= 1
1133 result.testsRun -= 1
1133 except self.failureException as e:
1134 except self.failureException as e:
1134 # This differs from unittest in that we don't capture
1135 # This differs from unittest in that we don't capture
1135 # the stack trace. This is for historical reasons and
1136 # the stack trace. This is for historical reasons and
1136 # this decision could be revisited in the future,
1137 # this decision could be revisited in the future,
1137 # especially for PythonTest instances.
1138 # especially for PythonTest instances.
1138 if result.addFailure(self, str(e)):
1139 if result.addFailure(self, str(e)):
1139 success = True
1140 success = True
1140 except Exception:
1141 except Exception:
1141 result.addError(self, sys.exc_info())
1142 result.addError(self, sys.exc_info())
1142 else:
1143 else:
1143 success = True
1144 success = True
1144
1145
1145 try:
1146 try:
1146 self.tearDown()
1147 self.tearDown()
1147 except (KeyboardInterrupt, SystemExit):
1148 except (KeyboardInterrupt, SystemExit):
1148 self._aborted = True
1149 self._aborted = True
1149 raise
1150 raise
1150 except Exception:
1151 except Exception:
1151 result.addError(self, sys.exc_info())
1152 result.addError(self, sys.exc_info())
1152 success = False
1153 success = False
1153
1154
1154 if success:
1155 if success:
1155 result.addSuccess(self)
1156 result.addSuccess(self)
1156 finally:
1157 finally:
1157 result.stopTest(self, interrupted=self._aborted)
1158 result.stopTest(self, interrupted=self._aborted)
1158
1159
1159 def runTest(self):
1160 def runTest(self):
1160 """Run this test instance.
1161 """Run this test instance.
1161
1162
1162 This will return a tuple describing the result of the test.
1163 This will return a tuple describing the result of the test.
1163 """
1164 """
1164 env = self._getenv()
1165 env = self._getenv()
1165 self._genrestoreenv(env)
1166 self._genrestoreenv(env)
1166 self._daemonpids.append(env['DAEMON_PIDS'])
1167 self._daemonpids.append(env['DAEMON_PIDS'])
1167 self._createhgrc(env['HGRCPATH'])
1168 self._createhgrc(env['HGRCPATH'])
1168
1169
1169 vlog('# Test', self.name)
1170 vlog('# Test', self.name)
1170
1171
1171 ret, out = self._run(env)
1172 ret, out = self._run(env)
1172 self._finished = True
1173 self._finished = True
1173 self._ret = ret
1174 self._ret = ret
1174 self._out = out
1175 self._out = out
1175
1176
1176 def describe(ret):
1177 def describe(ret):
1177 if ret < 0:
1178 if ret < 0:
1178 return 'killed by signal: %d' % -ret
1179 return 'killed by signal: %d' % -ret
1179 return 'returned error code %d' % ret
1180 return 'returned error code %d' % ret
1180
1181
1181 self._skipped = False
1182 self._skipped = False
1182
1183
1183 if ret == self.SKIPPED_STATUS:
1184 if ret == self.SKIPPED_STATUS:
1184 if out is None: # Debug mode, nothing to parse.
1185 if out is None: # Debug mode, nothing to parse.
1185 missing = ['unknown']
1186 missing = ['unknown']
1186 failed = None
1187 failed = None
1187 else:
1188 else:
1188 missing, failed = TTest.parsehghaveoutput(out)
1189 missing, failed = TTest.parsehghaveoutput(out)
1189
1190
1190 if not missing:
1191 if not missing:
1191 missing = ['skipped']
1192 missing = ['skipped']
1192
1193
1193 if failed:
1194 if failed:
1194 self.fail('hg have failed checking for %s' % failed[-1])
1195 self.fail('hg have failed checking for %s' % failed[-1])
1195 else:
1196 else:
1196 self._skipped = True
1197 self._skipped = True
1197 raise unittest.SkipTest(missing[-1])
1198 raise unittest.SkipTest(missing[-1])
1198 elif ret == 'timeout':
1199 elif ret == 'timeout':
1199 self.fail('timed out')
1200 self.fail('timed out')
1200 elif ret is False:
1201 elif ret is False:
1201 self.fail('no result code from test')
1202 self.fail('no result code from test')
1202 elif out != self._refout:
1203 elif out != self._refout:
1203 # Diff generation may rely on written .err file.
1204 # Diff generation may rely on written .err file.
1204 if (
1205 if (
1205 (ret != 0 or out != self._refout)
1206 (ret != 0 or out != self._refout)
1206 and not self._skipped
1207 and not self._skipped
1207 and not self._debug
1208 and not self._debug
1208 ):
1209 ):
1209 with open(self.errpath, 'wb') as f:
1210 with open(self.errpath, 'wb') as f:
1210 for line in out:
1211 for line in out:
1211 f.write(line)
1212 f.write(line)
1212
1213
1213 # The result object handles diff calculation for us.
1214 # The result object handles diff calculation for us.
1214 with firstlock:
1215 with firstlock:
1215 if self._result.addOutputMismatch(self, ret, out, self._refout):
1216 if self._result.addOutputMismatch(self, ret, out, self._refout):
1216 # change was accepted, skip failing
1217 # change was accepted, skip failing
1217 return
1218 return
1218 if self._first:
1219 if self._first:
1219 global firsterror
1220 global firsterror
1220 firsterror = True
1221 firsterror = True
1221
1222
1222 if ret:
1223 if ret:
1223 msg = 'output changed and ' + describe(ret)
1224 msg = 'output changed and ' + describe(ret)
1224 else:
1225 else:
1225 msg = 'output changed'
1226 msg = 'output changed'
1226
1227
1227 self.fail(msg)
1228 self.fail(msg)
1228 elif ret:
1229 elif ret:
1229 self.fail(describe(ret))
1230 self.fail(describe(ret))
1230
1231
1231 def tearDown(self):
1232 def tearDown(self):
1232 """Tasks to perform after run()."""
1233 """Tasks to perform after run()."""
1233 for entry in self._daemonpids:
1234 for entry in self._daemonpids:
1234 killdaemons(entry)
1235 killdaemons(entry)
1235 self._daemonpids = []
1236 self._daemonpids = []
1236
1237
1237 if self._keeptmpdir:
1238 if self._keeptmpdir:
1238 log(
1239 log(
1239 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1240 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1240 % (
1241 % (
1241 _bytes2sys(self._testtmp),
1242 _bytes2sys(self._testtmp),
1242 _bytes2sys(self._threadtmp),
1243 _bytes2sys(self._threadtmp),
1243 )
1244 )
1244 )
1245 )
1245 else:
1246 else:
1246 try:
1247 try:
1247 shutil.rmtree(self._testtmp)
1248 shutil.rmtree(self._testtmp)
1248 except OSError:
1249 except OSError:
1249 # unreadable directory may be left in $TESTTMP; fix permission
1250 # unreadable directory may be left in $TESTTMP; fix permission
1250 # and try again
1251 # and try again
1251 makecleanable(self._testtmp)
1252 makecleanable(self._testtmp)
1252 shutil.rmtree(self._testtmp, True)
1253 shutil.rmtree(self._testtmp, True)
1253 shutil.rmtree(self._threadtmp, True)
1254 shutil.rmtree(self._threadtmp, True)
1254
1255
1255 if self._usechg:
1256 if self._usechg:
1256 # chgservers will stop automatically after they find the socket
1257 # chgservers will stop automatically after they find the socket
1257 # files are deleted
1258 # files are deleted
1258 shutil.rmtree(self._chgsockdir, True)
1259 shutil.rmtree(self._chgsockdir, True)
1259
1260
1260 if (
1261 if (
1261 (self._ret != 0 or self._out != self._refout)
1262 (self._ret != 0 or self._out != self._refout)
1262 and not self._skipped
1263 and not self._skipped
1263 and not self._debug
1264 and not self._debug
1264 and self._out
1265 and self._out
1265 ):
1266 ):
1266 with open(self.errpath, 'wb') as f:
1267 with open(self.errpath, 'wb') as f:
1267 for line in self._out:
1268 for line in self._out:
1268 f.write(line)
1269 f.write(line)
1269
1270
1270 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1271 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1271
1272
1272 def _run(self, env):
1273 def _run(self, env):
1273 # This should be implemented in child classes to run tests.
1274 # This should be implemented in child classes to run tests.
1274 raise unittest.SkipTest('unknown test type')
1275 raise unittest.SkipTest('unknown test type')
1275
1276
1276 def abort(self):
1277 def abort(self):
1277 """Terminate execution of this test."""
1278 """Terminate execution of this test."""
1278 self._aborted = True
1279 self._aborted = True
1279
1280
1280 def _portmap(self, i):
1281 def _portmap(self, i):
1281 offset = b'' if i == 0 else b'%d' % i
1282 offset = b'' if i == 0 else b'%d' % i
1282 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1283 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1283
1284
1284 def _getreplacements(self):
1285 def _getreplacements(self):
1285 """Obtain a mapping of text replacements to apply to test output.
1286 """Obtain a mapping of text replacements to apply to test output.
1286
1287
1287 Test output needs to be normalized so it can be compared to expected
1288 Test output needs to be normalized so it can be compared to expected
1288 output. This function defines how some of that normalization will
1289 output. This function defines how some of that normalization will
1289 occur.
1290 occur.
1290 """
1291 """
1291 r = [
1292 r = [
1292 # This list should be parallel to defineport in _getenv
1293 # This list should be parallel to defineport in _getenv
1293 self._portmap(0),
1294 self._portmap(0),
1294 self._portmap(1),
1295 self._portmap(1),
1295 self._portmap(2),
1296 self._portmap(2),
1296 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1297 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1297 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1298 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1298 ]
1299 ]
1299 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1300 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1300
1301
1301 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1302 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1302
1303
1303 if os.path.exists(replacementfile):
1304 if os.path.exists(replacementfile):
1304 data = {}
1305 data = {}
1305 with open(replacementfile, mode='rb') as source:
1306 with open(replacementfile, mode='rb') as source:
1306 # the intermediate 'compile' step help with debugging
1307 # the intermediate 'compile' step help with debugging
1307 code = compile(source.read(), replacementfile, 'exec')
1308 code = compile(source.read(), replacementfile, 'exec')
1308 exec(code, data)
1309 exec(code, data)
1309 for value in data.get('substitutions', ()):
1310 for value in data.get('substitutions', ()):
1310 if len(value) != 2:
1311 if len(value) != 2:
1311 msg = 'malformatted substitution in %s: %r'
1312 msg = 'malformatted substitution in %s: %r'
1312 msg %= (replacementfile, value)
1313 msg %= (replacementfile, value)
1313 raise ValueError(msg)
1314 raise ValueError(msg)
1314 r.append(value)
1315 r.append(value)
1315 return r
1316 return r
1316
1317
1317 def _escapepath(self, p):
1318 def _escapepath(self, p):
1318 if os.name == 'nt':
1319 if os.name == 'nt':
1319 return b''.join(
1320 return b''.join(
1320 c.isalpha()
1321 c.isalpha()
1321 and b'[%s%s]' % (c.lower(), c.upper())
1322 and b'[%s%s]' % (c.lower(), c.upper())
1322 or c in b'/\\'
1323 or c in b'/\\'
1323 and br'[/\\]'
1324 and br'[/\\]'
1324 or c.isdigit()
1325 or c.isdigit()
1325 and c
1326 and c
1326 or b'\\' + c
1327 or b'\\' + c
1327 for c in [p[i : i + 1] for i in range(len(p))]
1328 for c in [p[i : i + 1] for i in range(len(p))]
1328 )
1329 )
1329 else:
1330 else:
1330 return re.escape(p)
1331 return re.escape(p)
1331
1332
1332 def _localip(self):
1333 def _localip(self):
1333 if self._useipv6:
1334 if self._useipv6:
1334 return b'::1'
1335 return b'::1'
1335 else:
1336 else:
1336 return b'127.0.0.1'
1337 return b'127.0.0.1'
1337
1338
1338 def _genrestoreenv(self, testenv):
1339 def _genrestoreenv(self, testenv):
1339 """Generate a script that can be used by tests to restore the original
1340 """Generate a script that can be used by tests to restore the original
1340 environment."""
1341 environment."""
1341 # Put the restoreenv script inside self._threadtmp
1342 # Put the restoreenv script inside self._threadtmp
1342 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1343 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1343 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1344 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1344
1345
1345 # Only restore environment variable names that the shell allows
1346 # Only restore environment variable names that the shell allows
1346 # us to export.
1347 # us to export.
1347 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1348 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1348
1349
1349 # Do not restore these variables; otherwise tests would fail.
1350 # Do not restore these variables; otherwise tests would fail.
1350 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1351 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1351
1352
1352 with open(scriptpath, 'w') as envf:
1353 with open(scriptpath, 'w') as envf:
1353 for name, value in origenviron.items():
1354 for name, value in origenviron.items():
1354 if not name_regex.match(name):
1355 if not name_regex.match(name):
1355 # Skip environment variables with unusual names not
1356 # Skip environment variables with unusual names not
1356 # allowed by most shells.
1357 # allowed by most shells.
1357 continue
1358 continue
1358 if name in reqnames:
1359 if name in reqnames:
1359 continue
1360 continue
1360 envf.write('%s=%s\n' % (name, shellquote(value)))
1361 envf.write('%s=%s\n' % (name, shellquote(value)))
1361
1362
1362 for name in testenv:
1363 for name in testenv:
1363 if name in origenviron or name in reqnames:
1364 if name in origenviron or name in reqnames:
1364 continue
1365 continue
1365 envf.write('unset %s\n' % (name,))
1366 envf.write('unset %s\n' % (name,))
1366
1367
1367 def _getenv(self):
1368 def _getenv(self):
1368 """Obtain environment variables to use during test execution."""
1369 """Obtain environment variables to use during test execution."""
1369
1370
1370 def defineport(i):
1371 def defineport(i):
1371 offset = '' if i == 0 else '%s' % i
1372 offset = '' if i == 0 else '%s' % i
1372 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1373 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1373
1374
1374 env = os.environ.copy()
1375 env = os.environ.copy()
1375 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1376 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1376 env['HGEMITWARNINGS'] = '1'
1377 env['HGEMITWARNINGS'] = '1'
1377 env['TESTTMP'] = _bytes2sys(self._testtmp)
1378 env['TESTTMP'] = _bytes2sys(self._testtmp)
1378 env['TESTNAME'] = self.name
1379 env['TESTNAME'] = self.name
1379 env['HOME'] = _bytes2sys(self._testtmp)
1380 env['HOME'] = _bytes2sys(self._testtmp)
1380 if os.name == 'nt':
1381 if os.name == 'nt':
1381 env['REALUSERPROFILE'] = env['USERPROFILE']
1382 env['REALUSERPROFILE'] = env['USERPROFILE']
1382 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1383 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1383 env['USERPROFILE'] = env['HOME']
1384 env['USERPROFILE'] = env['HOME']
1384 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1385 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1385 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1386 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1386 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1387 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1387 # This number should match portneeded in _getport
1388 # This number should match portneeded in _getport
1388 for port in xrange(3):
1389 for port in xrange(3):
1389 # This list should be parallel to _portmap in _getreplacements
1390 # This list should be parallel to _portmap in _getreplacements
1390 defineport(port)
1391 defineport(port)
1391 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1392 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1392 env["DAEMON_PIDS"] = _bytes2sys(
1393 env["DAEMON_PIDS"] = _bytes2sys(
1393 os.path.join(self._threadtmp, b'daemon.pids')
1394 os.path.join(self._threadtmp, b'daemon.pids')
1394 )
1395 )
1395 env["HGEDITOR"] = (
1396 env["HGEDITOR"] = (
1396 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1397 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1397 )
1398 )
1398 env["HGUSER"] = "test"
1399 env["HGUSER"] = "test"
1399 env["HGENCODING"] = "ascii"
1400 env["HGENCODING"] = "ascii"
1400 env["HGENCODINGMODE"] = "strict"
1401 env["HGENCODINGMODE"] = "strict"
1401 env["HGHOSTNAME"] = "test-hostname"
1402 env["HGHOSTNAME"] = "test-hostname"
1402 env['HGIPV6'] = str(int(self._useipv6))
1403 env['HGIPV6'] = str(int(self._useipv6))
1403 # See contrib/catapipe.py for how to use this functionality.
1404 # See contrib/catapipe.py for how to use this functionality.
1404 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1405 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1405 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1406 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1406 # non-test one in as a default, otherwise set to devnull
1407 # non-test one in as a default, otherwise set to devnull
1407 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1408 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1408 'HGCATAPULTSERVERPIPE', os.devnull
1409 'HGCATAPULTSERVERPIPE', os.devnull
1409 )
1410 )
1410
1411
1411 extraextensions = []
1412 extraextensions = []
1412 for opt in self._extraconfigopts:
1413 for opt in self._extraconfigopts:
1413 section, key = opt.split('.', 1)
1414 section, key = opt.split('.', 1)
1414 if section != 'extensions':
1415 if section != 'extensions':
1415 continue
1416 continue
1416 name = key.split('=', 1)[0]
1417 name = key.split('=', 1)[0]
1417 extraextensions.append(name)
1418 extraextensions.append(name)
1418
1419
1419 if extraextensions:
1420 if extraextensions:
1420 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1421 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1421
1422
1422 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1423 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1423 # IP addresses.
1424 # IP addresses.
1424 env['LOCALIP'] = _bytes2sys(self._localip())
1425 env['LOCALIP'] = _bytes2sys(self._localip())
1425
1426
1426 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1427 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1427 # but this is needed for testing python instances like dummyssh,
1428 # but this is needed for testing python instances like dummyssh,
1428 # dummysmtpd.py, and dumbhttp.py.
1429 # dummysmtpd.py, and dumbhttp.py.
1429 if PYTHON3 and os.name == 'nt':
1430 if PYTHON3 and os.name == 'nt':
1430 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1431 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1431
1432
1432 # Modified HOME in test environment can confuse Rust tools. So set
1433 # Modified HOME in test environment can confuse Rust tools. So set
1433 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1434 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1434 # present and these variables aren't already defined.
1435 # present and these variables aren't already defined.
1435 cargo_home_path = os.path.expanduser('~/.cargo')
1436 cargo_home_path = os.path.expanduser('~/.cargo')
1436 rustup_home_path = os.path.expanduser('~/.rustup')
1437 rustup_home_path = os.path.expanduser('~/.rustup')
1437
1438
1438 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1439 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1439 env['CARGO_HOME'] = cargo_home_path
1440 env['CARGO_HOME'] = cargo_home_path
1440 if (
1441 if (
1441 os.path.exists(rustup_home_path)
1442 os.path.exists(rustup_home_path)
1442 and b'RUSTUP_HOME' not in osenvironb
1443 and b'RUSTUP_HOME' not in osenvironb
1443 ):
1444 ):
1444 env['RUSTUP_HOME'] = rustup_home_path
1445 env['RUSTUP_HOME'] = rustup_home_path
1445
1446
1446 # Reset some environment variables to well-known values so that
1447 # Reset some environment variables to well-known values so that
1447 # the tests produce repeatable output.
1448 # the tests produce repeatable output.
1448 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1449 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1449 env['TZ'] = 'GMT'
1450 env['TZ'] = 'GMT'
1450 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1451 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1451 env['COLUMNS'] = '80'
1452 env['COLUMNS'] = '80'
1452 env['TERM'] = 'xterm'
1453 env['TERM'] = 'xterm'
1453
1454
1454 dropped = [
1455 dropped = [
1455 'CDPATH',
1456 'CDPATH',
1456 'CHGDEBUG',
1457 'CHGDEBUG',
1457 'EDITOR',
1458 'EDITOR',
1458 'GREP_OPTIONS',
1459 'GREP_OPTIONS',
1459 'HG',
1460 'HG',
1460 'HGMERGE',
1461 'HGMERGE',
1461 'HGPLAIN',
1462 'HGPLAIN',
1462 'HGPLAINEXCEPT',
1463 'HGPLAINEXCEPT',
1463 'HGPROF',
1464 'HGPROF',
1464 'http_proxy',
1465 'http_proxy',
1465 'no_proxy',
1466 'no_proxy',
1466 'NO_PROXY',
1467 'NO_PROXY',
1467 'PAGER',
1468 'PAGER',
1468 'VISUAL',
1469 'VISUAL',
1469 ]
1470 ]
1470
1471
1471 for k in dropped:
1472 for k in dropped:
1472 if k in env:
1473 if k in env:
1473 del env[k]
1474 del env[k]
1474
1475
1475 # unset env related to hooks
1476 # unset env related to hooks
1476 for k in list(env):
1477 for k in list(env):
1477 if k.startswith('HG_'):
1478 if k.startswith('HG_'):
1478 del env[k]
1479 del env[k]
1479
1480
1480 if self._usechg:
1481 if self._usechg:
1481 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1482 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1482 if self._chgdebug:
1483 if self._chgdebug:
1483 env['CHGDEBUG'] = 'true'
1484 env['CHGDEBUG'] = 'true'
1484
1485
1485 return env
1486 return env
1486
1487
1487 def _createhgrc(self, path):
1488 def _createhgrc(self, path):
1488 """Create an hgrc file for this test."""
1489 """Create an hgrc file for this test."""
1489 with open(path, 'wb') as hgrc:
1490 with open(path, 'wb') as hgrc:
1490 hgrc.write(b'[ui]\n')
1491 hgrc.write(b'[ui]\n')
1491 hgrc.write(b'slash = True\n')
1492 hgrc.write(b'slash = True\n')
1492 hgrc.write(b'interactive = False\n')
1493 hgrc.write(b'interactive = False\n')
1493 hgrc.write(b'detailed-exit-code = True\n')
1494 hgrc.write(b'detailed-exit-code = True\n')
1494 hgrc.write(b'merge = internal:merge\n')
1495 hgrc.write(b'merge = internal:merge\n')
1495 hgrc.write(b'mergemarkers = detailed\n')
1496 hgrc.write(b'mergemarkers = detailed\n')
1496 hgrc.write(b'promptecho = True\n')
1497 hgrc.write(b'promptecho = True\n')
1497 hgrc.write(b'timeout.warn=15\n')
1498 hgrc.write(b'timeout.warn=15\n')
1498 hgrc.write(b'[defaults]\n')
1499 hgrc.write(b'[defaults]\n')
1499 hgrc.write(b'[devel]\n')
1500 hgrc.write(b'[devel]\n')
1500 hgrc.write(b'all-warnings = true\n')
1501 hgrc.write(b'all-warnings = true\n')
1501 hgrc.write(b'default-date = 0 0\n')
1502 hgrc.write(b'default-date = 0 0\n')
1502 hgrc.write(b'[largefiles]\n')
1503 hgrc.write(b'[largefiles]\n')
1503 hgrc.write(
1504 hgrc.write(
1504 b'usercache = %s\n'
1505 b'usercache = %s\n'
1505 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1506 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1506 )
1507 )
1507 hgrc.write(b'[lfs]\n')
1508 hgrc.write(b'[lfs]\n')
1508 hgrc.write(
1509 hgrc.write(
1509 b'usercache = %s\n'
1510 b'usercache = %s\n'
1510 % (os.path.join(self._testtmp, b'.cache/lfs'))
1511 % (os.path.join(self._testtmp, b'.cache/lfs'))
1511 )
1512 )
1512 hgrc.write(b'[web]\n')
1513 hgrc.write(b'[web]\n')
1513 hgrc.write(b'address = localhost\n')
1514 hgrc.write(b'address = localhost\n')
1514 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1515 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1515 hgrc.write(b'server-header = testing stub value\n')
1516 hgrc.write(b'server-header = testing stub value\n')
1516
1517
1517 for opt in self._extraconfigopts:
1518 for opt in self._extraconfigopts:
1518 section, key = _sys2bytes(opt).split(b'.', 1)
1519 section, key = _sys2bytes(opt).split(b'.', 1)
1519 assert b'=' in key, (
1520 assert b'=' in key, (
1520 'extra config opt %s must ' 'have an = for assignment' % opt
1521 'extra config opt %s must ' 'have an = for assignment' % opt
1521 )
1522 )
1522 hgrc.write(b'[%s]\n%s\n' % (section, key))
1523 hgrc.write(b'[%s]\n%s\n' % (section, key))
1523
1524
1524 def fail(self, msg):
1525 def fail(self, msg):
1525 # unittest differentiates between errored and failed.
1526 # unittest differentiates between errored and failed.
1526 # Failed is denoted by AssertionError (by default at least).
1527 # Failed is denoted by AssertionError (by default at least).
1527 raise AssertionError(msg)
1528 raise AssertionError(msg)
1528
1529
1529 def _runcommand(self, cmd, env, normalizenewlines=False):
1530 def _runcommand(self, cmd, env, normalizenewlines=False):
1530 """Run command in a sub-process, capturing the output (stdout and
1531 """Run command in a sub-process, capturing the output (stdout and
1531 stderr).
1532 stderr).
1532
1533
1533 Return a tuple (exitcode, output). output is None in debug mode.
1534 Return a tuple (exitcode, output). output is None in debug mode.
1534 """
1535 """
1535 if self._debug:
1536 if self._debug:
1536 proc = subprocess.Popen(
1537 proc = subprocess.Popen(
1537 _bytes2sys(cmd),
1538 _bytes2sys(cmd),
1538 shell=True,
1539 shell=True,
1539 cwd=_bytes2sys(self._testtmp),
1540 cwd=_bytes2sys(self._testtmp),
1540 env=env,
1541 env=env,
1541 )
1542 )
1542 ret = proc.wait()
1543 ret = proc.wait()
1543 return (ret, None)
1544 return (ret, None)
1544
1545
1545 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1546 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1546
1547
1547 def cleanup():
1548 def cleanup():
1548 terminate(proc)
1549 terminate(proc)
1549 ret = proc.wait()
1550 ret = proc.wait()
1550 if ret == 0:
1551 if ret == 0:
1551 ret = signal.SIGTERM << 8
1552 ret = signal.SIGTERM << 8
1552 killdaemons(env['DAEMON_PIDS'])
1553 killdaemons(env['DAEMON_PIDS'])
1553 return ret
1554 return ret
1554
1555
1555 proc.tochild.close()
1556 proc.tochild.close()
1556
1557
1557 try:
1558 try:
1558 output = proc.fromchild.read()
1559 output = proc.fromchild.read()
1559 except KeyboardInterrupt:
1560 except KeyboardInterrupt:
1560 vlog('# Handling keyboard interrupt')
1561 vlog('# Handling keyboard interrupt')
1561 cleanup()
1562 cleanup()
1562 raise
1563 raise
1563
1564
1564 ret = proc.wait()
1565 ret = proc.wait()
1565 if wifexited(ret):
1566 if wifexited(ret):
1566 ret = os.WEXITSTATUS(ret)
1567 ret = os.WEXITSTATUS(ret)
1567
1568
1568 if proc.timeout:
1569 if proc.timeout:
1569 ret = 'timeout'
1570 ret = 'timeout'
1570
1571
1571 if ret:
1572 if ret:
1572 killdaemons(env['DAEMON_PIDS'])
1573 killdaemons(env['DAEMON_PIDS'])
1573
1574
1574 for s, r in self._getreplacements():
1575 for s, r in self._getreplacements():
1575 output = re.sub(s, r, output)
1576 output = re.sub(s, r, output)
1576
1577
1577 if normalizenewlines:
1578 if normalizenewlines:
1578 output = output.replace(b'\r\n', b'\n')
1579 output = output.replace(b'\r\n', b'\n')
1579
1580
1580 return ret, output.splitlines(True)
1581 return ret, output.splitlines(True)
1581
1582
1582
1583
1583 class PythonTest(Test):
1584 class PythonTest(Test):
1584 """A Python-based test."""
1585 """A Python-based test."""
1585
1586
1586 @property
1587 @property
1587 def refpath(self):
1588 def refpath(self):
1588 return os.path.join(self._testdir, b'%s.out' % self.bname)
1589 return os.path.join(self._testdir, b'%s.out' % self.bname)
1589
1590
1590 def _run(self, env):
1591 def _run(self, env):
1591 # Quote the python(3) executable for Windows
1592 # Quote the python(3) executable for Windows
1592 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1593 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1593 vlog("# Running", cmd.decode("utf-8"))
1594 vlog("# Running", cmd.decode("utf-8"))
1594 normalizenewlines = os.name == 'nt'
1595 normalizenewlines = os.name == 'nt'
1595 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1596 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1596 if self._aborted:
1597 if self._aborted:
1597 raise KeyboardInterrupt()
1598 raise KeyboardInterrupt()
1598
1599
1599 return result
1600 return result
1600
1601
1601
1602
1602 # Some glob patterns apply only in some circumstances, so the script
1603 # Some glob patterns apply only in some circumstances, so the script
1603 # might want to remove (glob) annotations that otherwise should be
1604 # might want to remove (glob) annotations that otherwise should be
1604 # retained.
1605 # retained.
1605 checkcodeglobpats = [
1606 checkcodeglobpats = [
1606 # On Windows it looks like \ doesn't require a (glob), but we know
1607 # On Windows it looks like \ doesn't require a (glob), but we know
1607 # better.
1608 # better.
1608 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1609 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1609 re.compile(br'^moving \S+/.*[^)]$'),
1610 re.compile(br'^moving \S+/.*[^)]$'),
1610 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1611 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1611 # Not all platforms have 127.0.0.1 as loopback (though most do),
1612 # Not all platforms have 127.0.0.1 as loopback (though most do),
1612 # so we always glob that too.
1613 # so we always glob that too.
1613 re.compile(br'.*\$LOCALIP.*$'),
1614 re.compile(br'.*\$LOCALIP.*$'),
1614 ]
1615 ]
1615
1616
1616 bchr = chr
1617 bchr = chr
1617 if PYTHON3:
1618 if PYTHON3:
1618 bchr = lambda x: bytes([x])
1619 bchr = lambda x: bytes([x])
1619
1620
1620 WARN_UNDEFINED = 1
1621 WARN_UNDEFINED = 1
1621 WARN_YES = 2
1622 WARN_YES = 2
1622 WARN_NO = 3
1623 WARN_NO = 3
1623
1624
1624 MARK_OPTIONAL = b" (?)\n"
1625 MARK_OPTIONAL = b" (?)\n"
1625
1626
1626
1627
1627 def isoptional(line):
1628 def isoptional(line):
1628 return line.endswith(MARK_OPTIONAL)
1629 return line.endswith(MARK_OPTIONAL)
1629
1630
1630
1631
1631 class TTest(Test):
1632 class TTest(Test):
1632 """A "t test" is a test backed by a .t file."""
1633 """A "t test" is a test backed by a .t file."""
1633
1634
1634 SKIPPED_PREFIX = b'skipped: '
1635 SKIPPED_PREFIX = b'skipped: '
1635 FAILED_PREFIX = b'hghave check failed: '
1636 FAILED_PREFIX = b'hghave check failed: '
1636 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1637 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1637
1638
1638 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1639 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1639 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1640 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1640 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1641 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1641
1642
1642 def __init__(self, path, *args, **kwds):
1643 def __init__(self, path, *args, **kwds):
1643 # accept an extra "case" parameter
1644 # accept an extra "case" parameter
1644 case = kwds.pop('case', [])
1645 case = kwds.pop('case', [])
1645 self._case = case
1646 self._case = case
1646 self._allcases = {x for y in parsettestcases(path) for x in y}
1647 self._allcases = {x for y in parsettestcases(path) for x in y}
1647 super(TTest, self).__init__(path, *args, **kwds)
1648 super(TTest, self).__init__(path, *args, **kwds)
1648 if case:
1649 if case:
1649 casepath = b'#'.join(case)
1650 casepath = b'#'.join(case)
1650 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1651 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1651 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1652 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1652 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1653 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1653 self._have = {}
1654 self._have = {}
1654
1655
1655 @property
1656 @property
1656 def refpath(self):
1657 def refpath(self):
1657 return os.path.join(self._testdir, self.bname)
1658 return os.path.join(self._testdir, self.bname)
1658
1659
1659 def _run(self, env):
1660 def _run(self, env):
1660 with open(self.path, 'rb') as f:
1661 with open(self.path, 'rb') as f:
1661 lines = f.readlines()
1662 lines = f.readlines()
1662
1663
1663 # .t file is both reference output and the test input, keep reference
1664 # .t file is both reference output and the test input, keep reference
1664 # output updated with the the test input. This avoids some race
1665 # output updated with the the test input. This avoids some race
1665 # conditions where the reference output does not match the actual test.
1666 # conditions where the reference output does not match the actual test.
1666 if self._refout is not None:
1667 if self._refout is not None:
1667 self._refout = lines
1668 self._refout = lines
1668
1669
1669 salt, script, after, expected = self._parsetest(lines)
1670 salt, script, after, expected = self._parsetest(lines)
1670
1671
1671 # Write out the generated script.
1672 # Write out the generated script.
1672 fname = b'%s.sh' % self._testtmp
1673 fname = b'%s.sh' % self._testtmp
1673 with open(fname, 'wb') as f:
1674 with open(fname, 'wb') as f:
1674 for l in script:
1675 for l in script:
1675 f.write(l)
1676 f.write(l)
1676
1677
1677 cmd = b'%s "%s"' % (self._shell, fname)
1678 cmd = b'%s "%s"' % (self._shell, fname)
1678 vlog("# Running", cmd.decode("utf-8"))
1679 vlog("# Running", cmd.decode("utf-8"))
1679
1680
1680 exitcode, output = self._runcommand(cmd, env)
1681 exitcode, output = self._runcommand(cmd, env)
1681
1682
1682 if self._aborted:
1683 if self._aborted:
1683 raise KeyboardInterrupt()
1684 raise KeyboardInterrupt()
1684
1685
1685 # Do not merge output if skipped. Return hghave message instead.
1686 # Do not merge output if skipped. Return hghave message instead.
1686 # Similarly, with --debug, output is None.
1687 # Similarly, with --debug, output is None.
1687 if exitcode == self.SKIPPED_STATUS or output is None:
1688 if exitcode == self.SKIPPED_STATUS or output is None:
1688 return exitcode, output
1689 return exitcode, output
1689
1690
1690 return self._processoutput(exitcode, output, salt, after, expected)
1691 return self._processoutput(exitcode, output, salt, after, expected)
1691
1692
1692 def _hghave(self, reqs):
1693 def _hghave(self, reqs):
1693 allreqs = b' '.join(reqs)
1694 allreqs = b' '.join(reqs)
1694
1695
1695 self._detectslow(reqs)
1696 self._detectslow(reqs)
1696
1697
1697 if allreqs in self._have:
1698 if allreqs in self._have:
1698 return self._have.get(allreqs)
1699 return self._have.get(allreqs)
1699
1700
1700 # TODO do something smarter when all other uses of hghave are gone.
1701 # TODO do something smarter when all other uses of hghave are gone.
1701 runtestdir = osenvironb[b'RUNTESTDIR']
1702 runtestdir = osenvironb[b'RUNTESTDIR']
1702 tdir = runtestdir.replace(b'\\', b'/')
1703 tdir = runtestdir.replace(b'\\', b'/')
1703 proc = Popen4(
1704 proc = Popen4(
1704 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1705 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1705 self._testtmp,
1706 self._testtmp,
1706 0,
1707 0,
1707 self._getenv(),
1708 self._getenv(),
1708 )
1709 )
1709 stdout, stderr = proc.communicate()
1710 stdout, stderr = proc.communicate()
1710 ret = proc.wait()
1711 ret = proc.wait()
1711 if wifexited(ret):
1712 if wifexited(ret):
1712 ret = os.WEXITSTATUS(ret)
1713 ret = os.WEXITSTATUS(ret)
1713 if ret == 2:
1714 if ret == 2:
1714 print(stdout.decode('utf-8'))
1715 print(stdout.decode('utf-8'))
1715 sys.exit(1)
1716 sys.exit(1)
1716
1717
1717 if ret != 0:
1718 if ret != 0:
1718 self._have[allreqs] = (False, stdout)
1719 self._have[allreqs] = (False, stdout)
1719 return False, stdout
1720 return False, stdout
1720
1721
1721 self._have[allreqs] = (True, None)
1722 self._have[allreqs] = (True, None)
1722 return True, None
1723 return True, None
1723
1724
1724 def _detectslow(self, reqs):
1725 def _detectslow(self, reqs):
1725 """update the timeout of slow test when appropriate"""
1726 """update the timeout of slow test when appropriate"""
1726 if b'slow' in reqs:
1727 if b'slow' in reqs:
1727 self._timeout = self._slowtimeout
1728 self._timeout = self._slowtimeout
1728
1729
1729 def _iftest(self, args):
1730 def _iftest(self, args):
1730 # implements "#if"
1731 # implements "#if"
1731 reqs = []
1732 reqs = []
1732 for arg in args:
1733 for arg in args:
1733 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1734 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1734 if arg[3:] in self._case:
1735 if arg[3:] in self._case:
1735 return False
1736 return False
1736 elif arg in self._allcases:
1737 elif arg in self._allcases:
1737 if arg not in self._case:
1738 if arg not in self._case:
1738 return False
1739 return False
1739 else:
1740 else:
1740 reqs.append(arg)
1741 reqs.append(arg)
1741 self._detectslow(reqs)
1742 self._detectslow(reqs)
1742 return self._hghave(reqs)[0]
1743 return self._hghave(reqs)[0]
1743
1744
1744 def _parsetest(self, lines):
1745 def _parsetest(self, lines):
1745 # We generate a shell script which outputs unique markers to line
1746 # We generate a shell script which outputs unique markers to line
1746 # up script results with our source. These markers include input
1747 # up script results with our source. These markers include input
1747 # line number and the last return code.
1748 # line number and the last return code.
1748 salt = b"SALT%d" % time.time()
1749 salt = b"SALT%d" % time.time()
1749
1750
1750 def addsalt(line, inpython):
1751 def addsalt(line, inpython):
1751 if inpython:
1752 if inpython:
1752 script.append(b'%s %d 0\n' % (salt, line))
1753 script.append(b'%s %d 0\n' % (salt, line))
1753 else:
1754 else:
1754 script.append(b'echo %s %d $?\n' % (salt, line))
1755 script.append(b'echo %s %d $?\n' % (salt, line))
1755
1756
1756 activetrace = []
1757 activetrace = []
1757 session = str(uuid.uuid4())
1758 session = str(uuid.uuid4())
1758 if PYTHON3:
1759 if PYTHON3:
1759 session = session.encode('ascii')
1760 session = session.encode('ascii')
1760 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1761 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1761 'HGCATAPULTSERVERPIPE'
1762 'HGCATAPULTSERVERPIPE'
1762 )
1763 )
1763
1764
1764 def toggletrace(cmd=None):
1765 def toggletrace(cmd=None):
1765 if not hgcatapult or hgcatapult == os.devnull:
1766 if not hgcatapult or hgcatapult == os.devnull:
1766 return
1767 return
1767
1768
1768 if activetrace:
1769 if activetrace:
1769 script.append(
1770 script.append(
1770 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1771 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1771 % (session, activetrace[0])
1772 % (session, activetrace[0])
1772 )
1773 )
1773 if cmd is None:
1774 if cmd is None:
1774 return
1775 return
1775
1776
1776 if isinstance(cmd, str):
1777 if isinstance(cmd, str):
1777 quoted = shellquote(cmd.strip())
1778 quoted = shellquote(cmd.strip())
1778 else:
1779 else:
1779 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1780 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1780 quoted = quoted.replace(b'\\', b'\\\\')
1781 quoted = quoted.replace(b'\\', b'\\\\')
1781 script.append(
1782 script.append(
1782 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1783 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1783 % (session, quoted)
1784 % (session, quoted)
1784 )
1785 )
1785 activetrace[0:] = [quoted]
1786 activetrace[0:] = [quoted]
1786
1787
1787 script = []
1788 script = []
1788
1789
1789 # After we run the shell script, we re-unify the script output
1790 # After we run the shell script, we re-unify the script output
1790 # with non-active parts of the source, with synchronization by our
1791 # with non-active parts of the source, with synchronization by our
1791 # SALT line number markers. The after table contains the non-active
1792 # SALT line number markers. The after table contains the non-active
1792 # components, ordered by line number.
1793 # components, ordered by line number.
1793 after = {}
1794 after = {}
1794
1795
1795 # Expected shell script output.
1796 # Expected shell script output.
1796 expected = {}
1797 expected = {}
1797
1798
1798 pos = prepos = -1
1799 pos = prepos = -1
1799
1800
1800 # True or False when in a true or false conditional section
1801 # True or False when in a true or false conditional section
1801 skipping = None
1802 skipping = None
1802
1803
1803 # We keep track of whether or not we're in a Python block so we
1804 # We keep track of whether or not we're in a Python block so we
1804 # can generate the surrounding doctest magic.
1805 # can generate the surrounding doctest magic.
1805 inpython = False
1806 inpython = False
1806
1807
1807 if self._debug:
1808 if self._debug:
1808 script.append(b'set -x\n')
1809 script.append(b'set -x\n')
1809 if self._hgcommand != b'hg':
1810 if self._hgcommand != b'hg':
1810 script.append(b'alias hg="%s"\n' % self._hgcommand)
1811 script.append(b'alias hg="%s"\n' % self._hgcommand)
1811 if os.getenv('MSYSTEM'):
1812 if os.getenv('MSYSTEM'):
1812 script.append(b'alias pwd="pwd -W"\n')
1813 script.append(b'alias pwd="pwd -W"\n')
1813
1814
1814 if hgcatapult and hgcatapult != os.devnull:
1815 if hgcatapult and hgcatapult != os.devnull:
1815 if PYTHON3:
1816 if PYTHON3:
1816 hgcatapult = hgcatapult.encode('utf8')
1817 hgcatapult = hgcatapult.encode('utf8')
1817 cataname = self.name.encode('utf8')
1818 cataname = self.name.encode('utf8')
1818 else:
1819 else:
1819 cataname = self.name
1820 cataname = self.name
1820
1821
1821 # Kludge: use a while loop to keep the pipe from getting
1822 # Kludge: use a while loop to keep the pipe from getting
1822 # closed by our echo commands. The still-running file gets
1823 # closed by our echo commands. The still-running file gets
1823 # reaped at the end of the script, which causes the while
1824 # reaped at the end of the script, which causes the while
1824 # loop to exit and closes the pipe. Sigh.
1825 # loop to exit and closes the pipe. Sigh.
1825 script.append(
1826 script.append(
1826 b'rtendtracing() {\n'
1827 b'rtendtracing() {\n'
1827 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1828 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1828 b' rm -f "$TESTTMP/.still-running"\n'
1829 b' rm -f "$TESTTMP/.still-running"\n'
1829 b'}\n'
1830 b'}\n'
1830 b'trap "rtendtracing" 0\n'
1831 b'trap "rtendtracing" 0\n'
1831 b'touch "$TESTTMP/.still-running"\n'
1832 b'touch "$TESTTMP/.still-running"\n'
1832 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1833 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1833 b'> %(catapult)s &\n'
1834 b'> %(catapult)s &\n'
1834 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1835 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1835 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1836 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1836 % {
1837 % {
1837 b'name': cataname,
1838 b'name': cataname,
1838 b'session': session,
1839 b'session': session,
1839 b'catapult': hgcatapult,
1840 b'catapult': hgcatapult,
1840 }
1841 }
1841 )
1842 )
1842
1843
1843 if self._case:
1844 if self._case:
1844 casestr = b'#'.join(self._case)
1845 casestr = b'#'.join(self._case)
1845 if isinstance(casestr, str):
1846 if isinstance(casestr, str):
1846 quoted = shellquote(casestr)
1847 quoted = shellquote(casestr)
1847 else:
1848 else:
1848 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1849 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1849 script.append(b'TESTCASE=%s\n' % quoted)
1850 script.append(b'TESTCASE=%s\n' % quoted)
1850 script.append(b'export TESTCASE\n')
1851 script.append(b'export TESTCASE\n')
1851
1852
1852 n = 0
1853 n = 0
1853 for n, l in enumerate(lines):
1854 for n, l in enumerate(lines):
1854 if not l.endswith(b'\n'):
1855 if not l.endswith(b'\n'):
1855 l += b'\n'
1856 l += b'\n'
1856 if l.startswith(b'#require'):
1857 if l.startswith(b'#require'):
1857 lsplit = l.split()
1858 lsplit = l.split()
1858 if len(lsplit) < 2 or lsplit[0] != b'#require':
1859 if len(lsplit) < 2 or lsplit[0] != b'#require':
1859 after.setdefault(pos, []).append(
1860 after.setdefault(pos, []).append(
1860 b' !!! invalid #require\n'
1861 b' !!! invalid #require\n'
1861 )
1862 )
1862 if not skipping:
1863 if not skipping:
1863 haveresult, message = self._hghave(lsplit[1:])
1864 haveresult, message = self._hghave(lsplit[1:])
1864 if not haveresult:
1865 if not haveresult:
1865 script = [b'echo "%s"\nexit 80\n' % message]
1866 script = [b'echo "%s"\nexit 80\n' % message]
1866 break
1867 break
1867 after.setdefault(pos, []).append(l)
1868 after.setdefault(pos, []).append(l)
1868 elif l.startswith(b'#if'):
1869 elif l.startswith(b'#if'):
1869 lsplit = l.split()
1870 lsplit = l.split()
1870 if len(lsplit) < 2 or lsplit[0] != b'#if':
1871 if len(lsplit) < 2 or lsplit[0] != b'#if':
1871 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1872 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1872 if skipping is not None:
1873 if skipping is not None:
1873 after.setdefault(pos, []).append(b' !!! nested #if\n')
1874 after.setdefault(pos, []).append(b' !!! nested #if\n')
1874 skipping = not self._iftest(lsplit[1:])
1875 skipping = not self._iftest(lsplit[1:])
1875 after.setdefault(pos, []).append(l)
1876 after.setdefault(pos, []).append(l)
1876 elif l.startswith(b'#else'):
1877 elif l.startswith(b'#else'):
1877 if skipping is None:
1878 if skipping is None:
1878 after.setdefault(pos, []).append(b' !!! missing #if\n')
1879 after.setdefault(pos, []).append(b' !!! missing #if\n')
1879 skipping = not skipping
1880 skipping = not skipping
1880 after.setdefault(pos, []).append(l)
1881 after.setdefault(pos, []).append(l)
1881 elif l.startswith(b'#endif'):
1882 elif l.startswith(b'#endif'):
1882 if skipping is None:
1883 if skipping is None:
1883 after.setdefault(pos, []).append(b' !!! missing #if\n')
1884 after.setdefault(pos, []).append(b' !!! missing #if\n')
1884 skipping = None
1885 skipping = None
1885 after.setdefault(pos, []).append(l)
1886 after.setdefault(pos, []).append(l)
1886 elif skipping:
1887 elif skipping:
1887 after.setdefault(pos, []).append(l)
1888 after.setdefault(pos, []).append(l)
1888 elif l.startswith(b' >>> '): # python inlines
1889 elif l.startswith(b' >>> '): # python inlines
1889 after.setdefault(pos, []).append(l)
1890 after.setdefault(pos, []).append(l)
1890 prepos = pos
1891 prepos = pos
1891 pos = n
1892 pos = n
1892 if not inpython:
1893 if not inpython:
1893 # We've just entered a Python block. Add the header.
1894 # We've just entered a Python block. Add the header.
1894 inpython = True
1895 inpython = True
1895 addsalt(prepos, False) # Make sure we report the exit code.
1896 addsalt(prepos, False) # Make sure we report the exit code.
1896 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1897 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1897 addsalt(n, True)
1898 addsalt(n, True)
1898 script.append(l[2:])
1899 script.append(l[2:])
1899 elif l.startswith(b' ... '): # python inlines
1900 elif l.startswith(b' ... '): # python inlines
1900 after.setdefault(prepos, []).append(l)
1901 after.setdefault(prepos, []).append(l)
1901 script.append(l[2:])
1902 script.append(l[2:])
1902 elif l.startswith(b' $ '): # commands
1903 elif l.startswith(b' $ '): # commands
1903 if inpython:
1904 if inpython:
1904 script.append(b'EOF\n')
1905 script.append(b'EOF\n')
1905 inpython = False
1906 inpython = False
1906 after.setdefault(pos, []).append(l)
1907 after.setdefault(pos, []).append(l)
1907 prepos = pos
1908 prepos = pos
1908 pos = n
1909 pos = n
1909 addsalt(n, False)
1910 addsalt(n, False)
1910 rawcmd = l[4:]
1911 rawcmd = l[4:]
1911 cmd = rawcmd.split()
1912 cmd = rawcmd.split()
1912 toggletrace(rawcmd)
1913 toggletrace(rawcmd)
1913 if len(cmd) == 2 and cmd[0] == b'cd':
1914 if len(cmd) == 2 and cmd[0] == b'cd':
1914 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1915 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1915 script.append(rawcmd)
1916 script.append(rawcmd)
1916 elif l.startswith(b' > '): # continuations
1917 elif l.startswith(b' > '): # continuations
1917 after.setdefault(prepos, []).append(l)
1918 after.setdefault(prepos, []).append(l)
1918 script.append(l[4:])
1919 script.append(l[4:])
1919 elif l.startswith(b' '): # results
1920 elif l.startswith(b' '): # results
1920 # Queue up a list of expected results.
1921 # Queue up a list of expected results.
1921 expected.setdefault(pos, []).append(l[2:])
1922 expected.setdefault(pos, []).append(l[2:])
1922 else:
1923 else:
1923 if inpython:
1924 if inpython:
1924 script.append(b'EOF\n')
1925 script.append(b'EOF\n')
1925 inpython = False
1926 inpython = False
1926 # Non-command/result. Queue up for merged output.
1927 # Non-command/result. Queue up for merged output.
1927 after.setdefault(pos, []).append(l)
1928 after.setdefault(pos, []).append(l)
1928
1929
1929 if inpython:
1930 if inpython:
1930 script.append(b'EOF\n')
1931 script.append(b'EOF\n')
1931 if skipping is not None:
1932 if skipping is not None:
1932 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1933 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1933 addsalt(n + 1, False)
1934 addsalt(n + 1, False)
1934 # Need to end any current per-command trace
1935 # Need to end any current per-command trace
1935 if activetrace:
1936 if activetrace:
1936 toggletrace()
1937 toggletrace()
1937 return salt, script, after, expected
1938 return salt, script, after, expected
1938
1939
1939 def _processoutput(self, exitcode, output, salt, after, expected):
1940 def _processoutput(self, exitcode, output, salt, after, expected):
1940 # Merge the script output back into a unified test.
1941 # Merge the script output back into a unified test.
1941 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1942 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1942 if exitcode != 0:
1943 if exitcode != 0:
1943 warnonly = WARN_NO
1944 warnonly = WARN_NO
1944
1945
1945 pos = -1
1946 pos = -1
1946 postout = []
1947 postout = []
1947 for out_rawline in output:
1948 for out_rawline in output:
1948 out_line, cmd_line = out_rawline, None
1949 out_line, cmd_line = out_rawline, None
1949 if salt in out_rawline:
1950 if salt in out_rawline:
1950 out_line, cmd_line = out_rawline.split(salt, 1)
1951 out_line, cmd_line = out_rawline.split(salt, 1)
1951
1952
1952 pos, postout, warnonly = self._process_out_line(
1953 pos, postout, warnonly = self._process_out_line(
1953 out_line, pos, postout, expected, warnonly
1954 out_line, pos, postout, expected, warnonly
1954 )
1955 )
1955 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1956 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1956
1957
1957 if pos in after:
1958 if pos in after:
1958 postout += after.pop(pos)
1959 postout += after.pop(pos)
1959
1960
1960 if warnonly == WARN_YES:
1961 if warnonly == WARN_YES:
1961 exitcode = False # Set exitcode to warned.
1962 exitcode = False # Set exitcode to warned.
1962
1963
1963 return exitcode, postout
1964 return exitcode, postout
1964
1965
1965 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1966 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1966 while out_line:
1967 while out_line:
1967 if not out_line.endswith(b'\n'):
1968 if not out_line.endswith(b'\n'):
1968 out_line += b' (no-eol)\n'
1969 out_line += b' (no-eol)\n'
1969
1970
1970 # Find the expected output at the current position.
1971 # Find the expected output at the current position.
1971 els = [None]
1972 els = [None]
1972 if expected.get(pos, None):
1973 if expected.get(pos, None):
1973 els = expected[pos]
1974 els = expected[pos]
1974
1975
1975 optional = []
1976 optional = []
1976 for i, el in enumerate(els):
1977 for i, el in enumerate(els):
1977 r = False
1978 r = False
1978 if el:
1979 if el:
1979 r, exact = self.linematch(el, out_line)
1980 r, exact = self.linematch(el, out_line)
1980 if isinstance(r, str):
1981 if isinstance(r, str):
1981 if r == '-glob':
1982 if r == '-glob':
1982 out_line = ''.join(el.rsplit(' (glob)', 1))
1983 out_line = ''.join(el.rsplit(' (glob)', 1))
1983 r = '' # Warn only this line.
1984 r = '' # Warn only this line.
1984 elif r == "retry":
1985 elif r == "retry":
1985 postout.append(b' ' + el)
1986 postout.append(b' ' + el)
1986 else:
1987 else:
1987 log('\ninfo, unknown linematch result: %r\n' % r)
1988 log('\ninfo, unknown linematch result: %r\n' % r)
1988 r = False
1989 r = False
1989 if r:
1990 if r:
1990 els.pop(i)
1991 els.pop(i)
1991 break
1992 break
1992 if el:
1993 if el:
1993 if isoptional(el):
1994 if isoptional(el):
1994 optional.append(i)
1995 optional.append(i)
1995 else:
1996 else:
1996 m = optline.match(el)
1997 m = optline.match(el)
1997 if m:
1998 if m:
1998 conditions = [c for c in m.group(2).split(b' ')]
1999 conditions = [c for c in m.group(2).split(b' ')]
1999
2000
2000 if not self._iftest(conditions):
2001 if not self._iftest(conditions):
2001 optional.append(i)
2002 optional.append(i)
2002 if exact:
2003 if exact:
2003 # Don't allow line to be matches against a later
2004 # Don't allow line to be matches against a later
2004 # line in the output
2005 # line in the output
2005 els.pop(i)
2006 els.pop(i)
2006 break
2007 break
2007
2008
2008 if r:
2009 if r:
2009 if r == "retry":
2010 if r == "retry":
2010 continue
2011 continue
2011 # clean up any optional leftovers
2012 # clean up any optional leftovers
2012 for i in optional:
2013 for i in optional:
2013 postout.append(b' ' + els[i])
2014 postout.append(b' ' + els[i])
2014 for i in reversed(optional):
2015 for i in reversed(optional):
2015 del els[i]
2016 del els[i]
2016 postout.append(b' ' + el)
2017 postout.append(b' ' + el)
2017 else:
2018 else:
2018 if self.NEEDESCAPE(out_line):
2019 if self.NEEDESCAPE(out_line):
2019 out_line = TTest._stringescape(
2020 out_line = TTest._stringescape(
2020 b'%s (esc)\n' % out_line.rstrip(b'\n')
2021 b'%s (esc)\n' % out_line.rstrip(b'\n')
2021 )
2022 )
2022 postout.append(b' ' + out_line) # Let diff deal with it.
2023 postout.append(b' ' + out_line) # Let diff deal with it.
2023 if r != '': # If line failed.
2024 if r != '': # If line failed.
2024 warnonly = WARN_NO
2025 warnonly = WARN_NO
2025 elif warnonly == WARN_UNDEFINED:
2026 elif warnonly == WARN_UNDEFINED:
2026 warnonly = WARN_YES
2027 warnonly = WARN_YES
2027 break
2028 break
2028 else:
2029 else:
2029 # clean up any optional leftovers
2030 # clean up any optional leftovers
2030 while expected.get(pos, None):
2031 while expected.get(pos, None):
2031 el = expected[pos].pop(0)
2032 el = expected[pos].pop(0)
2032 if el:
2033 if el:
2033 if not isoptional(el):
2034 if not isoptional(el):
2034 m = optline.match(el)
2035 m = optline.match(el)
2035 if m:
2036 if m:
2036 conditions = [c for c in m.group(2).split(b' ')]
2037 conditions = [c for c in m.group(2).split(b' ')]
2037
2038
2038 if self._iftest(conditions):
2039 if self._iftest(conditions):
2039 # Don't append as optional line
2040 # Don't append as optional line
2040 continue
2041 continue
2041 else:
2042 else:
2042 continue
2043 continue
2043 postout.append(b' ' + el)
2044 postout.append(b' ' + el)
2044 return pos, postout, warnonly
2045 return pos, postout, warnonly
2045
2046
2046 def _process_cmd_line(self, cmd_line, pos, postout, after):
2047 def _process_cmd_line(self, cmd_line, pos, postout, after):
2047 """process a "command" part of a line from unified test output"""
2048 """process a "command" part of a line from unified test output"""
2048 if cmd_line:
2049 if cmd_line:
2049 # Add on last return code.
2050 # Add on last return code.
2050 ret = int(cmd_line.split()[1])
2051 ret = int(cmd_line.split()[1])
2051 if ret != 0:
2052 if ret != 0:
2052 postout.append(b' [%d]\n' % ret)
2053 postout.append(b' [%d]\n' % ret)
2053 if pos in after:
2054 if pos in after:
2054 # Merge in non-active test bits.
2055 # Merge in non-active test bits.
2055 postout += after.pop(pos)
2056 postout += after.pop(pos)
2056 pos = int(cmd_line.split()[0])
2057 pos = int(cmd_line.split()[0])
2057 return pos, postout
2058 return pos, postout
2058
2059
2059 @staticmethod
2060 @staticmethod
2060 def rematch(el, l):
2061 def rematch(el, l):
2061 try:
2062 try:
2062 # parse any flags at the beginning of the regex. Only 'i' is
2063 # parse any flags at the beginning of the regex. Only 'i' is
2063 # supported right now, but this should be easy to extend.
2064 # supported right now, but this should be easy to extend.
2064 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2065 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2065 flags = flags or b''
2066 flags = flags or b''
2066 el = flags + b'(?:' + el + b')'
2067 el = flags + b'(?:' + el + b')'
2067 # use \Z to ensure that the regex matches to the end of the string
2068 # use \Z to ensure that the regex matches to the end of the string
2068 if os.name == 'nt':
2069 if os.name == 'nt':
2069 return re.match(el + br'\r?\n\Z', l)
2070 return re.match(el + br'\r?\n\Z', l)
2070 return re.match(el + br'\n\Z', l)
2071 return re.match(el + br'\n\Z', l)
2071 except re.error:
2072 except re.error:
2072 # el is an invalid regex
2073 # el is an invalid regex
2073 return False
2074 return False
2074
2075
2075 @staticmethod
2076 @staticmethod
2076 def globmatch(el, l):
2077 def globmatch(el, l):
2077 # The only supported special characters are * and ? plus / which also
2078 # The only supported special characters are * and ? plus / which also
2078 # matches \ on windows. Escaping of these characters is supported.
2079 # matches \ on windows. Escaping of these characters is supported.
2079 if el + b'\n' == l:
2080 if el + b'\n' == l:
2080 if os.altsep:
2081 if os.altsep:
2081 # matching on "/" is not needed for this line
2082 # matching on "/" is not needed for this line
2082 for pat in checkcodeglobpats:
2083 for pat in checkcodeglobpats:
2083 if pat.match(el):
2084 if pat.match(el):
2084 return True
2085 return True
2085 return b'-glob'
2086 return b'-glob'
2086 return True
2087 return True
2087 el = el.replace(b'$LOCALIP', b'*')
2088 el = el.replace(b'$LOCALIP', b'*')
2088 i, n = 0, len(el)
2089 i, n = 0, len(el)
2089 res = b''
2090 res = b''
2090 while i < n:
2091 while i < n:
2091 c = el[i : i + 1]
2092 c = el[i : i + 1]
2092 i += 1
2093 i += 1
2093 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2094 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2094 res += el[i - 1 : i + 1]
2095 res += el[i - 1 : i + 1]
2095 i += 1
2096 i += 1
2096 elif c == b'*':
2097 elif c == b'*':
2097 res += b'.*'
2098 res += b'.*'
2098 elif c == b'?':
2099 elif c == b'?':
2099 res += b'.'
2100 res += b'.'
2100 elif c == b'/' and os.altsep:
2101 elif c == b'/' and os.altsep:
2101 res += b'[/\\\\]'
2102 res += b'[/\\\\]'
2102 else:
2103 else:
2103 res += re.escape(c)
2104 res += re.escape(c)
2104 return TTest.rematch(res, l)
2105 return TTest.rematch(res, l)
2105
2106
2106 def linematch(self, el, l):
2107 def linematch(self, el, l):
2107 if el == l: # perfect match (fast)
2108 if el == l: # perfect match (fast)
2108 return True, True
2109 return True, True
2109 retry = False
2110 retry = False
2110 if isoptional(el):
2111 if isoptional(el):
2111 retry = "retry"
2112 retry = "retry"
2112 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2113 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2113 else:
2114 else:
2114 m = optline.match(el)
2115 m = optline.match(el)
2115 if m:
2116 if m:
2116 conditions = [c for c in m.group(2).split(b' ')]
2117 conditions = [c for c in m.group(2).split(b' ')]
2117
2118
2118 el = m.group(1) + b"\n"
2119 el = m.group(1) + b"\n"
2119 if not self._iftest(conditions):
2120 if not self._iftest(conditions):
2120 # listed feature missing, should not match
2121 # listed feature missing, should not match
2121 return "retry", False
2122 return "retry", False
2122
2123
2123 if el.endswith(b" (esc)\n"):
2124 if el.endswith(b" (esc)\n"):
2124 if PYTHON3:
2125 if PYTHON3:
2125 el = el[:-7].decode('unicode_escape') + '\n'
2126 el = el[:-7].decode('unicode_escape') + '\n'
2126 el = el.encode('latin-1')
2127 el = el.encode('latin-1')
2127 else:
2128 else:
2128 el = el[:-7].decode('string-escape') + '\n'
2129 el = el[:-7].decode('string-escape') + '\n'
2129 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2130 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2130 return True, True
2131 return True, True
2131 if el.endswith(b" (re)\n"):
2132 if el.endswith(b" (re)\n"):
2132 return (TTest.rematch(el[:-6], l) or retry), False
2133 return (TTest.rematch(el[:-6], l) or retry), False
2133 if el.endswith(b" (glob)\n"):
2134 if el.endswith(b" (glob)\n"):
2134 # ignore '(glob)' added to l by 'replacements'
2135 # ignore '(glob)' added to l by 'replacements'
2135 if l.endswith(b" (glob)\n"):
2136 if l.endswith(b" (glob)\n"):
2136 l = l[:-8] + b"\n"
2137 l = l[:-8] + b"\n"
2137 return (TTest.globmatch(el[:-8], l) or retry), False
2138 return (TTest.globmatch(el[:-8], l) or retry), False
2138 if os.altsep:
2139 if os.altsep:
2139 _l = l.replace(b'\\', b'/')
2140 _l = l.replace(b'\\', b'/')
2140 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2141 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2141 return True, True
2142 return True, True
2142 return retry, True
2143 return retry, True
2143
2144
2144 @staticmethod
2145 @staticmethod
2145 def parsehghaveoutput(lines):
2146 def parsehghaveoutput(lines):
2146 """Parse hghave log lines.
2147 """Parse hghave log lines.
2147
2148
2148 Return tuple of lists (missing, failed):
2149 Return tuple of lists (missing, failed):
2149 * the missing/unknown features
2150 * the missing/unknown features
2150 * the features for which existence check failed"""
2151 * the features for which existence check failed"""
2151 missing = []
2152 missing = []
2152 failed = []
2153 failed = []
2153 for line in lines:
2154 for line in lines:
2154 if line.startswith(TTest.SKIPPED_PREFIX):
2155 if line.startswith(TTest.SKIPPED_PREFIX):
2155 line = line.splitlines()[0]
2156 line = line.splitlines()[0]
2156 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2157 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2157 elif line.startswith(TTest.FAILED_PREFIX):
2158 elif line.startswith(TTest.FAILED_PREFIX):
2158 line = line.splitlines()[0]
2159 line = line.splitlines()[0]
2159 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2160 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2160
2161
2161 return missing, failed
2162 return missing, failed
2162
2163
2163 @staticmethod
2164 @staticmethod
2164 def _escapef(m):
2165 def _escapef(m):
2165 return TTest.ESCAPEMAP[m.group(0)]
2166 return TTest.ESCAPEMAP[m.group(0)]
2166
2167
2167 @staticmethod
2168 @staticmethod
2168 def _stringescape(s):
2169 def _stringescape(s):
2169 return TTest.ESCAPESUB(TTest._escapef, s)
2170 return TTest.ESCAPESUB(TTest._escapef, s)
2170
2171
2171
2172
2172 iolock = threading.RLock()
2173 iolock = threading.RLock()
2173 firstlock = threading.RLock()
2174 firstlock = threading.RLock()
2174 firsterror = False
2175 firsterror = False
2175
2176
2176
2177
2177 class TestResult(unittest._TextTestResult):
2178 class TestResult(unittest._TextTestResult):
2178 """Holds results when executing via unittest."""
2179 """Holds results when executing via unittest."""
2179
2180
2180 # Don't worry too much about accessing the non-public _TextTestResult.
2181 # Don't worry too much about accessing the non-public _TextTestResult.
2181 # It is relatively common in Python testing tools.
2182 # It is relatively common in Python testing tools.
2182 def __init__(self, options, *args, **kwargs):
2183 def __init__(self, options, *args, **kwargs):
2183 super(TestResult, self).__init__(*args, **kwargs)
2184 super(TestResult, self).__init__(*args, **kwargs)
2184
2185
2185 self._options = options
2186 self._options = options
2186
2187
2187 # unittest.TestResult didn't have skipped until 2.7. We need to
2188 # unittest.TestResult didn't have skipped until 2.7. We need to
2188 # polyfill it.
2189 # polyfill it.
2189 self.skipped = []
2190 self.skipped = []
2190
2191
2191 # We have a custom "ignored" result that isn't present in any Python
2192 # 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
2193 # unittest implementation. It is very similar to skipped. It may make
2193 # sense to map it into skip some day.
2194 # sense to map it into skip some day.
2194 self.ignored = []
2195 self.ignored = []
2195
2196
2196 self.times = []
2197 self.times = []
2197 self._firststarttime = None
2198 self._firststarttime = None
2198 # Data stored for the benefit of generating xunit reports.
2199 # Data stored for the benefit of generating xunit reports.
2199 self.successes = []
2200 self.successes = []
2200 self.faildata = {}
2201 self.faildata = {}
2201
2202
2202 if options.color == 'auto':
2203 if options.color == 'auto':
2203 self.color = pygmentspresent and self.stream.isatty()
2204 self.color = pygmentspresent and self.stream.isatty()
2204 elif options.color == 'never':
2205 elif options.color == 'never':
2205 self.color = False
2206 self.color = False
2206 else: # 'always', for testing purposes
2207 else: # 'always', for testing purposes
2207 self.color = pygmentspresent
2208 self.color = pygmentspresent
2208
2209
2209 def onStart(self, test):
2210 def onStart(self, test):
2210 """Can be overriden by custom TestResult"""
2211 """Can be overriden by custom TestResult"""
2211
2212
2212 def onEnd(self):
2213 def onEnd(self):
2213 """Can be overriden by custom TestResult"""
2214 """Can be overriden by custom TestResult"""
2214
2215
2215 def addFailure(self, test, reason):
2216 def addFailure(self, test, reason):
2216 self.failures.append((test, reason))
2217 self.failures.append((test, reason))
2217
2218
2218 if self._options.first:
2219 if self._options.first:
2219 self.stop()
2220 self.stop()
2220 else:
2221 else:
2221 with iolock:
2222 with iolock:
2222 if reason == "timed out":
2223 if reason == "timed out":
2223 self.stream.write('t')
2224 self.stream.write('t')
2224 else:
2225 else:
2225 if not self._options.nodiff:
2226 if not self._options.nodiff:
2226 self.stream.write('\n')
2227 self.stream.write('\n')
2227 # Exclude the '\n' from highlighting to lex correctly
2228 # Exclude the '\n' from highlighting to lex correctly
2228 formatted = 'ERROR: %s output changed\n' % test
2229 formatted = 'ERROR: %s output changed\n' % test
2229 self.stream.write(highlightmsg(formatted, self.color))
2230 self.stream.write(highlightmsg(formatted, self.color))
2230 self.stream.write('!')
2231 self.stream.write('!')
2231
2232
2232 self.stream.flush()
2233 self.stream.flush()
2233
2234
2234 def addSuccess(self, test):
2235 def addSuccess(self, test):
2235 with iolock:
2236 with iolock:
2236 super(TestResult, self).addSuccess(test)
2237 super(TestResult, self).addSuccess(test)
2237 self.successes.append(test)
2238 self.successes.append(test)
2238
2239
2239 def addError(self, test, err):
2240 def addError(self, test, err):
2240 super(TestResult, self).addError(test, err)
2241 super(TestResult, self).addError(test, err)
2241 if self._options.first:
2242 if self._options.first:
2242 self.stop()
2243 self.stop()
2243
2244
2244 # Polyfill.
2245 # Polyfill.
2245 def addSkip(self, test, reason):
2246 def addSkip(self, test, reason):
2246 self.skipped.append((test, reason))
2247 self.skipped.append((test, reason))
2247 with iolock:
2248 with iolock:
2248 if self.showAll:
2249 if self.showAll:
2249 self.stream.writeln('skipped %s' % reason)
2250 self.stream.writeln('skipped %s' % reason)
2250 else:
2251 else:
2251 self.stream.write('s')
2252 self.stream.write('s')
2252 self.stream.flush()
2253 self.stream.flush()
2253
2254
2254 def addIgnore(self, test, reason):
2255 def addIgnore(self, test, reason):
2255 self.ignored.append((test, reason))
2256 self.ignored.append((test, reason))
2256 with iolock:
2257 with iolock:
2257 if self.showAll:
2258 if self.showAll:
2258 self.stream.writeln('ignored %s' % reason)
2259 self.stream.writeln('ignored %s' % reason)
2259 else:
2260 else:
2260 if reason not in ('not retesting', "doesn't match keyword"):
2261 if reason not in ('not retesting', "doesn't match keyword"):
2261 self.stream.write('i')
2262 self.stream.write('i')
2262 else:
2263 else:
2263 self.testsRun += 1
2264 self.testsRun += 1
2264 self.stream.flush()
2265 self.stream.flush()
2265
2266
2266 def addOutputMismatch(self, test, ret, got, expected):
2267 def addOutputMismatch(self, test, ret, got, expected):
2267 """Record a mismatch in test output for a particular test."""
2268 """Record a mismatch in test output for a particular test."""
2268 if self.shouldStop or firsterror:
2269 if self.shouldStop or firsterror:
2269 # don't print, some other test case already failed and
2270 # don't print, some other test case already failed and
2270 # printed, we're just stale and probably failed due to our
2271 # printed, we're just stale and probably failed due to our
2271 # temp dir getting cleaned up.
2272 # temp dir getting cleaned up.
2272 return
2273 return
2273
2274
2274 accepted = False
2275 accepted = False
2275 lines = []
2276 lines = []
2276
2277
2277 with iolock:
2278 with iolock:
2278 if self._options.nodiff:
2279 if self._options.nodiff:
2279 pass
2280 pass
2280 elif self._options.view:
2281 elif self._options.view:
2281 v = self._options.view
2282 v = self._options.view
2282 subprocess.call(
2283 subprocess.call(
2283 r'"%s" "%s" "%s"'
2284 r'"%s" "%s" "%s"'
2284 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2285 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2285 shell=True,
2286 shell=True,
2286 )
2287 )
2287 else:
2288 else:
2288 servefail, lines = getdiff(
2289 servefail, lines = getdiff(
2289 expected, got, test.refpath, test.errpath
2290 expected, got, test.refpath, test.errpath
2290 )
2291 )
2291 self.stream.write('\n')
2292 self.stream.write('\n')
2292 for line in lines:
2293 for line in lines:
2293 line = highlightdiff(line, self.color)
2294 line = highlightdiff(line, self.color)
2294 if PYTHON3:
2295 if PYTHON3:
2295 self.stream.flush()
2296 self.stream.flush()
2296 self.stream.buffer.write(line)
2297 self.stream.buffer.write(line)
2297 self.stream.buffer.flush()
2298 self.stream.buffer.flush()
2298 else:
2299 else:
2299 self.stream.write(line)
2300 self.stream.write(line)
2300 self.stream.flush()
2301 self.stream.flush()
2301
2302
2302 if servefail:
2303 if servefail:
2303 raise test.failureException(
2304 raise test.failureException(
2304 'server failed to start (HGPORT=%s)' % test._startport
2305 'server failed to start (HGPORT=%s)' % test._startport
2305 )
2306 )
2306
2307
2307 # handle interactive prompt without releasing iolock
2308 # handle interactive prompt without releasing iolock
2308 if self._options.interactive:
2309 if self._options.interactive:
2309 if test.readrefout() != expected:
2310 if test.readrefout() != expected:
2310 self.stream.write(
2311 self.stream.write(
2311 'Reference output has changed (run again to prompt '
2312 'Reference output has changed (run again to prompt '
2312 'changes)'
2313 'changes)'
2313 )
2314 )
2314 else:
2315 else:
2315 self.stream.write('Accept this change? [y/N] ')
2316 self.stream.write('Accept this change? [y/N] ')
2316 self.stream.flush()
2317 self.stream.flush()
2317 answer = sys.stdin.readline().strip()
2318 answer = sys.stdin.readline().strip()
2318 if answer.lower() in ('y', 'yes'):
2319 if answer.lower() in ('y', 'yes'):
2319 if test.path.endswith(b'.t'):
2320 if test.path.endswith(b'.t'):
2320 rename(test.errpath, test.path)
2321 rename(test.errpath, test.path)
2321 else:
2322 else:
2322 rename(test.errpath, b'%s.out' % test.path)
2323 rename(test.errpath, b'%s.out' % test.path)
2323 accepted = True
2324 accepted = True
2324 if not accepted:
2325 if not accepted:
2325 self.faildata[test.name] = b''.join(lines)
2326 self.faildata[test.name] = b''.join(lines)
2326
2327
2327 return accepted
2328 return accepted
2328
2329
2329 def startTest(self, test):
2330 def startTest(self, test):
2330 super(TestResult, self).startTest(test)
2331 super(TestResult, self).startTest(test)
2331
2332
2332 # os.times module computes the user time and system time spent by
2333 # 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.
2334 # 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
2335 # 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
2336 # and not for Windows. Hence why we fall back to another function
2336 # for wall time calculations.
2337 # for wall time calculations.
2337 test.started_times = os.times()
2338 test.started_times = os.times()
2338 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2339 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2339 test.started_time = time.time()
2340 test.started_time = time.time()
2340 if self._firststarttime is None: # thread racy but irrelevant
2341 if self._firststarttime is None: # thread racy but irrelevant
2341 self._firststarttime = test.started_time
2342 self._firststarttime = test.started_time
2342
2343
2343 def stopTest(self, test, interrupted=False):
2344 def stopTest(self, test, interrupted=False):
2344 super(TestResult, self).stopTest(test)
2345 super(TestResult, self).stopTest(test)
2345
2346
2346 test.stopped_times = os.times()
2347 test.stopped_times = os.times()
2347 stopped_time = time.time()
2348 stopped_time = time.time()
2348
2349
2349 starttime = test.started_times
2350 starttime = test.started_times
2350 endtime = test.stopped_times
2351 endtime = test.stopped_times
2351 origin = self._firststarttime
2352 origin = self._firststarttime
2352 self.times.append(
2353 self.times.append(
2353 (
2354 (
2354 test.name,
2355 test.name,
2355 endtime[2] - starttime[2], # user space CPU time
2356 endtime[2] - starttime[2], # user space CPU time
2356 endtime[3] - starttime[3], # sys space CPU time
2357 endtime[3] - starttime[3], # sys space CPU time
2357 stopped_time - test.started_time, # real time
2358 stopped_time - test.started_time, # real time
2358 test.started_time - origin, # start date in run context
2359 test.started_time - origin, # start date in run context
2359 stopped_time - origin, # end date in run context
2360 stopped_time - origin, # end date in run context
2360 )
2361 )
2361 )
2362 )
2362
2363
2363 if interrupted:
2364 if interrupted:
2364 with iolock:
2365 with iolock:
2365 self.stream.writeln(
2366 self.stream.writeln(
2366 'INTERRUPTED: %s (after %d seconds)'
2367 'INTERRUPTED: %s (after %d seconds)'
2367 % (test.name, self.times[-1][3])
2368 % (test.name, self.times[-1][3])
2368 )
2369 )
2369
2370
2370
2371
2371 def getTestResult():
2372 def getTestResult():
2372 """
2373 """
2373 Returns the relevant test result
2374 Returns the relevant test result
2374 """
2375 """
2375 if "CUSTOM_TEST_RESULT" in os.environ:
2376 if "CUSTOM_TEST_RESULT" in os.environ:
2376 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2377 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2377 return testresultmodule.TestResult
2378 return testresultmodule.TestResult
2378 else:
2379 else:
2379 return TestResult
2380 return TestResult
2380
2381
2381
2382
2382 class TestSuite(unittest.TestSuite):
2383 class TestSuite(unittest.TestSuite):
2383 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2384 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2384
2385
2385 def __init__(
2386 def __init__(
2386 self,
2387 self,
2387 testdir,
2388 testdir,
2388 jobs=1,
2389 jobs=1,
2389 whitelist=None,
2390 whitelist=None,
2390 blacklist=None,
2391 blacklist=None,
2391 keywords=None,
2392 keywords=None,
2392 loop=False,
2393 loop=False,
2393 runs_per_test=1,
2394 runs_per_test=1,
2394 loadtest=None,
2395 loadtest=None,
2395 showchannels=False,
2396 showchannels=False,
2396 *args,
2397 *args,
2397 **kwargs
2398 **kwargs
2398 ):
2399 ):
2399 """Create a new instance that can run tests with a configuration.
2400 """Create a new instance that can run tests with a configuration.
2400
2401
2401 testdir specifies the directory where tests are executed from. This
2402 testdir specifies the directory where tests are executed from. This
2402 is typically the ``tests`` directory from Mercurial's source
2403 is typically the ``tests`` directory from Mercurial's source
2403 repository.
2404 repository.
2404
2405
2405 jobs specifies the number of jobs to run concurrently. Each test
2406 jobs specifies the number of jobs to run concurrently. Each test
2406 executes on its own thread. Tests actually spawn new processes, so
2407 executes on its own thread. Tests actually spawn new processes, so
2407 state mutation should not be an issue.
2408 state mutation should not be an issue.
2408
2409
2409 If there is only one job, it will use the main thread.
2410 If there is only one job, it will use the main thread.
2410
2411
2411 whitelist and blacklist denote tests that have been whitelisted and
2412 whitelist and blacklist denote tests that have been whitelisted and
2412 blacklisted, respectively. These arguments don't belong in TestSuite.
2413 blacklisted, respectively. These arguments don't belong in TestSuite.
2413 Instead, whitelist and blacklist should be handled by the thing that
2414 Instead, whitelist and blacklist should be handled by the thing that
2414 populates the TestSuite with tests. They are present to preserve
2415 populates the TestSuite with tests. They are present to preserve
2415 backwards compatible behavior which reports skipped tests as part
2416 backwards compatible behavior which reports skipped tests as part
2416 of the results.
2417 of the results.
2417
2418
2418 keywords denotes key words that will be used to filter which tests
2419 keywords denotes key words that will be used to filter which tests
2419 to execute. This arguably belongs outside of TestSuite.
2420 to execute. This arguably belongs outside of TestSuite.
2420
2421
2421 loop denotes whether to loop over tests forever.
2422 loop denotes whether to loop over tests forever.
2422 """
2423 """
2423 super(TestSuite, self).__init__(*args, **kwargs)
2424 super(TestSuite, self).__init__(*args, **kwargs)
2424
2425
2425 self._jobs = jobs
2426 self._jobs = jobs
2426 self._whitelist = whitelist
2427 self._whitelist = whitelist
2427 self._blacklist = blacklist
2428 self._blacklist = blacklist
2428 self._keywords = keywords
2429 self._keywords = keywords
2429 self._loop = loop
2430 self._loop = loop
2430 self._runs_per_test = runs_per_test
2431 self._runs_per_test = runs_per_test
2431 self._loadtest = loadtest
2432 self._loadtest = loadtest
2432 self._showchannels = showchannels
2433 self._showchannels = showchannels
2433
2434
2434 def run(self, result):
2435 def run(self, result):
2435 # We have a number of filters that need to be applied. We do this
2436 # 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
2437 # here instead of inside Test because it makes the running logic for
2437 # Test simpler.
2438 # Test simpler.
2438 tests = []
2439 tests = []
2439 num_tests = [0]
2440 num_tests = [0]
2440 for test in self._tests:
2441 for test in self._tests:
2441
2442
2442 def get():
2443 def get():
2443 num_tests[0] += 1
2444 num_tests[0] += 1
2444 if getattr(test, 'should_reload', False):
2445 if getattr(test, 'should_reload', False):
2445 return self._loadtest(test, num_tests[0])
2446 return self._loadtest(test, num_tests[0])
2446 return test
2447 return test
2447
2448
2448 if not os.path.exists(test.path):
2449 if not os.path.exists(test.path):
2449 result.addSkip(test, "Doesn't exist")
2450 result.addSkip(test, "Doesn't exist")
2450 continue
2451 continue
2451
2452
2452 is_whitelisted = self._whitelist and (
2453 is_whitelisted = self._whitelist and (
2453 test.relpath in self._whitelist or test.bname in self._whitelist
2454 test.relpath in self._whitelist or test.bname in self._whitelist
2454 )
2455 )
2455 if not is_whitelisted:
2456 if not is_whitelisted:
2456 is_blacklisted = self._blacklist and (
2457 is_blacklisted = self._blacklist and (
2457 test.relpath in self._blacklist
2458 test.relpath in self._blacklist
2458 or test.bname in self._blacklist
2459 or test.bname in self._blacklist
2459 )
2460 )
2460 if is_blacklisted:
2461 if is_blacklisted:
2461 result.addSkip(test, 'blacklisted')
2462 result.addSkip(test, 'blacklisted')
2462 continue
2463 continue
2463 if self._keywords:
2464 if self._keywords:
2464 with open(test.path, 'rb') as f:
2465 with open(test.path, 'rb') as f:
2465 t = f.read().lower() + test.bname.lower()
2466 t = f.read().lower() + test.bname.lower()
2466 ignored = False
2467 ignored = False
2467 for k in self._keywords.lower().split():
2468 for k in self._keywords.lower().split():
2468 if k not in t:
2469 if k not in t:
2469 result.addIgnore(test, "doesn't match keyword")
2470 result.addIgnore(test, "doesn't match keyword")
2470 ignored = True
2471 ignored = True
2471 break
2472 break
2472
2473
2473 if ignored:
2474 if ignored:
2474 continue
2475 continue
2475 for _ in xrange(self._runs_per_test):
2476 for _ in xrange(self._runs_per_test):
2476 tests.append(get())
2477 tests.append(get())
2477
2478
2478 runtests = list(tests)
2479 runtests = list(tests)
2479 done = queue.Queue()
2480 done = queue.Queue()
2480 running = 0
2481 running = 0
2481
2482
2482 channels = [""] * self._jobs
2483 channels = [""] * self._jobs
2483
2484
2484 def job(test, result):
2485 def job(test, result):
2485 for n, v in enumerate(channels):
2486 for n, v in enumerate(channels):
2486 if not v:
2487 if not v:
2487 channel = n
2488 channel = n
2488 break
2489 break
2489 else:
2490 else:
2490 raise ValueError('Could not find output channel')
2491 raise ValueError('Could not find output channel')
2491 channels[channel] = "=" + test.name[5:].split(".")[0]
2492 channels[channel] = "=" + test.name[5:].split(".")[0]
2492 try:
2493 try:
2493 test(result)
2494 test(result)
2494 done.put(None)
2495 done.put(None)
2495 except KeyboardInterrupt:
2496 except KeyboardInterrupt:
2496 pass
2497 pass
2497 except: # re-raises
2498 except: # re-raises
2498 done.put(('!', test, 'run-test raised an error, see traceback'))
2499 done.put(('!', test, 'run-test raised an error, see traceback'))
2499 raise
2500 raise
2500 finally:
2501 finally:
2501 try:
2502 try:
2502 channels[channel] = ''
2503 channels[channel] = ''
2503 except IndexError:
2504 except IndexError:
2504 pass
2505 pass
2505
2506
2506 def stat():
2507 def stat():
2507 count = 0
2508 count = 0
2508 while channels:
2509 while channels:
2509 d = '\n%03s ' % count
2510 d = '\n%03s ' % count
2510 for n, v in enumerate(channels):
2511 for n, v in enumerate(channels):
2511 if v:
2512 if v:
2512 d += v[0]
2513 d += v[0]
2513 channels[n] = v[1:] or '.'
2514 channels[n] = v[1:] or '.'
2514 else:
2515 else:
2515 d += ' '
2516 d += ' '
2516 d += ' '
2517 d += ' '
2517 with iolock:
2518 with iolock:
2518 sys.stdout.write(d + ' ')
2519 sys.stdout.write(d + ' ')
2519 sys.stdout.flush()
2520 sys.stdout.flush()
2520 for x in xrange(10):
2521 for x in xrange(10):
2521 if channels:
2522 if channels:
2522 time.sleep(0.1)
2523 time.sleep(0.1)
2523 count += 1
2524 count += 1
2524
2525
2525 stoppedearly = False
2526 stoppedearly = False
2526
2527
2527 if self._showchannels:
2528 if self._showchannels:
2528 statthread = threading.Thread(target=stat, name="stat")
2529 statthread = threading.Thread(target=stat, name="stat")
2529 statthread.start()
2530 statthread.start()
2530
2531
2531 try:
2532 try:
2532 while tests or running:
2533 while tests or running:
2533 if not done.empty() or running == self._jobs or not tests:
2534 if not done.empty() or running == self._jobs or not tests:
2534 try:
2535 try:
2535 done.get(True, 1)
2536 done.get(True, 1)
2536 running -= 1
2537 running -= 1
2537 if result and result.shouldStop:
2538 if result and result.shouldStop:
2538 stoppedearly = True
2539 stoppedearly = True
2539 break
2540 break
2540 except queue.Empty:
2541 except queue.Empty:
2541 continue
2542 continue
2542 if tests and not running == self._jobs:
2543 if tests and not running == self._jobs:
2543 test = tests.pop(0)
2544 test = tests.pop(0)
2544 if self._loop:
2545 if self._loop:
2545 if getattr(test, 'should_reload', False):
2546 if getattr(test, 'should_reload', False):
2546 num_tests[0] += 1
2547 num_tests[0] += 1
2547 tests.append(self._loadtest(test, num_tests[0]))
2548 tests.append(self._loadtest(test, num_tests[0]))
2548 else:
2549 else:
2549 tests.append(test)
2550 tests.append(test)
2550 if self._jobs == 1:
2551 if self._jobs == 1:
2551 job(test, result)
2552 job(test, result)
2552 else:
2553 else:
2553 t = threading.Thread(
2554 t = threading.Thread(
2554 target=job, name=test.name, args=(test, result)
2555 target=job, name=test.name, args=(test, result)
2555 )
2556 )
2556 t.start()
2557 t.start()
2557 running += 1
2558 running += 1
2558
2559
2559 # If we stop early we still need to wait on started tests to
2560 # If we stop early we still need to wait on started tests to
2560 # finish. Otherwise, there is a race between the test completing
2561 # finish. Otherwise, there is a race between the test completing
2561 # and the test's cleanup code running. This could result in the
2562 # and the test's cleanup code running. This could result in the
2562 # test reporting incorrect.
2563 # test reporting incorrect.
2563 if stoppedearly:
2564 if stoppedearly:
2564 while running:
2565 while running:
2565 try:
2566 try:
2566 done.get(True, 1)
2567 done.get(True, 1)
2567 running -= 1
2568 running -= 1
2568 except queue.Empty:
2569 except queue.Empty:
2569 continue
2570 continue
2570 except KeyboardInterrupt:
2571 except KeyboardInterrupt:
2571 for test in runtests:
2572 for test in runtests:
2572 test.abort()
2573 test.abort()
2573
2574
2574 channels = []
2575 channels = []
2575
2576
2576 return result
2577 return result
2577
2578
2578
2579
2579 # Save the most recent 5 wall-clock runtimes of each test to a
2580 # Save the most recent 5 wall-clock runtimes of each test to a
2580 # human-readable text file named .testtimes. Tests are sorted
2581 # human-readable text file named .testtimes. Tests are sorted
2581 # alphabetically, while times for each test are listed from oldest to
2582 # alphabetically, while times for each test are listed from oldest to
2582 # newest.
2583 # newest.
2583
2584
2584
2585
2585 def loadtimes(outputdir):
2586 def loadtimes(outputdir):
2586 times = []
2587 times = []
2587 try:
2588 try:
2588 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2589 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2589 for line in fp:
2590 for line in fp:
2590 m = re.match('(.*?) ([0-9. ]+)', line)
2591 m = re.match('(.*?) ([0-9. ]+)', line)
2591 times.append(
2592 times.append(
2592 (m.group(1), [float(t) for t in m.group(2).split()])
2593 (m.group(1), [float(t) for t in m.group(2).split()])
2593 )
2594 )
2594 except IOError as err:
2595 except IOError as err:
2595 if err.errno != errno.ENOENT:
2596 if err.errno != errno.ENOENT:
2596 raise
2597 raise
2597 return times
2598 return times
2598
2599
2599
2600
2600 def savetimes(outputdir, result):
2601 def savetimes(outputdir, result):
2601 saved = dict(loadtimes(outputdir))
2602 saved = dict(loadtimes(outputdir))
2602 maxruns = 5
2603 maxruns = 5
2603 skipped = {str(t[0]) for t in result.skipped}
2604 skipped = {str(t[0]) for t in result.skipped}
2604 for tdata in result.times:
2605 for tdata in result.times:
2605 test, real = tdata[0], tdata[3]
2606 test, real = tdata[0], tdata[3]
2606 if test not in skipped:
2607 if test not in skipped:
2607 ts = saved.setdefault(test, [])
2608 ts = saved.setdefault(test, [])
2608 ts.append(real)
2609 ts.append(real)
2609 ts[:] = ts[-maxruns:]
2610 ts[:] = ts[-maxruns:]
2610
2611
2611 fd, tmpname = tempfile.mkstemp(
2612 fd, tmpname = tempfile.mkstemp(
2612 prefix=b'.testtimes', dir=outputdir, text=True
2613 prefix=b'.testtimes', dir=outputdir, text=True
2613 )
2614 )
2614 with os.fdopen(fd, 'w') as fp:
2615 with os.fdopen(fd, 'w') as fp:
2615 for name, ts in sorted(saved.items()):
2616 for name, ts in sorted(saved.items()):
2616 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2617 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2617 timepath = os.path.join(outputdir, b'.testtimes')
2618 timepath = os.path.join(outputdir, b'.testtimes')
2618 try:
2619 try:
2619 os.unlink(timepath)
2620 os.unlink(timepath)
2620 except OSError:
2621 except OSError:
2621 pass
2622 pass
2622 try:
2623 try:
2623 os.rename(tmpname, timepath)
2624 os.rename(tmpname, timepath)
2624 except OSError:
2625 except OSError:
2625 pass
2626 pass
2626
2627
2627
2628
2628 class TextTestRunner(unittest.TextTestRunner):
2629 class TextTestRunner(unittest.TextTestRunner):
2629 """Custom unittest test runner that uses appropriate settings."""
2630 """Custom unittest test runner that uses appropriate settings."""
2630
2631
2631 def __init__(self, runner, *args, **kwargs):
2632 def __init__(self, runner, *args, **kwargs):
2632 super(TextTestRunner, self).__init__(*args, **kwargs)
2633 super(TextTestRunner, self).__init__(*args, **kwargs)
2633
2634
2634 self._runner = runner
2635 self._runner = runner
2635
2636
2636 self._result = getTestResult()(
2637 self._result = getTestResult()(
2637 self._runner.options, self.stream, self.descriptions, self.verbosity
2638 self._runner.options, self.stream, self.descriptions, self.verbosity
2638 )
2639 )
2639
2640
2640 def listtests(self, test):
2641 def listtests(self, test):
2641 test = sorted(test, key=lambda t: t.name)
2642 test = sorted(test, key=lambda t: t.name)
2642
2643
2643 self._result.onStart(test)
2644 self._result.onStart(test)
2644
2645
2645 for t in test:
2646 for t in test:
2646 print(t.name)
2647 print(t.name)
2647 self._result.addSuccess(t)
2648 self._result.addSuccess(t)
2648
2649
2649 if self._runner.options.xunit:
2650 if self._runner.options.xunit:
2650 with open(self._runner.options.xunit, "wb") as xuf:
2651 with open(self._runner.options.xunit, "wb") as xuf:
2651 self._writexunit(self._result, xuf)
2652 self._writexunit(self._result, xuf)
2652
2653
2653 if self._runner.options.json:
2654 if self._runner.options.json:
2654 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2655 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2655 with open(jsonpath, 'w') as fp:
2656 with open(jsonpath, 'w') as fp:
2656 self._writejson(self._result, fp)
2657 self._writejson(self._result, fp)
2657
2658
2658 return self._result
2659 return self._result
2659
2660
2660 def run(self, test):
2661 def run(self, test):
2661 self._result.onStart(test)
2662 self._result.onStart(test)
2662 test(self._result)
2663 test(self._result)
2663
2664
2664 failed = len(self._result.failures)
2665 failed = len(self._result.failures)
2665 skipped = len(self._result.skipped)
2666 skipped = len(self._result.skipped)
2666 ignored = len(self._result.ignored)
2667 ignored = len(self._result.ignored)
2667
2668
2668 with iolock:
2669 with iolock:
2669 self.stream.writeln('')
2670 self.stream.writeln('')
2670
2671
2671 if not self._runner.options.noskips:
2672 if not self._runner.options.noskips:
2672 for test, msg in sorted(
2673 for test, msg in sorted(
2673 self._result.skipped, key=lambda s: s[0].name
2674 self._result.skipped, key=lambda s: s[0].name
2674 ):
2675 ):
2675 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2676 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2676 msg = highlightmsg(formatted, self._result.color)
2677 msg = highlightmsg(formatted, self._result.color)
2677 self.stream.write(msg)
2678 self.stream.write(msg)
2678 for test, msg in sorted(
2679 for test, msg in sorted(
2679 self._result.failures, key=lambda f: f[0].name
2680 self._result.failures, key=lambda f: f[0].name
2680 ):
2681 ):
2681 formatted = 'Failed %s: %s\n' % (test.name, msg)
2682 formatted = 'Failed %s: %s\n' % (test.name, msg)
2682 self.stream.write(highlightmsg(formatted, self._result.color))
2683 self.stream.write(highlightmsg(formatted, self._result.color))
2683 for test, msg in sorted(
2684 for test, msg in sorted(
2684 self._result.errors, key=lambda e: e[0].name
2685 self._result.errors, key=lambda e: e[0].name
2685 ):
2686 ):
2686 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2687 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2687
2688
2688 if self._runner.options.xunit:
2689 if self._runner.options.xunit:
2689 with open(self._runner.options.xunit, "wb") as xuf:
2690 with open(self._runner.options.xunit, "wb") as xuf:
2690 self._writexunit(self._result, xuf)
2691 self._writexunit(self._result, xuf)
2691
2692
2692 if self._runner.options.json:
2693 if self._runner.options.json:
2693 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2694 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2694 with open(jsonpath, 'w') as fp:
2695 with open(jsonpath, 'w') as fp:
2695 self._writejson(self._result, fp)
2696 self._writejson(self._result, fp)
2696
2697
2697 self._runner._checkhglib('Tested')
2698 self._runner._checkhglib('Tested')
2698
2699
2699 savetimes(self._runner._outputdir, self._result)
2700 savetimes(self._runner._outputdir, self._result)
2700
2701
2701 if failed and self._runner.options.known_good_rev:
2702 if failed and self._runner.options.known_good_rev:
2702 self._bisecttests(t for t, m in self._result.failures)
2703 self._bisecttests(t for t, m in self._result.failures)
2703 self.stream.writeln(
2704 self.stream.writeln(
2704 '# Ran %d tests, %d skipped, %d failed.'
2705 '# Ran %d tests, %d skipped, %d failed.'
2705 % (self._result.testsRun, skipped + ignored, failed)
2706 % (self._result.testsRun, skipped + ignored, failed)
2706 )
2707 )
2707 if failed:
2708 if failed:
2708 self.stream.writeln(
2709 self.stream.writeln(
2709 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2710 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2710 )
2711 )
2711 if self._runner.options.time:
2712 if self._runner.options.time:
2712 self.printtimes(self._result.times)
2713 self.printtimes(self._result.times)
2713
2714
2714 if self._runner.options.exceptions:
2715 if self._runner.options.exceptions:
2715 exceptions = aggregateexceptions(
2716 exceptions = aggregateexceptions(
2716 os.path.join(self._runner._outputdir, b'exceptions')
2717 os.path.join(self._runner._outputdir, b'exceptions')
2717 )
2718 )
2718
2719
2719 self.stream.writeln('Exceptions Report:')
2720 self.stream.writeln('Exceptions Report:')
2720 self.stream.writeln(
2721 self.stream.writeln(
2721 '%d total from %d frames'
2722 '%d total from %d frames'
2722 % (exceptions['total'], len(exceptions['exceptioncounts']))
2723 % (exceptions['total'], len(exceptions['exceptioncounts']))
2723 )
2724 )
2724 combined = exceptions['combined']
2725 combined = exceptions['combined']
2725 for key in sorted(combined, key=combined.get, reverse=True):
2726 for key in sorted(combined, key=combined.get, reverse=True):
2726 frame, line, exc = key
2727 frame, line, exc = key
2727 totalcount, testcount, leastcount, leasttest = combined[key]
2728 totalcount, testcount, leastcount, leasttest = combined[key]
2728
2729
2729 self.stream.writeln(
2730 self.stream.writeln(
2730 '%d (%d tests)\t%s: %s (%s - %d total)'
2731 '%d (%d tests)\t%s: %s (%s - %d total)'
2731 % (
2732 % (
2732 totalcount,
2733 totalcount,
2733 testcount,
2734 testcount,
2734 frame,
2735 frame,
2735 exc,
2736 exc,
2736 leasttest,
2737 leasttest,
2737 leastcount,
2738 leastcount,
2738 )
2739 )
2739 )
2740 )
2740
2741
2741 self.stream.flush()
2742 self.stream.flush()
2742
2743
2743 return self._result
2744 return self._result
2744
2745
2745 def _bisecttests(self, tests):
2746 def _bisecttests(self, tests):
2746 bisectcmd = ['hg', 'bisect']
2747 bisectcmd = ['hg', 'bisect']
2747 bisectrepo = self._runner.options.bisect_repo
2748 bisectrepo = self._runner.options.bisect_repo
2748 if bisectrepo:
2749 if bisectrepo:
2749 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2750 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2750
2751
2751 def pread(args):
2752 def pread(args):
2752 env = os.environ.copy()
2753 env = os.environ.copy()
2753 env['HGPLAIN'] = '1'
2754 env['HGPLAIN'] = '1'
2754 p = subprocess.Popen(
2755 p = subprocess.Popen(
2755 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2756 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2756 )
2757 )
2757 data = p.stdout.read()
2758 data = p.stdout.read()
2758 p.wait()
2759 p.wait()
2759 return data
2760 return data
2760
2761
2761 for test in tests:
2762 for test in tests:
2762 pread(bisectcmd + ['--reset']),
2763 pread(bisectcmd + ['--reset']),
2763 pread(bisectcmd + ['--bad', '.'])
2764 pread(bisectcmd + ['--bad', '.'])
2764 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2765 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2765 # TODO: we probably need to forward more options
2766 # TODO: we probably need to forward more options
2766 # that alter hg's behavior inside the tests.
2767 # that alter hg's behavior inside the tests.
2767 opts = ''
2768 opts = ''
2768 withhg = self._runner.options.with_hg
2769 withhg = self._runner.options.with_hg
2769 if withhg:
2770 if withhg:
2770 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2771 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2771 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2772 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2772 data = pread(bisectcmd + ['--command', rtc])
2773 data = pread(bisectcmd + ['--command', rtc])
2773 m = re.search(
2774 m = re.search(
2774 (
2775 (
2775 br'\nThe first (?P<goodbad>bad|good) revision '
2776 br'\nThe first (?P<goodbad>bad|good) revision '
2776 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2777 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2777 br'summary: +(?P<summary>[^\n]+)\n'
2778 br'summary: +(?P<summary>[^\n]+)\n'
2778 ),
2779 ),
2779 data,
2780 data,
2780 (re.MULTILINE | re.DOTALL),
2781 (re.MULTILINE | re.DOTALL),
2781 )
2782 )
2782 if m is None:
2783 if m is None:
2783 self.stream.writeln(
2784 self.stream.writeln(
2784 'Failed to identify failure point for %s' % test
2785 'Failed to identify failure point for %s' % test
2785 )
2786 )
2786 continue
2787 continue
2787 dat = m.groupdict()
2788 dat = m.groupdict()
2788 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2789 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2789 self.stream.writeln(
2790 self.stream.writeln(
2790 '%s %s by %s (%s)'
2791 '%s %s by %s (%s)'
2791 % (
2792 % (
2792 test,
2793 test,
2793 verb,
2794 verb,
2794 dat['node'].decode('ascii'),
2795 dat['node'].decode('ascii'),
2795 dat['summary'].decode('utf8', 'ignore'),
2796 dat['summary'].decode('utf8', 'ignore'),
2796 )
2797 )
2797 )
2798 )
2798
2799
2799 def printtimes(self, times):
2800 def printtimes(self, times):
2800 # iolock held by run
2801 # iolock held by run
2801 self.stream.writeln('# Producing time report')
2802 self.stream.writeln('# Producing time report')
2802 times.sort(key=lambda t: (t[3]))
2803 times.sort(key=lambda t: (t[3]))
2803 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2804 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2804 self.stream.writeln(
2805 self.stream.writeln(
2805 '%-7s %-7s %-7s %-7s %-7s %s'
2806 '%-7s %-7s %-7s %-7s %-7s %s'
2806 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2807 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2807 )
2808 )
2808 for tdata in times:
2809 for tdata in times:
2809 test = tdata[0]
2810 test = tdata[0]
2810 cuser, csys, real, start, end = tdata[1:6]
2811 cuser, csys, real, start, end = tdata[1:6]
2811 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2812 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2812
2813
2813 @staticmethod
2814 @staticmethod
2814 def _writexunit(result, outf):
2815 def _writexunit(result, outf):
2815 # See http://llg.cubic.org/docs/junit/ for a reference.
2816 # See http://llg.cubic.org/docs/junit/ for a reference.
2816 timesd = {t[0]: t[3] for t in result.times}
2817 timesd = {t[0]: t[3] for t in result.times}
2817 doc = minidom.Document()
2818 doc = minidom.Document()
2818 s = doc.createElement('testsuite')
2819 s = doc.createElement('testsuite')
2819 s.setAttribute('errors', "0") # TODO
2820 s.setAttribute('errors', "0") # TODO
2820 s.setAttribute('failures', str(len(result.failures)))
2821 s.setAttribute('failures', str(len(result.failures)))
2821 s.setAttribute('name', 'run-tests')
2822 s.setAttribute('name', 'run-tests')
2822 s.setAttribute(
2823 s.setAttribute(
2823 'skipped', str(len(result.skipped) + len(result.ignored))
2824 'skipped', str(len(result.skipped) + len(result.ignored))
2824 )
2825 )
2825 s.setAttribute('tests', str(result.testsRun))
2826 s.setAttribute('tests', str(result.testsRun))
2826 doc.appendChild(s)
2827 doc.appendChild(s)
2827 for tc in result.successes:
2828 for tc in result.successes:
2828 t = doc.createElement('testcase')
2829 t = doc.createElement('testcase')
2829 t.setAttribute('name', tc.name)
2830 t.setAttribute('name', tc.name)
2830 tctime = timesd.get(tc.name)
2831 tctime = timesd.get(tc.name)
2831 if tctime is not None:
2832 if tctime is not None:
2832 t.setAttribute('time', '%.3f' % tctime)
2833 t.setAttribute('time', '%.3f' % tctime)
2833 s.appendChild(t)
2834 s.appendChild(t)
2834 for tc, err in sorted(result.faildata.items()):
2835 for tc, err in sorted(result.faildata.items()):
2835 t = doc.createElement('testcase')
2836 t = doc.createElement('testcase')
2836 t.setAttribute('name', tc)
2837 t.setAttribute('name', tc)
2837 tctime = timesd.get(tc)
2838 tctime = timesd.get(tc)
2838 if tctime is not None:
2839 if tctime is not None:
2839 t.setAttribute('time', '%.3f' % tctime)
2840 t.setAttribute('time', '%.3f' % tctime)
2840 # createCDATASection expects a unicode or it will
2841 # createCDATASection expects a unicode or it will
2841 # convert using default conversion rules, which will
2842 # convert using default conversion rules, which will
2842 # fail if string isn't ASCII.
2843 # fail if string isn't ASCII.
2843 err = cdatasafe(err).decode('utf-8', 'replace')
2844 err = cdatasafe(err).decode('utf-8', 'replace')
2844 cd = doc.createCDATASection(err)
2845 cd = doc.createCDATASection(err)
2845 # Use 'failure' here instead of 'error' to match errors = 0,
2846 # Use 'failure' here instead of 'error' to match errors = 0,
2846 # failures = len(result.failures) in the testsuite element.
2847 # failures = len(result.failures) in the testsuite element.
2847 failelem = doc.createElement('failure')
2848 failelem = doc.createElement('failure')
2848 failelem.setAttribute('message', 'output changed')
2849 failelem.setAttribute('message', 'output changed')
2849 failelem.setAttribute('type', 'output-mismatch')
2850 failelem.setAttribute('type', 'output-mismatch')
2850 failelem.appendChild(cd)
2851 failelem.appendChild(cd)
2851 t.appendChild(failelem)
2852 t.appendChild(failelem)
2852 s.appendChild(t)
2853 s.appendChild(t)
2853 for tc, message in result.skipped:
2854 for tc, message in result.skipped:
2854 # According to the schema, 'skipped' has no attributes. So store
2855 # According to the schema, 'skipped' has no attributes. So store
2855 # the skip message as a text node instead.
2856 # the skip message as a text node instead.
2856 t = doc.createElement('testcase')
2857 t = doc.createElement('testcase')
2857 t.setAttribute('name', tc.name)
2858 t.setAttribute('name', tc.name)
2858 binmessage = message.encode('utf-8')
2859 binmessage = message.encode('utf-8')
2859 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2860 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2860 cd = doc.createCDATASection(message)
2861 cd = doc.createCDATASection(message)
2861 skipelem = doc.createElement('skipped')
2862 skipelem = doc.createElement('skipped')
2862 skipelem.appendChild(cd)
2863 skipelem.appendChild(cd)
2863 t.appendChild(skipelem)
2864 t.appendChild(skipelem)
2864 s.appendChild(t)
2865 s.appendChild(t)
2865 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2866 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2866
2867
2867 @staticmethod
2868 @staticmethod
2868 def _writejson(result, outf):
2869 def _writejson(result, outf):
2869 timesd = {}
2870 timesd = {}
2870 for tdata in result.times:
2871 for tdata in result.times:
2871 test = tdata[0]
2872 test = tdata[0]
2872 timesd[test] = tdata[1:]
2873 timesd[test] = tdata[1:]
2873
2874
2874 outcome = {}
2875 outcome = {}
2875 groups = [
2876 groups = [
2876 ('success', ((tc, None) for tc in result.successes)),
2877 ('success', ((tc, None) for tc in result.successes)),
2877 ('failure', result.failures),
2878 ('failure', result.failures),
2878 ('skip', result.skipped),
2879 ('skip', result.skipped),
2879 ]
2880 ]
2880 for res, testcases in groups:
2881 for res, testcases in groups:
2881 for tc, __ in testcases:
2882 for tc, __ in testcases:
2882 if tc.name in timesd:
2883 if tc.name in timesd:
2883 diff = result.faildata.get(tc.name, b'')
2884 diff = result.faildata.get(tc.name, b'')
2884 try:
2885 try:
2885 diff = diff.decode('unicode_escape')
2886 diff = diff.decode('unicode_escape')
2886 except UnicodeDecodeError as e:
2887 except UnicodeDecodeError as e:
2887 diff = '%r decoding diff, sorry' % e
2888 diff = '%r decoding diff, sorry' % e
2888 tres = {
2889 tres = {
2889 'result': res,
2890 'result': res,
2890 'time': ('%0.3f' % timesd[tc.name][2]),
2891 'time': ('%0.3f' % timesd[tc.name][2]),
2891 'cuser': ('%0.3f' % timesd[tc.name][0]),
2892 'cuser': ('%0.3f' % timesd[tc.name][0]),
2892 'csys': ('%0.3f' % timesd[tc.name][1]),
2893 'csys': ('%0.3f' % timesd[tc.name][1]),
2893 'start': ('%0.3f' % timesd[tc.name][3]),
2894 'start': ('%0.3f' % timesd[tc.name][3]),
2894 'end': ('%0.3f' % timesd[tc.name][4]),
2895 'end': ('%0.3f' % timesd[tc.name][4]),
2895 'diff': diff,
2896 'diff': diff,
2896 }
2897 }
2897 else:
2898 else:
2898 # blacklisted test
2899 # blacklisted test
2899 tres = {'result': res}
2900 tres = {'result': res}
2900
2901
2901 outcome[tc.name] = tres
2902 outcome[tc.name] = tres
2902 jsonout = json.dumps(
2903 jsonout = json.dumps(
2903 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2904 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2904 )
2905 )
2905 outf.writelines(("testreport =", jsonout))
2906 outf.writelines(("testreport =", jsonout))
2906
2907
2907
2908
2908 def sorttests(testdescs, previoustimes, shuffle=False):
2909 def sorttests(testdescs, previoustimes, shuffle=False):
2909 """Do an in-place sort of tests."""
2910 """Do an in-place sort of tests."""
2910 if shuffle:
2911 if shuffle:
2911 random.shuffle(testdescs)
2912 random.shuffle(testdescs)
2912 return
2913 return
2913
2914
2914 if previoustimes:
2915 if previoustimes:
2915
2916
2916 def sortkey(f):
2917 def sortkey(f):
2917 f = f['path']
2918 f = f['path']
2918 if f in previoustimes:
2919 if f in previoustimes:
2919 # Use most recent time as estimate
2920 # Use most recent time as estimate
2920 return -(previoustimes[f][-1])
2921 return -(previoustimes[f][-1])
2921 else:
2922 else:
2922 # Default to a rather arbitrary value of 1 second for new tests
2923 # Default to a rather arbitrary value of 1 second for new tests
2923 return -1.0
2924 return -1.0
2924
2925
2925 else:
2926 else:
2926 # keywords for slow tests
2927 # keywords for slow tests
2927 slow = {
2928 slow = {
2928 b'svn': 10,
2929 b'svn': 10,
2929 b'cvs': 10,
2930 b'cvs': 10,
2930 b'hghave': 10,
2931 b'hghave': 10,
2931 b'largefiles-update': 10,
2932 b'largefiles-update': 10,
2932 b'run-tests': 10,
2933 b'run-tests': 10,
2933 b'corruption': 10,
2934 b'corruption': 10,
2934 b'race': 10,
2935 b'race': 10,
2935 b'i18n': 10,
2936 b'i18n': 10,
2936 b'check': 100,
2937 b'check': 100,
2937 b'gendoc': 100,
2938 b'gendoc': 100,
2938 b'contrib-perf': 200,
2939 b'contrib-perf': 200,
2939 b'merge-combination': 100,
2940 b'merge-combination': 100,
2940 }
2941 }
2941 perf = {}
2942 perf = {}
2942
2943
2943 def sortkey(f):
2944 def sortkey(f):
2944 # run largest tests first, as they tend to take the longest
2945 # run largest tests first, as they tend to take the longest
2945 f = f['path']
2946 f = f['path']
2946 try:
2947 try:
2947 return perf[f]
2948 return perf[f]
2948 except KeyError:
2949 except KeyError:
2949 try:
2950 try:
2950 val = -os.stat(f).st_size
2951 val = -os.stat(f).st_size
2951 except OSError as e:
2952 except OSError as e:
2952 if e.errno != errno.ENOENT:
2953 if e.errno != errno.ENOENT:
2953 raise
2954 raise
2954 perf[f] = -1e9 # file does not exist, tell early
2955 perf[f] = -1e9 # file does not exist, tell early
2955 return -1e9
2956 return -1e9
2956 for kw, mul in slow.items():
2957 for kw, mul in slow.items():
2957 if kw in f:
2958 if kw in f:
2958 val *= mul
2959 val *= mul
2959 if f.endswith(b'.py'):
2960 if f.endswith(b'.py'):
2960 val /= 10.0
2961 val /= 10.0
2961 perf[f] = val / 1000.0
2962 perf[f] = val / 1000.0
2962 return perf[f]
2963 return perf[f]
2963
2964
2964 testdescs.sort(key=sortkey)
2965 testdescs.sort(key=sortkey)
2965
2966
2966
2967
2967 class TestRunner(object):
2968 class TestRunner(object):
2968 """Holds context for executing tests.
2969 """Holds context for executing tests.
2969
2970
2970 Tests rely on a lot of state. This object holds it for them.
2971 Tests rely on a lot of state. This object holds it for them.
2971 """
2972 """
2972
2973
2973 # Programs required to run tests.
2974 # Programs required to run tests.
2974 REQUIREDTOOLS = [
2975 REQUIREDTOOLS = [
2975 b'diff',
2976 b'diff',
2976 b'grep',
2977 b'grep',
2977 b'unzip',
2978 b'unzip',
2978 b'gunzip',
2979 b'gunzip',
2979 b'bunzip2',
2980 b'bunzip2',
2980 b'sed',
2981 b'sed',
2981 ]
2982 ]
2982
2983
2983 # Maps file extensions to test class.
2984 # Maps file extensions to test class.
2984 TESTTYPES = [
2985 TESTTYPES = [
2985 (b'.py', PythonTest),
2986 (b'.py', PythonTest),
2986 (b'.t', TTest),
2987 (b'.t', TTest),
2987 ]
2988 ]
2988
2989
2989 def __init__(self):
2990 def __init__(self):
2990 self.options = None
2991 self.options = None
2991 self._hgroot = None
2992 self._hgroot = None
2992 self._testdir = None
2993 self._testdir = None
2993 self._outputdir = None
2994 self._outputdir = None
2994 self._hgtmp = None
2995 self._hgtmp = None
2995 self._installdir = None
2996 self._installdir = None
2996 self._bindir = None
2997 self._bindir = None
2997 self._tmpbindir = None
2998 self._tmpbindir = None
2998 self._pythondir = None
2999 self._pythondir = None
2999 self._coveragefile = None
3000 self._coveragefile = None
3000 self._createdfiles = []
3001 self._createdfiles = []
3001 self._hgcommand = None
3002 self._hgcommand = None
3002 self._hgpath = None
3003 self._hgpath = None
3003 self._portoffset = 0
3004 self._portoffset = 0
3004 self._ports = {}
3005 self._ports = {}
3005
3006
3006 def run(self, args, parser=None):
3007 def run(self, args, parser=None):
3007 """Run the test suite."""
3008 """Run the test suite."""
3008 oldmask = os.umask(0o22)
3009 oldmask = os.umask(0o22)
3009 try:
3010 try:
3010 parser = parser or getparser()
3011 parser = parser or getparser()
3011 options = parseargs(args, parser)
3012 options = parseargs(args, parser)
3012 tests = [_sys2bytes(a) for a in options.tests]
3013 tests = [_sys2bytes(a) for a in options.tests]
3013 if options.test_list is not None:
3014 if options.test_list is not None:
3014 for listfile in options.test_list:
3015 for listfile in options.test_list:
3015 with open(listfile, 'rb') as f:
3016 with open(listfile, 'rb') as f:
3016 tests.extend(t for t in f.read().splitlines() if t)
3017 tests.extend(t for t in f.read().splitlines() if t)
3017 self.options = options
3018 self.options = options
3018
3019
3019 self._checktools()
3020 self._checktools()
3020 testdescs = self.findtests(tests)
3021 testdescs = self.findtests(tests)
3021 if options.profile_runner:
3022 if options.profile_runner:
3022 import statprof
3023 import statprof
3023
3024
3024 statprof.start()
3025 statprof.start()
3025 result = self._run(testdescs)
3026 result = self._run(testdescs)
3026 if options.profile_runner:
3027 if options.profile_runner:
3027 statprof.stop()
3028 statprof.stop()
3028 statprof.display()
3029 statprof.display()
3029 return result
3030 return result
3030
3031
3031 finally:
3032 finally:
3032 os.umask(oldmask)
3033 os.umask(oldmask)
3033
3034
3034 def _run(self, testdescs):
3035 def _run(self, testdescs):
3035 testdir = getcwdb()
3036 testdir = getcwdb()
3036 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
3037 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
3037 # assume all tests in same folder for now
3038 # assume all tests in same folder for now
3038 if testdescs:
3039 if testdescs:
3039 pathname = os.path.dirname(testdescs[0]['path'])
3040 pathname = os.path.dirname(testdescs[0]['path'])
3040 if pathname:
3041 if pathname:
3041 testdir = os.path.join(testdir, pathname)
3042 testdir = os.path.join(testdir, pathname)
3042 self._testdir = osenvironb[b'TESTDIR'] = testdir
3043 self._testdir = osenvironb[b'TESTDIR'] = testdir
3043 if self.options.outputdir:
3044 if self.options.outputdir:
3044 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3045 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3045 else:
3046 else:
3046 self._outputdir = getcwdb()
3047 self._outputdir = getcwdb()
3047 if testdescs and pathname:
3048 if testdescs and pathname:
3048 self._outputdir = os.path.join(self._outputdir, pathname)
3049 self._outputdir = os.path.join(self._outputdir, pathname)
3049 previoustimes = {}
3050 previoustimes = {}
3050 if self.options.order_by_runtime:
3051 if self.options.order_by_runtime:
3051 previoustimes = dict(loadtimes(self._outputdir))
3052 previoustimes = dict(loadtimes(self._outputdir))
3052 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3053 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3053
3054
3054 if 'PYTHONHASHSEED' not in os.environ:
3055 if 'PYTHONHASHSEED' not in os.environ:
3055 # use a random python hash seed all the time
3056 # use a random python hash seed all the time
3056 # we do the randomness ourself to know what seed is used
3057 # we do the randomness ourself to know what seed is used
3057 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3058 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3058
3059
3059 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3060 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3060 # by default, causing thrashing on high-cpu-count systems.
3061 # by default, causing thrashing on high-cpu-count systems.
3061 # Setting its limit to 3 during tests should still let us uncover
3062 # Setting its limit to 3 during tests should still let us uncover
3062 # multi-threading bugs while keeping the thrashing reasonable.
3063 # multi-threading bugs while keeping the thrashing reasonable.
3063 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3064 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3064
3065
3065 if self.options.tmpdir:
3066 if self.options.tmpdir:
3066 self.options.keep_tmpdir = True
3067 self.options.keep_tmpdir = True
3067 tmpdir = _sys2bytes(self.options.tmpdir)
3068 tmpdir = _sys2bytes(self.options.tmpdir)
3068 if os.path.exists(tmpdir):
3069 if os.path.exists(tmpdir):
3069 # Meaning of tmpdir has changed since 1.3: we used to create
3070 # Meaning of tmpdir has changed since 1.3: we used to create
3070 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3071 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3071 # tmpdir already exists.
3072 # tmpdir already exists.
3072 print("error: temp dir %r already exists" % tmpdir)
3073 print("error: temp dir %r already exists" % tmpdir)
3073 return 1
3074 return 1
3074
3075
3075 os.makedirs(tmpdir)
3076 os.makedirs(tmpdir)
3076 else:
3077 else:
3077 d = None
3078 d = None
3078 if os.name == 'nt':
3079 if os.name == 'nt':
3079 # without this, we get the default temp dir location, but
3080 # without this, we get the default temp dir location, but
3080 # in all lowercase, which causes troubles with paths (issue3490)
3081 # in all lowercase, which causes troubles with paths (issue3490)
3081 d = osenvironb.get(b'TMP', None)
3082 d = osenvironb.get(b'TMP', None)
3082 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3083 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3083
3084
3084 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3085 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3085
3086
3086 if self.options.with_hg:
3087 if self.options.with_hg:
3087 self._installdir = None
3088 self._installdir = None
3088 whg = self.options.with_hg
3089 whg = self.options.with_hg
3089 self._bindir = os.path.dirname(os.path.realpath(whg))
3090 self._bindir = os.path.dirname(os.path.realpath(whg))
3090 assert isinstance(self._bindir, bytes)
3091 assert isinstance(self._bindir, bytes)
3091 self._hgcommand = os.path.basename(whg)
3092 self._hgcommand = os.path.basename(whg)
3092 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3093 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3093 os.makedirs(self._tmpbindir)
3094 os.makedirs(self._tmpbindir)
3094
3095
3095 normbin = os.path.normpath(os.path.abspath(whg))
3096 normbin = os.path.normpath(os.path.abspath(whg))
3096 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3097 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3097
3098
3098 # Other Python scripts in the test harness need to
3099 # Other Python scripts in the test harness need to
3099 # `import mercurial`. If `hg` is a Python script, we assume
3100 # `import mercurial`. If `hg` is a Python script, we assume
3100 # the Mercurial modules are relative to its path and tell the tests
3101 # the Mercurial modules are relative to its path and tell the tests
3101 # to load Python modules from its directory.
3102 # to load Python modules from its directory.
3102 with open(whg, 'rb') as fh:
3103 with open(whg, 'rb') as fh:
3103 initial = fh.read(1024)
3104 initial = fh.read(1024)
3104
3105
3105 if re.match(b'#!.*python', initial):
3106 if re.match(b'#!.*python', initial):
3106 self._pythondir = self._bindir
3107 self._pythondir = self._bindir
3107 # If it looks like our in-repo Rust binary, use the source root.
3108 # If it looks like our in-repo Rust binary, use the source root.
3108 # This is a bit hacky. But rhg is still not supported outside the
3109 # This is a bit hacky. But rhg is still not supported outside the
3109 # source directory. So until it is, do the simple thing.
3110 # source directory. So until it is, do the simple thing.
3110 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3111 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3111 self._pythondir = os.path.dirname(self._testdir)
3112 self._pythondir = os.path.dirname(self._testdir)
3112 # Fall back to the legacy behavior.
3113 # Fall back to the legacy behavior.
3113 else:
3114 else:
3114 self._pythondir = self._bindir
3115 self._pythondir = self._bindir
3115
3116
3116 else:
3117 else:
3117 self._installdir = os.path.join(self._hgtmp, b"install")
3118 self._installdir = os.path.join(self._hgtmp, b"install")
3118 self._bindir = os.path.join(self._installdir, b"bin")
3119 self._bindir = os.path.join(self._installdir, b"bin")
3119 self._hgcommand = b'hg'
3120 self._hgcommand = b'hg'
3120 self._tmpbindir = self._bindir
3121 self._tmpbindir = self._bindir
3121 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3122 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3122
3123
3123 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3124 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3124 # a python script and feed it to python.exe. Legacy stdio is force
3125 # a python script and feed it to python.exe. Legacy stdio is force
3125 # enabled by hg.exe, and this is a more realistic way to launch hg
3126 # enabled by hg.exe, and this is a more realistic way to launch hg
3126 # anyway.
3127 # anyway.
3127 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3128 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3128 self._hgcommand += b'.exe'
3129 self._hgcommand += b'.exe'
3129
3130
3130 # set CHGHG, then replace "hg" command by "chg"
3131 # set CHGHG, then replace "hg" command by "chg"
3131 chgbindir = self._bindir
3132 chgbindir = self._bindir
3132 if self.options.chg or self.options.with_chg:
3133 if self.options.chg or self.options.with_chg:
3133 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3134 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3134 else:
3135 else:
3135 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3136 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3136 if self.options.chg:
3137 if self.options.chg:
3137 self._hgcommand = b'chg'
3138 self._hgcommand = b'chg'
3138 elif self.options.with_chg:
3139 elif self.options.with_chg:
3139 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3140 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3140 self._hgcommand = os.path.basename(self.options.with_chg)
3141 self._hgcommand = os.path.basename(self.options.with_chg)
3141
3142
3142 # configure fallback and replace "hg" command by "rhg"
3143 # configure fallback and replace "hg" command by "rhg"
3143 rhgbindir = self._bindir
3144 rhgbindir = self._bindir
3144 if self.options.rhg or self.options.with_rhg:
3145 if self.options.rhg or self.options.with_rhg:
3145 # Affects hghave.py
3146 # Affects hghave.py
3146 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3147 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3147 # Affects configuration. Alternatives would be setting configuration through
3148 # Affects configuration. Alternatives would be setting configuration through
3148 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3149 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3149 # `--config` but that disrupts tests that print command lines and check expected
3150 # `--config` but that disrupts tests that print command lines and check expected
3150 # output.
3151 # output.
3151 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3152 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3152 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = os.path.join(
3153 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = os.path.join(
3153 self._bindir, self._hgcommand
3154 self._bindir, self._hgcommand
3154 )
3155 )
3155 if self.options.rhg:
3156 if self.options.rhg:
3156 self._hgcommand = b'rhg'
3157 self._hgcommand = b'rhg'
3157 elif self.options.with_rhg:
3158 elif self.options.with_rhg:
3158 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3159 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3159 self._hgcommand = os.path.basename(self.options.with_rhg)
3160 self._hgcommand = os.path.basename(self.options.with_rhg)
3160
3161
3161 osenvironb[b"BINDIR"] = self._bindir
3162 osenvironb[b"BINDIR"] = self._bindir
3162 osenvironb[b"PYTHON"] = PYTHON
3163 osenvironb[b"PYTHON"] = PYTHON
3163
3164
3164 fileb = _sys2bytes(__file__)
3165 fileb = _sys2bytes(__file__)
3165 runtestdir = os.path.abspath(os.path.dirname(fileb))
3166 runtestdir = os.path.abspath(os.path.dirname(fileb))
3166 osenvironb[b'RUNTESTDIR'] = runtestdir
3167 osenvironb[b'RUNTESTDIR'] = runtestdir
3167 if PYTHON3:
3168 if PYTHON3:
3168 sepb = _sys2bytes(os.pathsep)
3169 sepb = _sys2bytes(os.pathsep)
3169 else:
3170 else:
3170 sepb = os.pathsep
3171 sepb = os.pathsep
3171 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3172 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3172 if os.path.islink(__file__):
3173 if os.path.islink(__file__):
3173 # test helper will likely be at the end of the symlink
3174 # test helper will likely be at the end of the symlink
3174 realfile = os.path.realpath(fileb)
3175 realfile = os.path.realpath(fileb)
3175 realdir = os.path.abspath(os.path.dirname(realfile))
3176 realdir = os.path.abspath(os.path.dirname(realfile))
3176 path.insert(2, realdir)
3177 path.insert(2, realdir)
3177 if chgbindir != self._bindir:
3178 if chgbindir != self._bindir:
3178 path.insert(1, chgbindir)
3179 path.insert(1, chgbindir)
3179 if rhgbindir != self._bindir:
3180 if rhgbindir != self._bindir:
3180 path.insert(1, rhgbindir)
3181 path.insert(1, rhgbindir)
3181 if self._testdir != runtestdir:
3182 if self._testdir != runtestdir:
3182 path = [self._testdir] + path
3183 path = [self._testdir] + path
3183 if self._tmpbindir != self._bindir:
3184 if self._tmpbindir != self._bindir:
3184 path = [self._tmpbindir] + path
3185 path = [self._tmpbindir] + path
3185 osenvironb[b"PATH"] = sepb.join(path)
3186 osenvironb[b"PATH"] = sepb.join(path)
3186
3187
3187 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3188 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3188 # can run .../tests/run-tests.py test-foo where test-foo
3189 # can run .../tests/run-tests.py test-foo where test-foo
3189 # adds an extension to HGRC. Also include run-test.py directory to
3190 # adds an extension to HGRC. Also include run-test.py directory to
3190 # import modules like heredoctest.
3191 # import modules like heredoctest.
3191 pypath = [self._pythondir, self._testdir, runtestdir]
3192 pypath = [self._pythondir, self._testdir, runtestdir]
3192 # We have to augment PYTHONPATH, rather than simply replacing
3193 # We have to augment PYTHONPATH, rather than simply replacing
3193 # it, in case external libraries are only available via current
3194 # it, in case external libraries are only available via current
3194 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3195 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3195 # are in /opt/subversion.)
3196 # are in /opt/subversion.)
3196 oldpypath = osenvironb.get(IMPL_PATH)
3197 oldpypath = osenvironb.get(IMPL_PATH)
3197 if oldpypath:
3198 if oldpypath:
3198 pypath.append(oldpypath)
3199 pypath.append(oldpypath)
3199 osenvironb[IMPL_PATH] = sepb.join(pypath)
3200 osenvironb[IMPL_PATH] = sepb.join(pypath)
3200
3201
3201 if self.options.pure:
3202 if self.options.pure:
3202 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3203 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3203 os.environ["HGMODULEPOLICY"] = "py"
3204 os.environ["HGMODULEPOLICY"] = "py"
3204 if self.options.rust:
3205 if self.options.rust:
3205 os.environ["HGMODULEPOLICY"] = "rust+c"
3206 os.environ["HGMODULEPOLICY"] = "rust+c"
3206 if self.options.no_rust:
3207 if self.options.no_rust:
3207 current_policy = os.environ.get("HGMODULEPOLICY", "")
3208 current_policy = os.environ.get("HGMODULEPOLICY", "")
3208 if current_policy.startswith("rust+"):
3209 if current_policy.startswith("rust+"):
3209 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3210 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3210 os.environ.pop("HGWITHRUSTEXT", None)
3211 os.environ.pop("HGWITHRUSTEXT", None)
3211
3212
3212 if self.options.allow_slow_tests:
3213 if self.options.allow_slow_tests:
3213 os.environ["HGTEST_SLOW"] = "slow"
3214 os.environ["HGTEST_SLOW"] = "slow"
3214 elif 'HGTEST_SLOW' in os.environ:
3215 elif 'HGTEST_SLOW' in os.environ:
3215 del os.environ['HGTEST_SLOW']
3216 del os.environ['HGTEST_SLOW']
3216
3217
3217 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3218 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3218
3219
3219 if self.options.exceptions:
3220 if self.options.exceptions:
3220 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3221 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3221 try:
3222 try:
3222 os.makedirs(exceptionsdir)
3223 os.makedirs(exceptionsdir)
3223 except OSError as e:
3224 except OSError as e:
3224 if e.errno != errno.EEXIST:
3225 if e.errno != errno.EEXIST:
3225 raise
3226 raise
3226
3227
3227 # Remove all existing exception reports.
3228 # Remove all existing exception reports.
3228 for f in os.listdir(exceptionsdir):
3229 for f in os.listdir(exceptionsdir):
3229 os.unlink(os.path.join(exceptionsdir, f))
3230 os.unlink(os.path.join(exceptionsdir, f))
3230
3231
3231 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3232 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3232 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3233 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3233 self.options.extra_config_opt.append(
3234 self.options.extra_config_opt.append(
3234 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3235 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3235 )
3236 )
3236
3237
3237 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3238 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3238 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3239 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3239 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3240 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3240 vlog("# Using PATH", os.environ["PATH"])
3241 vlog("# Using PATH", os.environ["PATH"])
3241 vlog(
3242 vlog(
3242 "# Using",
3243 "# Using",
3243 _bytes2sys(IMPL_PATH),
3244 _bytes2sys(IMPL_PATH),
3244 _bytes2sys(osenvironb[IMPL_PATH]),
3245 _bytes2sys(osenvironb[IMPL_PATH]),
3245 )
3246 )
3246 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3247 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3247
3248
3248 try:
3249 try:
3249 return self._runtests(testdescs) or 0
3250 return self._runtests(testdescs) or 0
3250 finally:
3251 finally:
3251 time.sleep(0.1)
3252 time.sleep(0.1)
3252 self._cleanup()
3253 self._cleanup()
3253
3254
3254 def findtests(self, args):
3255 def findtests(self, args):
3255 """Finds possible test files from arguments.
3256 """Finds possible test files from arguments.
3256
3257
3257 If you wish to inject custom tests into the test harness, this would
3258 If you wish to inject custom tests into the test harness, this would
3258 be a good function to monkeypatch or override in a derived class.
3259 be a good function to monkeypatch or override in a derived class.
3259 """
3260 """
3260 if not args:
3261 if not args:
3261 if self.options.changed:
3262 if self.options.changed:
3262 proc = Popen4(
3263 proc = Popen4(
3263 b'hg st --rev "%s" -man0 .'
3264 b'hg st --rev "%s" -man0 .'
3264 % _sys2bytes(self.options.changed),
3265 % _sys2bytes(self.options.changed),
3265 None,
3266 None,
3266 0,
3267 0,
3267 )
3268 )
3268 stdout, stderr = proc.communicate()
3269 stdout, stderr = proc.communicate()
3269 args = stdout.strip(b'\0').split(b'\0')
3270 args = stdout.strip(b'\0').split(b'\0')
3270 else:
3271 else:
3271 args = os.listdir(b'.')
3272 args = os.listdir(b'.')
3272
3273
3273 expanded_args = []
3274 expanded_args = []
3274 for arg in args:
3275 for arg in args:
3275 if os.path.isdir(arg):
3276 if os.path.isdir(arg):
3276 if not arg.endswith(b'/'):
3277 if not arg.endswith(b'/'):
3277 arg += b'/'
3278 arg += b'/'
3278 expanded_args.extend([arg + a for a in os.listdir(arg)])
3279 expanded_args.extend([arg + a for a in os.listdir(arg)])
3279 else:
3280 else:
3280 expanded_args.append(arg)
3281 expanded_args.append(arg)
3281 args = expanded_args
3282 args = expanded_args
3282
3283
3283 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3284 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3284 tests = []
3285 tests = []
3285 for t in args:
3286 for t in args:
3286 case = []
3287 case = []
3287
3288
3288 if not (
3289 if not (
3289 os.path.basename(t).startswith(b'test-')
3290 os.path.basename(t).startswith(b'test-')
3290 and (t.endswith(b'.py') or t.endswith(b'.t'))
3291 and (t.endswith(b'.py') or t.endswith(b'.t'))
3291 ):
3292 ):
3292
3293
3293 m = testcasepattern.match(os.path.basename(t))
3294 m = testcasepattern.match(os.path.basename(t))
3294 if m is not None:
3295 if m is not None:
3295 t_basename, casestr = m.groups()
3296 t_basename, casestr = m.groups()
3296 t = os.path.join(os.path.dirname(t), t_basename)
3297 t = os.path.join(os.path.dirname(t), t_basename)
3297 if casestr:
3298 if casestr:
3298 case = casestr.split(b'#')
3299 case = casestr.split(b'#')
3299 else:
3300 else:
3300 continue
3301 continue
3301
3302
3302 if t.endswith(b'.t'):
3303 if t.endswith(b'.t'):
3303 # .t file may contain multiple test cases
3304 # .t file may contain multiple test cases
3304 casedimensions = parsettestcases(t)
3305 casedimensions = parsettestcases(t)
3305 if casedimensions:
3306 if casedimensions:
3306 cases = []
3307 cases = []
3307
3308
3308 def addcases(case, casedimensions):
3309 def addcases(case, casedimensions):
3309 if not casedimensions:
3310 if not casedimensions:
3310 cases.append(case)
3311 cases.append(case)
3311 else:
3312 else:
3312 for c in casedimensions[0]:
3313 for c in casedimensions[0]:
3313 addcases(case + [c], casedimensions[1:])
3314 addcases(case + [c], casedimensions[1:])
3314
3315
3315 addcases([], casedimensions)
3316 addcases([], casedimensions)
3316 if case and case in cases:
3317 if case and case in cases:
3317 cases = [case]
3318 cases = [case]
3318 elif case:
3319 elif case:
3319 # Ignore invalid cases
3320 # Ignore invalid cases
3320 cases = []
3321 cases = []
3321 else:
3322 else:
3322 pass
3323 pass
3323 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3324 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3324 else:
3325 else:
3325 tests.append({'path': t})
3326 tests.append({'path': t})
3326 else:
3327 else:
3327 tests.append({'path': t})
3328 tests.append({'path': t})
3328
3329
3329 if self.options.retest:
3330 if self.options.retest:
3330 retest_args = []
3331 retest_args = []
3331 for test in tests:
3332 for test in tests:
3332 errpath = self._geterrpath(test)
3333 errpath = self._geterrpath(test)
3333 if os.path.exists(errpath):
3334 if os.path.exists(errpath):
3334 retest_args.append(test)
3335 retest_args.append(test)
3335 tests = retest_args
3336 tests = retest_args
3336 return tests
3337 return tests
3337
3338
3338 def _runtests(self, testdescs):
3339 def _runtests(self, testdescs):
3339 def _reloadtest(test, i):
3340 def _reloadtest(test, i):
3340 # convert a test back to its description dict
3341 # convert a test back to its description dict
3341 desc = {'path': test.path}
3342 desc = {'path': test.path}
3342 case = getattr(test, '_case', [])
3343 case = getattr(test, '_case', [])
3343 if case:
3344 if case:
3344 desc['case'] = case
3345 desc['case'] = case
3345 return self._gettest(desc, i)
3346 return self._gettest(desc, i)
3346
3347
3347 try:
3348 try:
3348 if self.options.restart:
3349 if self.options.restart:
3349 orig = list(testdescs)
3350 orig = list(testdescs)
3350 while testdescs:
3351 while testdescs:
3351 desc = testdescs[0]
3352 desc = testdescs[0]
3352 errpath = self._geterrpath(desc)
3353 errpath = self._geterrpath(desc)
3353 if os.path.exists(errpath):
3354 if os.path.exists(errpath):
3354 break
3355 break
3355 testdescs.pop(0)
3356 testdescs.pop(0)
3356 if not testdescs:
3357 if not testdescs:
3357 print("running all tests")
3358 print("running all tests")
3358 testdescs = orig
3359 testdescs = orig
3359
3360
3360 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3361 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3361 num_tests = len(tests) * self.options.runs_per_test
3362 num_tests = len(tests) * self.options.runs_per_test
3362
3363
3363 jobs = min(num_tests, self.options.jobs)
3364 jobs = min(num_tests, self.options.jobs)
3364
3365
3365 failed = False
3366 failed = False
3366 kws = self.options.keywords
3367 kws = self.options.keywords
3367 if kws is not None and PYTHON3:
3368 if kws is not None and PYTHON3:
3368 kws = kws.encode('utf-8')
3369 kws = kws.encode('utf-8')
3369
3370
3370 suite = TestSuite(
3371 suite = TestSuite(
3371 self._testdir,
3372 self._testdir,
3372 jobs=jobs,
3373 jobs=jobs,
3373 whitelist=self.options.whitelisted,
3374 whitelist=self.options.whitelisted,
3374 blacklist=self.options.blacklist,
3375 blacklist=self.options.blacklist,
3375 keywords=kws,
3376 keywords=kws,
3376 loop=self.options.loop,
3377 loop=self.options.loop,
3377 runs_per_test=self.options.runs_per_test,
3378 runs_per_test=self.options.runs_per_test,
3378 showchannels=self.options.showchannels,
3379 showchannels=self.options.showchannels,
3379 tests=tests,
3380 tests=tests,
3380 loadtest=_reloadtest,
3381 loadtest=_reloadtest,
3381 )
3382 )
3382 verbosity = 1
3383 verbosity = 1
3383 if self.options.list_tests:
3384 if self.options.list_tests:
3384 verbosity = 0
3385 verbosity = 0
3385 elif self.options.verbose:
3386 elif self.options.verbose:
3386 verbosity = 2
3387 verbosity = 2
3387 runner = TextTestRunner(self, verbosity=verbosity)
3388 runner = TextTestRunner(self, verbosity=verbosity)
3388
3389
3389 if self.options.list_tests:
3390 if self.options.list_tests:
3390 result = runner.listtests(suite)
3391 result = runner.listtests(suite)
3391 else:
3392 else:
3392 if self._installdir:
3393 if self._installdir:
3393 self._installhg()
3394 self._installhg()
3394 self._checkhglib("Testing")
3395 self._checkhglib("Testing")
3395 else:
3396 else:
3396 self._usecorrectpython()
3397 self._usecorrectpython()
3397 if self.options.chg:
3398 if self.options.chg:
3398 assert self._installdir
3399 assert self._installdir
3399 self._installchg()
3400 self._installchg()
3400 if self.options.rhg:
3401 if self.options.rhg:
3401 assert self._installdir
3402 assert self._installdir
3402 self._installrhg()
3403 self._installrhg()
3403
3404
3404 log(
3405 log(
3405 'running %d tests using %d parallel processes'
3406 'running %d tests using %d parallel processes'
3406 % (num_tests, jobs)
3407 % (num_tests, jobs)
3407 )
3408 )
3408
3409
3409 result = runner.run(suite)
3410 result = runner.run(suite)
3410
3411
3411 if result.failures or result.errors:
3412 if result.failures or result.errors:
3412 failed = True
3413 failed = True
3413
3414
3414 result.onEnd()
3415 result.onEnd()
3415
3416
3416 if self.options.anycoverage:
3417 if self.options.anycoverage:
3417 self._outputcoverage()
3418 self._outputcoverage()
3418 except KeyboardInterrupt:
3419 except KeyboardInterrupt:
3419 failed = True
3420 failed = True
3420 print("\ninterrupted!")
3421 print("\ninterrupted!")
3421
3422
3422 if failed:
3423 if failed:
3423 return 1
3424 return 1
3424
3425
3425 def _geterrpath(self, test):
3426 def _geterrpath(self, test):
3426 # test['path'] is a relative path
3427 # test['path'] is a relative path
3427 if 'case' in test:
3428 if 'case' in test:
3428 # for multiple dimensions test cases
3429 # for multiple dimensions test cases
3429 casestr = b'#'.join(test['case'])
3430 casestr = b'#'.join(test['case'])
3430 errpath = b'%s#%s.err' % (test['path'], casestr)
3431 errpath = b'%s#%s.err' % (test['path'], casestr)
3431 else:
3432 else:
3432 errpath = b'%s.err' % test['path']
3433 errpath = b'%s.err' % test['path']
3433 if self.options.outputdir:
3434 if self.options.outputdir:
3434 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3435 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3435 errpath = os.path.join(self._outputdir, errpath)
3436 errpath = os.path.join(self._outputdir, errpath)
3436 return errpath
3437 return errpath
3437
3438
3438 def _getport(self, count):
3439 def _getport(self, count):
3439 port = self._ports.get(count) # do we have a cached entry?
3440 port = self._ports.get(count) # do we have a cached entry?
3440 if port is None:
3441 if port is None:
3441 portneeded = 3
3442 portneeded = 3
3442 # above 100 tries we just give up and let test reports failure
3443 # above 100 tries we just give up and let test reports failure
3443 for tries in xrange(100):
3444 for tries in xrange(100):
3444 allfree = True
3445 allfree = True
3445 port = self.options.port + self._portoffset
3446 port = self.options.port + self._portoffset
3446 for idx in xrange(portneeded):
3447 for idx in xrange(portneeded):
3447 if not checkportisavailable(port + idx):
3448 if not checkportisavailable(port + idx):
3448 allfree = False
3449 allfree = False
3449 break
3450 break
3450 self._portoffset += portneeded
3451 self._portoffset += portneeded
3451 if allfree:
3452 if allfree:
3452 break
3453 break
3453 self._ports[count] = port
3454 self._ports[count] = port
3454 return port
3455 return port
3455
3456
3456 def _gettest(self, testdesc, count):
3457 def _gettest(self, testdesc, count):
3457 """Obtain a Test by looking at its filename.
3458 """Obtain a Test by looking at its filename.
3458
3459
3459 Returns a Test instance. The Test may not be runnable if it doesn't
3460 Returns a Test instance. The Test may not be runnable if it doesn't
3460 map to a known type.
3461 map to a known type.
3461 """
3462 """
3462 path = testdesc['path']
3463 path = testdesc['path']
3463 lctest = path.lower()
3464 lctest = path.lower()
3464 testcls = Test
3465 testcls = Test
3465
3466
3466 for ext, cls in self.TESTTYPES:
3467 for ext, cls in self.TESTTYPES:
3467 if lctest.endswith(ext):
3468 if lctest.endswith(ext):
3468 testcls = cls
3469 testcls = cls
3469 break
3470 break
3470
3471
3471 refpath = os.path.join(getcwdb(), path)
3472 refpath = os.path.join(getcwdb(), path)
3472 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3473 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3473
3474
3474 # extra keyword parameters. 'case' is used by .t tests
3475 # extra keyword parameters. 'case' is used by .t tests
3475 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3476 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3476
3477
3477 t = testcls(
3478 t = testcls(
3478 refpath,
3479 refpath,
3479 self._outputdir,
3480 self._outputdir,
3480 tmpdir,
3481 tmpdir,
3481 keeptmpdir=self.options.keep_tmpdir,
3482 keeptmpdir=self.options.keep_tmpdir,
3482 debug=self.options.debug,
3483 debug=self.options.debug,
3483 first=self.options.first,
3484 first=self.options.first,
3484 timeout=self.options.timeout,
3485 timeout=self.options.timeout,
3485 startport=self._getport(count),
3486 startport=self._getport(count),
3486 extraconfigopts=self.options.extra_config_opt,
3487 extraconfigopts=self.options.extra_config_opt,
3487 shell=self.options.shell,
3488 shell=self.options.shell,
3488 hgcommand=self._hgcommand,
3489 hgcommand=self._hgcommand,
3489 usechg=bool(self.options.with_chg or self.options.chg),
3490 usechg=bool(self.options.with_chg or self.options.chg),
3490 chgdebug=self.options.chg_debug,
3491 chgdebug=self.options.chg_debug,
3491 useipv6=useipv6,
3492 useipv6=useipv6,
3492 **kwds
3493 **kwds
3493 )
3494 )
3494 t.should_reload = True
3495 t.should_reload = True
3495 return t
3496 return t
3496
3497
3497 def _cleanup(self):
3498 def _cleanup(self):
3498 """Clean up state from this test invocation."""
3499 """Clean up state from this test invocation."""
3499 if self.options.keep_tmpdir:
3500 if self.options.keep_tmpdir:
3500 return
3501 return
3501
3502
3502 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3503 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3503 shutil.rmtree(self._hgtmp, True)
3504 shutil.rmtree(self._hgtmp, True)
3504 for f in self._createdfiles:
3505 for f in self._createdfiles:
3505 try:
3506 try:
3506 os.remove(f)
3507 os.remove(f)
3507 except OSError:
3508 except OSError:
3508 pass
3509 pass
3509
3510
3510 def _usecorrectpython(self):
3511 def _usecorrectpython(self):
3511 """Configure the environment to use the appropriate Python in tests."""
3512 """Configure the environment to use the appropriate Python in tests."""
3512 # Tests must use the same interpreter as us or bad things will happen.
3513 # Tests must use the same interpreter as us or bad things will happen.
3513 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3514 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3514
3515
3515 # os.symlink() is a thing with py3 on Windows, but it requires
3516 # os.symlink() is a thing with py3 on Windows, but it requires
3516 # Administrator rights.
3517 # Administrator rights.
3517 if getattr(os, 'symlink', None) and os.name != 'nt':
3518 if getattr(os, 'symlink', None) and os.name != 'nt':
3518 vlog(
3519 vlog(
3519 "# Making python executable in test path a symlink to '%s'"
3520 "# Making python executable in test path a symlink to '%s'"
3520 % sysexecutable
3521 % sysexecutable
3521 )
3522 )
3522 mypython = os.path.join(self._tmpbindir, pyexename)
3523 mypython = os.path.join(self._tmpbindir, pyexename)
3523 try:
3524 try:
3524 if os.readlink(mypython) == sysexecutable:
3525 if os.readlink(mypython) == sysexecutable:
3525 return
3526 return
3526 os.unlink(mypython)
3527 os.unlink(mypython)
3527 except OSError as err:
3528 except OSError as err:
3528 if err.errno != errno.ENOENT:
3529 if err.errno != errno.ENOENT:
3529 raise
3530 raise
3530 if self._findprogram(pyexename) != sysexecutable:
3531 if self._findprogram(pyexename) != sysexecutable:
3531 try:
3532 try:
3532 os.symlink(sysexecutable, mypython)
3533 os.symlink(sysexecutable, mypython)
3533 self._createdfiles.append(mypython)
3534 self._createdfiles.append(mypython)
3534 except OSError as err:
3535 except OSError as err:
3535 # child processes may race, which is harmless
3536 # child processes may race, which is harmless
3536 if err.errno != errno.EEXIST:
3537 if err.errno != errno.EEXIST:
3537 raise
3538 raise
3538 else:
3539 else:
3539 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3540 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3540 # reparse point with that name provided by Microsoft. Create a
3541 # reparse point with that name provided by Microsoft. Create a
3541 # simple script on PATH with that name that delegates to the py3
3542 # simple script on PATH with that name that delegates to the py3
3542 # launcher so the shebang lines work.
3543 # launcher so the shebang lines work.
3543 if os.getenv('MSYSTEM'):
3544 if os.getenv('MSYSTEM'):
3544 with open(osenvironb[b'RUNTESTDIR'] + b'/python3', 'wb') as f:
3545 with open(osenvironb[b'RUNTESTDIR'] + b'/python3', 'wb') as f:
3545 f.write(b'#!/bin/sh\n')
3546 f.write(b'#!/bin/sh\n')
3546 f.write(b'py -3 "$@"\n')
3547 f.write(b'py -3 "$@"\n')
3547
3548
3548 exedir, exename = os.path.split(sysexecutable)
3549 exedir, exename = os.path.split(sysexecutable)
3549 vlog(
3550 vlog(
3550 "# Modifying search path to find %s as %s in '%s'"
3551 "# Modifying search path to find %s as %s in '%s'"
3551 % (exename, pyexename, exedir)
3552 % (exename, pyexename, exedir)
3552 )
3553 )
3553 path = os.environ['PATH'].split(os.pathsep)
3554 path = os.environ['PATH'].split(os.pathsep)
3554 while exedir in path:
3555 while exedir in path:
3555 path.remove(exedir)
3556 path.remove(exedir)
3556
3557
3557 # Binaries installed by pip into the user area like pylint.exe may
3558 # Binaries installed by pip into the user area like pylint.exe may
3558 # not be in PATH by default.
3559 # not be in PATH by default.
3559 extra_paths = [exedir]
3560 extra_paths = [exedir]
3560 vi = sys.version_info
3561 vi = sys.version_info
3561 if 'APPDATA' in os.environ:
3562 if 'APPDATA' in os.environ:
3562 scripts_dir = os.path.join(
3563 scripts_dir = os.path.join(
3563 os.environ['APPDATA'],
3564 os.environ['APPDATA'],
3564 'Python',
3565 'Python',
3565 'Python%d%d' % (vi[0], vi[1]),
3566 'Python%d%d' % (vi[0], vi[1]),
3566 'Scripts',
3567 'Scripts',
3567 )
3568 )
3568
3569
3569 if vi.major == 2:
3570 if vi.major == 2:
3570 scripts_dir = os.path.join(
3571 scripts_dir = os.path.join(
3571 os.environ['APPDATA'],
3572 os.environ['APPDATA'],
3572 'Python',
3573 'Python',
3573 'Scripts',
3574 'Scripts',
3574 )
3575 )
3575
3576
3576 extra_paths.append(scripts_dir)
3577 extra_paths.append(scripts_dir)
3577
3578
3578 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3579 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3579 if not self._findprogram(pyexename):
3580 if not self._findprogram(pyexename):
3580 print("WARNING: Cannot find %s in search path" % pyexename)
3581 print("WARNING: Cannot find %s in search path" % pyexename)
3581
3582
3582 def _installhg(self):
3583 def _installhg(self):
3583 """Install hg into the test environment.
3584 """Install hg into the test environment.
3584
3585
3585 This will also configure hg with the appropriate testing settings.
3586 This will also configure hg with the appropriate testing settings.
3586 """
3587 """
3587 vlog("# Performing temporary installation of HG")
3588 vlog("# Performing temporary installation of HG")
3588 installerrs = os.path.join(self._hgtmp, b"install.err")
3589 installerrs = os.path.join(self._hgtmp, b"install.err")
3589 compiler = ''
3590 compiler = ''
3590 if self.options.compiler:
3591 if self.options.compiler:
3591 compiler = '--compiler ' + self.options.compiler
3592 compiler = '--compiler ' + self.options.compiler
3592 setup_opts = b""
3593 setup_opts = b""
3593 if self.options.pure:
3594 if self.options.pure:
3594 setup_opts = b"--pure"
3595 setup_opts = b"--pure"
3595 elif self.options.rust:
3596 elif self.options.rust:
3596 setup_opts = b"--rust"
3597 setup_opts = b"--rust"
3597 elif self.options.no_rust:
3598 elif self.options.no_rust:
3598 setup_opts = b"--no-rust"
3599 setup_opts = b"--no-rust"
3599
3600
3600 # Run installer in hg root
3601 # Run installer in hg root
3601 script = os.path.realpath(sys.argv[0])
3602 script = os.path.realpath(sys.argv[0])
3602 exe = sysexecutable
3603 exe = sysexecutable
3603 if PYTHON3:
3604 if PYTHON3:
3604 compiler = _sys2bytes(compiler)
3605 compiler = _sys2bytes(compiler)
3605 script = _sys2bytes(script)
3606 script = _sys2bytes(script)
3606 exe = _sys2bytes(exe)
3607 exe = _sys2bytes(exe)
3607 hgroot = os.path.dirname(os.path.dirname(script))
3608 hgroot = os.path.dirname(os.path.dirname(script))
3608 self._hgroot = hgroot
3609 self._hgroot = hgroot
3609 os.chdir(hgroot)
3610 os.chdir(hgroot)
3610 nohome = b'--home=""'
3611 nohome = b'--home=""'
3611 if os.name == 'nt':
3612 if os.name == 'nt':
3612 # The --home="" trick works only on OS where os.sep == '/'
3613 # The --home="" trick works only on OS where os.sep == '/'
3613 # because of a distutils convert_path() fast-path. Avoid it at
3614 # because of a distutils convert_path() fast-path. Avoid it at
3614 # least on Windows for now, deal with .pydistutils.cfg bugs
3615 # least on Windows for now, deal with .pydistutils.cfg bugs
3615 # when they happen.
3616 # when they happen.
3616 nohome = b''
3617 nohome = b''
3617 cmd = (
3618 cmd = (
3618 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3619 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3619 b' build %(compiler)s --build-base="%(base)s"'
3620 b' build %(compiler)s --build-base="%(base)s"'
3620 b' install --force --prefix="%(prefix)s"'
3621 b' install --force --prefix="%(prefix)s"'
3621 b' --install-lib="%(libdir)s"'
3622 b' --install-lib="%(libdir)s"'
3622 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3623 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3623 % {
3624 % {
3624 b'exe': exe,
3625 b'exe': exe,
3625 b'setup_opts': setup_opts,
3626 b'setup_opts': setup_opts,
3626 b'compiler': compiler,
3627 b'compiler': compiler,
3627 b'base': os.path.join(self._hgtmp, b"build"),
3628 b'base': os.path.join(self._hgtmp, b"build"),
3628 b'prefix': self._installdir,
3629 b'prefix': self._installdir,
3629 b'libdir': self._pythondir,
3630 b'libdir': self._pythondir,
3630 b'bindir': self._bindir,
3631 b'bindir': self._bindir,
3631 b'nohome': nohome,
3632 b'nohome': nohome,
3632 b'logfile': installerrs,
3633 b'logfile': installerrs,
3633 }
3634 }
3634 )
3635 )
3635
3636
3636 # setuptools requires install directories to exist.
3637 # setuptools requires install directories to exist.
3637 def makedirs(p):
3638 def makedirs(p):
3638 try:
3639 try:
3639 os.makedirs(p)
3640 os.makedirs(p)
3640 except OSError as e:
3641 except OSError as e:
3641 if e.errno != errno.EEXIST:
3642 if e.errno != errno.EEXIST:
3642 raise
3643 raise
3643
3644
3644 makedirs(self._pythondir)
3645 makedirs(self._pythondir)
3645 makedirs(self._bindir)
3646 makedirs(self._bindir)
3646
3647
3647 vlog("# Running", cmd.decode("utf-8"))
3648 vlog("# Running", cmd.decode("utf-8"))
3648 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3649 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3649 if not self.options.verbose:
3650 if not self.options.verbose:
3650 try:
3651 try:
3651 os.remove(installerrs)
3652 os.remove(installerrs)
3652 except OSError as e:
3653 except OSError as e:
3653 if e.errno != errno.ENOENT:
3654 if e.errno != errno.ENOENT:
3654 raise
3655 raise
3655 else:
3656 else:
3656 with open(installerrs, 'rb') as f:
3657 with open(installerrs, 'rb') as f:
3657 for line in f:
3658 for line in f:
3658 if PYTHON3:
3659 if PYTHON3:
3659 sys.stdout.buffer.write(line)
3660 sys.stdout.buffer.write(line)
3660 else:
3661 else:
3661 sys.stdout.write(line)
3662 sys.stdout.write(line)
3662 sys.exit(1)
3663 sys.exit(1)
3663 os.chdir(self._testdir)
3664 os.chdir(self._testdir)
3664
3665
3665 self._usecorrectpython()
3666 self._usecorrectpython()
3666
3667
3667 hgbat = os.path.join(self._bindir, b'hg.bat')
3668 hgbat = os.path.join(self._bindir, b'hg.bat')
3668 if os.path.isfile(hgbat):
3669 if os.path.isfile(hgbat):
3669 # hg.bat expects to be put in bin/scripts while run-tests.py
3670 # hg.bat expects to be put in bin/scripts while run-tests.py
3670 # installation layout put it in bin/ directly. Fix it
3671 # installation layout put it in bin/ directly. Fix it
3671 with open(hgbat, 'rb') as f:
3672 with open(hgbat, 'rb') as f:
3672 data = f.read()
3673 data = f.read()
3673 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3674 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3674 data = data.replace(
3675 data = data.replace(
3675 br'"%~dp0..\python" "%~dp0hg" %*',
3676 br'"%~dp0..\python" "%~dp0hg" %*',
3676 b'"%~dp0python" "%~dp0hg" %*',
3677 b'"%~dp0python" "%~dp0hg" %*',
3677 )
3678 )
3678 with open(hgbat, 'wb') as f:
3679 with open(hgbat, 'wb') as f:
3679 f.write(data)
3680 f.write(data)
3680 else:
3681 else:
3681 print('WARNING: cannot fix hg.bat reference to python.exe')
3682 print('WARNING: cannot fix hg.bat reference to python.exe')
3682
3683
3683 if self.options.anycoverage:
3684 if self.options.anycoverage:
3684 custom = os.path.join(
3685 custom = os.path.join(
3685 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3686 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3686 )
3687 )
3687 target = os.path.join(self._pythondir, b'sitecustomize.py')
3688 target = os.path.join(self._pythondir, b'sitecustomize.py')
3688 vlog('# Installing coverage trigger to %s' % target)
3689 vlog('# Installing coverage trigger to %s' % target)
3689 shutil.copyfile(custom, target)
3690 shutil.copyfile(custom, target)
3690 rc = os.path.join(self._testdir, b'.coveragerc')
3691 rc = os.path.join(self._testdir, b'.coveragerc')
3691 vlog('# Installing coverage rc to %s' % rc)
3692 vlog('# Installing coverage rc to %s' % rc)
3692 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3693 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3693 covdir = os.path.join(self._installdir, b'..', b'coverage')
3694 covdir = os.path.join(self._installdir, b'..', b'coverage')
3694 try:
3695 try:
3695 os.mkdir(covdir)
3696 os.mkdir(covdir)
3696 except OSError as e:
3697 except OSError as e:
3697 if e.errno != errno.EEXIST:
3698 if e.errno != errno.EEXIST:
3698 raise
3699 raise
3699
3700
3700 osenvironb[b'COVERAGE_DIR'] = covdir
3701 osenvironb[b'COVERAGE_DIR'] = covdir
3701
3702
3702 def _checkhglib(self, verb):
3703 def _checkhglib(self, verb):
3703 """Ensure that the 'mercurial' package imported by python is
3704 """Ensure that the 'mercurial' package imported by python is
3704 the one we expect it to be. If not, print a warning to stderr."""
3705 the one we expect it to be. If not, print a warning to stderr."""
3705 if (self._bindir == self._pythondir) and (
3706 if (self._bindir == self._pythondir) and (
3706 self._bindir != self._tmpbindir
3707 self._bindir != self._tmpbindir
3707 ):
3708 ):
3708 # The pythondir has been inferred from --with-hg flag.
3709 # The pythondir has been inferred from --with-hg flag.
3709 # We cannot expect anything sensible here.
3710 # We cannot expect anything sensible here.
3710 return
3711 return
3711 expecthg = os.path.join(self._pythondir, b'mercurial')
3712 expecthg = os.path.join(self._pythondir, b'mercurial')
3712 actualhg = self._gethgpath()
3713 actualhg = self._gethgpath()
3713 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3714 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3714 sys.stderr.write(
3715 sys.stderr.write(
3715 'warning: %s with unexpected mercurial lib: %s\n'
3716 'warning: %s with unexpected mercurial lib: %s\n'
3716 ' (expected %s)\n' % (verb, actualhg, expecthg)
3717 ' (expected %s)\n' % (verb, actualhg, expecthg)
3717 )
3718 )
3718
3719
3719 def _gethgpath(self):
3720 def _gethgpath(self):
3720 """Return the path to the mercurial package that is actually found by
3721 """Return the path to the mercurial package that is actually found by
3721 the current Python interpreter."""
3722 the current Python interpreter."""
3722 if self._hgpath is not None:
3723 if self._hgpath is not None:
3723 return self._hgpath
3724 return self._hgpath
3724
3725
3725 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3726 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3726 cmd = cmd % PYTHON
3727 cmd = cmd % PYTHON
3727 if PYTHON3:
3728 if PYTHON3:
3728 cmd = _bytes2sys(cmd)
3729 cmd = _bytes2sys(cmd)
3729
3730
3730 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3731 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3731 out, err = p.communicate()
3732 out, err = p.communicate()
3732
3733
3733 self._hgpath = out.strip()
3734 self._hgpath = out.strip()
3734
3735
3735 return self._hgpath
3736 return self._hgpath
3736
3737
3737 def _installchg(self):
3738 def _installchg(self):
3738 """Install chg into the test environment"""
3739 """Install chg into the test environment"""
3739 vlog('# Performing temporary installation of CHG')
3740 vlog('# Performing temporary installation of CHG')
3740 assert os.path.dirname(self._bindir) == self._installdir
3741 assert os.path.dirname(self._bindir) == self._installdir
3741 assert self._hgroot, 'must be called after _installhg()'
3742 assert self._hgroot, 'must be called after _installhg()'
3742 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3743 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3743 b'make': b'make', # TODO: switch by option or environment?
3744 b'make': b'make', # TODO: switch by option or environment?
3744 b'prefix': self._installdir,
3745 b'prefix': self._installdir,
3745 }
3746 }
3746 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3747 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3747 vlog("# Running", cmd)
3748 vlog("# Running", cmd)
3748 proc = subprocess.Popen(
3749 proc = subprocess.Popen(
3749 cmd,
3750 cmd,
3750 shell=True,
3751 shell=True,
3751 cwd=cwd,
3752 cwd=cwd,
3752 stdin=subprocess.PIPE,
3753 stdin=subprocess.PIPE,
3753 stdout=subprocess.PIPE,
3754 stdout=subprocess.PIPE,
3754 stderr=subprocess.STDOUT,
3755 stderr=subprocess.STDOUT,
3755 )
3756 )
3756 out, _err = proc.communicate()
3757 out, _err = proc.communicate()
3757 if proc.returncode != 0:
3758 if proc.returncode != 0:
3758 if PYTHON3:
3759 if PYTHON3:
3759 sys.stdout.buffer.write(out)
3760 sys.stdout.buffer.write(out)
3760 else:
3761 else:
3761 sys.stdout.write(out)
3762 sys.stdout.write(out)
3762 sys.exit(1)
3763 sys.exit(1)
3763
3764
3764 def _installrhg(self):
3765 def _installrhg(self):
3765 """Install rhg into the test environment"""
3766 """Install rhg into the test environment"""
3766 vlog('# Performing temporary installation of rhg')
3767 vlog('# Performing temporary installation of rhg')
3767 assert os.path.dirname(self._bindir) == self._installdir
3768 assert os.path.dirname(self._bindir) == self._installdir
3768 assert self._hgroot, 'must be called after _installhg()'
3769 assert self._hgroot, 'must be called after _installhg()'
3769 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3770 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3770 b'make': b'make', # TODO: switch by option or environment?
3771 b'make': b'make', # TODO: switch by option or environment?
3771 b'prefix': self._installdir,
3772 b'prefix': self._installdir,
3772 }
3773 }
3773 cwd = self._hgroot
3774 cwd = self._hgroot
3774 vlog("# Running", cmd)
3775 vlog("# Running", cmd)
3775 proc = subprocess.Popen(
3776 proc = subprocess.Popen(
3776 cmd,
3777 cmd,
3777 shell=True,
3778 shell=True,
3778 cwd=cwd,
3779 cwd=cwd,
3779 stdin=subprocess.PIPE,
3780 stdin=subprocess.PIPE,
3780 stdout=subprocess.PIPE,
3781 stdout=subprocess.PIPE,
3781 stderr=subprocess.STDOUT,
3782 stderr=subprocess.STDOUT,
3782 )
3783 )
3783 out, _err = proc.communicate()
3784 out, _err = proc.communicate()
3784 if proc.returncode != 0:
3785 if proc.returncode != 0:
3785 if PYTHON3:
3786 if PYTHON3:
3786 sys.stdout.buffer.write(out)
3787 sys.stdout.buffer.write(out)
3787 else:
3788 else:
3788 sys.stdout.write(out)
3789 sys.stdout.write(out)
3789 sys.exit(1)
3790 sys.exit(1)
3790
3791
3791 def _outputcoverage(self):
3792 def _outputcoverage(self):
3792 """Produce code coverage output."""
3793 """Produce code coverage output."""
3793 import coverage
3794 import coverage
3794
3795
3795 coverage = coverage.coverage
3796 coverage = coverage.coverage
3796
3797
3797 vlog('# Producing coverage report')
3798 vlog('# Producing coverage report')
3798 # chdir is the easiest way to get short, relative paths in the
3799 # chdir is the easiest way to get short, relative paths in the
3799 # output.
3800 # output.
3800 os.chdir(self._hgroot)
3801 os.chdir(self._hgroot)
3801 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3802 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3802 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3803 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3803
3804
3804 # Map install directory paths back to source directory.
3805 # Map install directory paths back to source directory.
3805 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3806 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3806
3807
3807 cov.combine()
3808 cov.combine()
3808
3809
3809 omit = [
3810 omit = [
3810 _bytes2sys(os.path.join(x, b'*'))
3811 _bytes2sys(os.path.join(x, b'*'))
3811 for x in [self._bindir, self._testdir]
3812 for x in [self._bindir, self._testdir]
3812 ]
3813 ]
3813 cov.report(ignore_errors=True, omit=omit)
3814 cov.report(ignore_errors=True, omit=omit)
3814
3815
3815 if self.options.htmlcov:
3816 if self.options.htmlcov:
3816 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3817 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3817 cov.html_report(directory=htmldir, omit=omit)
3818 cov.html_report(directory=htmldir, omit=omit)
3818 if self.options.annotate:
3819 if self.options.annotate:
3819 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3820 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3820 if not os.path.isdir(adir):
3821 if not os.path.isdir(adir):
3821 os.mkdir(adir)
3822 os.mkdir(adir)
3822 cov.annotate(directory=adir, omit=omit)
3823 cov.annotate(directory=adir, omit=omit)
3823
3824
3824 def _findprogram(self, program):
3825 def _findprogram(self, program):
3825 """Search PATH for a executable program"""
3826 """Search PATH for a executable program"""
3826 dpb = _sys2bytes(os.defpath)
3827 dpb = _sys2bytes(os.defpath)
3827 sepb = _sys2bytes(os.pathsep)
3828 sepb = _sys2bytes(os.pathsep)
3828 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3829 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3829 name = os.path.join(p, program)
3830 name = os.path.join(p, program)
3830 if os.name == 'nt' or os.access(name, os.X_OK):
3831 if os.name == 'nt' or os.access(name, os.X_OK):
3831 return _bytes2sys(name)
3832 return _bytes2sys(name)
3832 return None
3833 return None
3833
3834
3834 def _checktools(self):
3835 def _checktools(self):
3835 """Ensure tools required to run tests are present."""
3836 """Ensure tools required to run tests are present."""
3836 for p in self.REQUIREDTOOLS:
3837 for p in self.REQUIREDTOOLS:
3837 if os.name == 'nt' and not p.endswith(b'.exe'):
3838 if os.name == 'nt' and not p.endswith(b'.exe'):
3838 p += b'.exe'
3839 p += b'.exe'
3839 found = self._findprogram(p)
3840 found = self._findprogram(p)
3840 p = p.decode("utf-8")
3841 p = p.decode("utf-8")
3841 if found:
3842 if found:
3842 vlog("# Found prerequisite", p, "at", found)
3843 vlog("# Found prerequisite", p, "at", found)
3843 else:
3844 else:
3844 print("WARNING: Did not find prerequisite tool: %s " % p)
3845 print("WARNING: Did not find prerequisite tool: %s " % p)
3845
3846
3846
3847
3847 def aggregateexceptions(path):
3848 def aggregateexceptions(path):
3848 exceptioncounts = collections.Counter()
3849 exceptioncounts = collections.Counter()
3849 testsbyfailure = collections.defaultdict(set)
3850 testsbyfailure = collections.defaultdict(set)
3850 failuresbytest = collections.defaultdict(set)
3851 failuresbytest = collections.defaultdict(set)
3851
3852
3852 for f in os.listdir(path):
3853 for f in os.listdir(path):
3853 with open(os.path.join(path, f), 'rb') as fh:
3854 with open(os.path.join(path, f), 'rb') as fh:
3854 data = fh.read().split(b'\0')
3855 data = fh.read().split(b'\0')
3855 if len(data) != 5:
3856 if len(data) != 5:
3856 continue
3857 continue
3857
3858
3858 exc, mainframe, hgframe, hgline, testname = data
3859 exc, mainframe, hgframe, hgline, testname = data
3859 exc = exc.decode('utf-8')
3860 exc = exc.decode('utf-8')
3860 mainframe = mainframe.decode('utf-8')
3861 mainframe = mainframe.decode('utf-8')
3861 hgframe = hgframe.decode('utf-8')
3862 hgframe = hgframe.decode('utf-8')
3862 hgline = hgline.decode('utf-8')
3863 hgline = hgline.decode('utf-8')
3863 testname = testname.decode('utf-8')
3864 testname = testname.decode('utf-8')
3864
3865
3865 key = (hgframe, hgline, exc)
3866 key = (hgframe, hgline, exc)
3866 exceptioncounts[key] += 1
3867 exceptioncounts[key] += 1
3867 testsbyfailure[key].add(testname)
3868 testsbyfailure[key].add(testname)
3868 failuresbytest[testname].add(key)
3869 failuresbytest[testname].add(key)
3869
3870
3870 # Find test having fewest failures for each failure.
3871 # Find test having fewest failures for each failure.
3871 leastfailing = {}
3872 leastfailing = {}
3872 for key, tests in testsbyfailure.items():
3873 for key, tests in testsbyfailure.items():
3873 fewesttest = None
3874 fewesttest = None
3874 fewestcount = 99999999
3875 fewestcount = 99999999
3875 for test in sorted(tests):
3876 for test in sorted(tests):
3876 if len(failuresbytest[test]) < fewestcount:
3877 if len(failuresbytest[test]) < fewestcount:
3877 fewesttest = test
3878 fewesttest = test
3878 fewestcount = len(failuresbytest[test])
3879 fewestcount = len(failuresbytest[test])
3879
3880
3880 leastfailing[key] = (fewestcount, fewesttest)
3881 leastfailing[key] = (fewestcount, fewesttest)
3881
3882
3882 # Create a combined counter so we can sort by total occurrences and
3883 # Create a combined counter so we can sort by total occurrences and
3883 # impacted tests.
3884 # impacted tests.
3884 combined = {}
3885 combined = {}
3885 for key in exceptioncounts:
3886 for key in exceptioncounts:
3886 combined[key] = (
3887 combined[key] = (
3887 exceptioncounts[key],
3888 exceptioncounts[key],
3888 len(testsbyfailure[key]),
3889 len(testsbyfailure[key]),
3889 leastfailing[key][0],
3890 leastfailing[key][0],
3890 leastfailing[key][1],
3891 leastfailing[key][1],
3891 )
3892 )
3892
3893
3893 return {
3894 return {
3894 'exceptioncounts': exceptioncounts,
3895 'exceptioncounts': exceptioncounts,
3895 'total': sum(exceptioncounts.values()),
3896 'total': sum(exceptioncounts.values()),
3896 'combined': combined,
3897 'combined': combined,
3897 'leastfailing': leastfailing,
3898 'leastfailing': leastfailing,
3898 'byfailure': testsbyfailure,
3899 'byfailure': testsbyfailure,
3899 'bytest': failuresbytest,
3900 'bytest': failuresbytest,
3900 }
3901 }
3901
3902
3902
3903
3903 if __name__ == '__main__':
3904 if __name__ == '__main__':
3904 runner = TestRunner()
3905 runner = TestRunner()
3905
3906
3906 try:
3907 try:
3907 import msvcrt
3908 import msvcrt
3908
3909
3909 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3910 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3910 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3911 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3911 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3912 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3912 except ImportError:
3913 except ImportError:
3913 pass
3914 pass
3914
3915
3915 sys.exit(runner.run(sys.argv[1:]))
3916 sys.exit(runner.run(sys.argv[1:]))
@@ -1,2034 +1,2038 b''
1 This file tests the behavior of run-tests.py itself.
1 This file tests the behavior of run-tests.py itself.
2
2
3 Avoid interference from actual test env:
3 Avoid interference from actual test env:
4
4
5 $ . "$TESTDIR/helper-runtests.sh"
5 $ . "$TESTDIR/helper-runtests.sh"
6
6
7 Smoke test with install
7 Smoke test with install
8 ============
8 ============
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
10 running 0 tests using 0 parallel processes
10 running 0 tests using 0 parallel processes
11
11
12 # Ran 0 tests, 0 skipped, 0 failed.
12 # Ran 0 tests, 0 skipped, 0 failed.
13
13
14 Define a helper to avoid the install step
14 Define a helper to avoid the install step
15 =============
15 =============
16 $ rt()
16 $ rt()
17 > {
17 > {
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@"
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@"
19 > }
19 > }
20
20
21 error paths
21 error paths
22
22
23 #if symlink
23 #if symlink
24 $ ln -s `which true` hg
24 $ ln -s `which true` hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
26 warning: --with-hg should specify an hg script
26 warning: --with-hg should specify an hg script
27 running 0 tests using 0 parallel processes
27 running 0 tests using 0 parallel processes
28
28
29 # Ran 0 tests, 0 skipped, 0 failed.
29 # Ran 0 tests, 0 skipped, 0 failed.
30 $ rm hg
30 $ rm hg
31 #endif
31 #endif
32
32
33 #if execbit
33 #if execbit
34 $ touch hg
34 $ touch hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
36 usage: run-tests.py [options] [tests]
36 usage: run-tests.py [options] [tests]
37 run-tests.py: error: --with-hg must specify an executable hg script
37 run-tests.py: error: --with-hg must specify an executable hg script
38 [2]
38 [2]
39 $ rm hg
39 $ rm hg
40 #endif
40 #endif
41
41
42 Features for testing optional lines
42 Features for testing optional lines
43 ===================================
43 ===================================
44
44
45 $ cat > hghaveaddon.py <<EOF
45 $ cat > hghaveaddon.py <<EOF
46 > import hghave
46 > import hghave
47 > @hghave.check("custom", "custom hghave feature")
47 > @hghave.check("custom", "custom hghave feature")
48 > def has_custom():
48 > def has_custom():
49 > return True
49 > return True
50 > @hghave.check("missing", "missing hghave feature")
50 > @hghave.check("missing", "missing hghave feature")
51 > def has_missing():
51 > def has_missing():
52 > return False
52 > return False
53 > EOF
53 > EOF
54
54
55 an empty test
55 an empty test
56 =======================
56 =======================
57
57
58 $ touch test-empty.t
58 $ touch test-empty.t
59 $ rt
59 $ rt
60 running 1 tests using 1 parallel processes
60 running 1 tests using 1 parallel processes
61 .
61 .
62 # Ran 1 tests, 0 skipped, 0 failed.
62 # Ran 1 tests, 0 skipped, 0 failed.
63 $ rm test-empty.t
63 $ rm test-empty.t
64
64
65 a succesful test
65 a succesful test
66 =======================
66 =======================
67
67
68 $ cat > test-success.t << EOF
68 $ cat > test-success.t << EOF
69 > $ echo babar
69 > $ echo babar
70 > babar
70 > babar
71 > $ echo xyzzy
71 > $ echo xyzzy
72 > dont_print (?)
72 > dont_print (?)
73 > nothing[42]line (re) (?)
73 > nothing[42]line (re) (?)
74 > never*happens (glob) (?)
74 > never*happens (glob) (?)
75 > more_nothing (?)
75 > more_nothing (?)
76 > xyzzy
76 > xyzzy
77 > nor this (?)
77 > nor this (?)
78 > $ printf 'abc\ndef\nxyz\n'
78 > $ printf 'abc\ndef\nxyz\n'
79 > 123 (?)
79 > 123 (?)
80 > abc
80 > abc
81 > def (?)
81 > def (?)
82 > 456 (?)
82 > 456 (?)
83 > xyz
83 > xyz
84 > $ printf 'zyx\nwvu\ntsr\n'
84 > $ printf 'zyx\nwvu\ntsr\n'
85 > abc (?)
85 > abc (?)
86 > zyx (custom !)
86 > zyx (custom !)
87 > wvu
87 > wvu
88 > no_print (no-custom !)
88 > no_print (no-custom !)
89 > tsr (no-missing !)
89 > tsr (no-missing !)
90 > missing (missing !)
90 > missing (missing !)
91 > EOF
91 > EOF
92
92
93 $ rt
93 $ rt
94 running 1 tests using 1 parallel processes
94 running 1 tests using 1 parallel processes
95 .
95 .
96 # Ran 1 tests, 0 skipped, 0 failed.
96 # Ran 1 tests, 0 skipped, 0 failed.
97
97
98 failing test
98 failing test
99 ==================
99 ==================
100
100
101 test churn with globs
101 test churn with globs
102 $ cat > test-failure.t <<EOF
102 $ cat > test-failure.t <<EOF
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
104 > bar*bad (glob)
104 > bar*bad (glob)
105 > bar*baz (glob)
105 > bar*baz (glob)
106 > | fo (re)
106 > | fo (re)
107 > EOF
107 > EOF
108 $ rt test-failure.t
108 $ rt test-failure.t
109 running 1 tests using 1 parallel processes
109 running 1 tests using 1 parallel processes
110
110
111 --- $TESTTMP/test-failure.t
111 --- $TESTTMP/test-failure.t
112 +++ $TESTTMP/test-failure.t.err
112 +++ $TESTTMP/test-failure.t.err
113 @@ -1,4 +1,4 @@
113 @@ -1,4 +1,4 @@
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
115 + bar*baz (glob)
115 + bar*baz (glob)
116 bar*bad (glob)
116 bar*bad (glob)
117 - bar*baz (glob)
117 - bar*baz (glob)
118 - | fo (re)
118 - | fo (re)
119 + foo
119 + foo
120
120
121 ERROR: test-failure.t output changed
121 ERROR: test-failure.t output changed
122 !
122 !
123 Failed test-failure.t: output changed
123 Failed test-failure.t: output changed
124 # Ran 1 tests, 0 skipped, 1 failed.
124 # Ran 1 tests, 0 skipped, 1 failed.
125 python hash seed: * (glob)
125 python hash seed: * (glob)
126 [1]
126 [1]
127
127
128 test how multiple globs gets matched with lines in output
128 test how multiple globs gets matched with lines in output
129 $ cat > test-failure-globs.t <<EOF
129 $ cat > test-failure-globs.t <<EOF
130 > $ echo "context"; echo "context"; \
130 > $ echo "context"; echo "context"; \
131 > echo "key: 1"; echo "value: not a"; \
131 > echo "key: 1"; echo "value: not a"; \
132 > echo "key: 2"; echo "value: not b"; \
132 > echo "key: 2"; echo "value: not b"; \
133 > echo "key: 3"; echo "value: c"; \
133 > echo "key: 3"; echo "value: c"; \
134 > echo "key: 4"; echo "value: d"
134 > echo "key: 4"; echo "value: d"
135 > context
135 > context
136 > context
136 > context
137 > key: 1
137 > key: 1
138 > value: a
138 > value: a
139 > key: 2
139 > key: 2
140 > value: b
140 > value: b
141 > key: 3
141 > key: 3
142 > value: * (glob)
142 > value: * (glob)
143 > key: 4
143 > key: 4
144 > value: * (glob)
144 > value: * (glob)
145 > EOF
145 > EOF
146 $ rt test-failure-globs.t
146 $ rt test-failure-globs.t
147 running 1 tests using 1 parallel processes
147 running 1 tests using 1 parallel processes
148
148
149 --- $TESTTMP/test-failure-globs.t
149 --- $TESTTMP/test-failure-globs.t
150 +++ $TESTTMP/test-failure-globs.t.err
150 +++ $TESTTMP/test-failure-globs.t.err
151 @@ -2,9 +2,9 @@
151 @@ -2,9 +2,9 @@
152 context
152 context
153 context
153 context
154 key: 1
154 key: 1
155 - value: a
155 - value: a
156 + value: not a
156 + value: not a
157 key: 2
157 key: 2
158 - value: b
158 - value: b
159 + value: not b
159 + value: not b
160 key: 3
160 key: 3
161 value: * (glob)
161 value: * (glob)
162 key: 4
162 key: 4
163
163
164 ERROR: test-failure-globs.t output changed
164 ERROR: test-failure-globs.t output changed
165 !
165 !
166 Failed test-failure-globs.t: output changed
166 Failed test-failure-globs.t: output changed
167 # Ran 1 tests, 0 skipped, 1 failed.
167 # Ran 1 tests, 0 skipped, 1 failed.
168 python hash seed: * (glob)
168 python hash seed: * (glob)
169 [1]
169 [1]
170 $ rm test-failure-globs.t
170 $ rm test-failure-globs.t
171
171
172 test diff colorisation
172 test diff colorisation
173
173
174 #if no-windows pygments
174 #if no-windows pygments
175 $ rt test-failure.t --color always
175 $ rt test-failure.t --color always
176 running 1 tests using 1 parallel processes
176 running 1 tests using 1 parallel processes
177
177
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
183 bar*bad (glob)
183 bar*bad (glob)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
187
187
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
189 !
189 !
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
191 # Ran 1 tests, 0 skipped, 1 failed.
191 # Ran 1 tests, 0 skipped, 1 failed.
192 python hash seed: * (glob)
192 python hash seed: * (glob)
193 [1]
193 [1]
194
194
195 $ rt test-failure.t 2> tmp.log
195 $ rt test-failure.t 2> tmp.log
196 running 1 tests using 1 parallel processes
196 running 1 tests using 1 parallel processes
197 [1]
197 [1]
198 $ cat tmp.log
198 $ cat tmp.log
199
199
200 --- $TESTTMP/test-failure.t
200 --- $TESTTMP/test-failure.t
201 +++ $TESTTMP/test-failure.t.err
201 +++ $TESTTMP/test-failure.t.err
202 @@ -1,4 +1,4 @@
202 @@ -1,4 +1,4 @@
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
204 + bar*baz (glob)
204 + bar*baz (glob)
205 bar*bad (glob)
205 bar*bad (glob)
206 - bar*baz (glob)
206 - bar*baz (glob)
207 - | fo (re)
207 - | fo (re)
208 + foo
208 + foo
209
209
210 ERROR: test-failure.t output changed
210 ERROR: test-failure.t output changed
211 !
211 !
212 Failed test-failure.t: output changed
212 Failed test-failure.t: output changed
213 # Ran 1 tests, 0 skipped, 1 failed.
213 # Ran 1 tests, 0 skipped, 1 failed.
214 python hash seed: * (glob)
214 python hash seed: * (glob)
215 #endif
215 #endif
216
216
217 $ cat > test-failure.t << EOF
217 $ cat > test-failure.t << EOF
218 > $ true
218 > $ true
219 > should go away (true !)
219 > should go away (true !)
220 > $ true
220 > $ true
221 > should stay (false !)
221 > should stay (false !)
222 >
222 >
223 > Should remove first line, not second or third
223 > Should remove first line, not second or third
224 > $ echo 'testing'
224 > $ echo 'testing'
225 > baz*foo (glob) (true !)
225 > baz*foo (glob) (true !)
226 > foobar*foo (glob) (false !)
226 > foobar*foo (glob) (false !)
227 > te*ting (glob) (true !)
227 > te*ting (glob) (true !)
228 >
228 >
229 > Should keep first two lines, remove third and last
229 > Should keep first two lines, remove third and last
230 > $ echo 'testing'
230 > $ echo 'testing'
231 > test.ng (re) (true !)
231 > test.ng (re) (true !)
232 > foo.ar (re) (false !)
232 > foo.ar (re) (false !)
233 > b.r (re) (true !)
233 > b.r (re) (true !)
234 > missing (?)
234 > missing (?)
235 > awol (true !)
235 > awol (true !)
236 >
236 >
237 > The "missing" line should stay, even though awol is dropped
237 > The "missing" line should stay, even though awol is dropped
238 > $ echo 'testing'
238 > $ echo 'testing'
239 > test.ng (re) (true !)
239 > test.ng (re) (true !)
240 > foo.ar (?)
240 > foo.ar (?)
241 > awol
241 > awol
242 > missing (?)
242 > missing (?)
243 > EOF
243 > EOF
244 $ rt test-failure.t
244 $ rt test-failure.t
245 running 1 tests using 1 parallel processes
245 running 1 tests using 1 parallel processes
246
246
247 --- $TESTTMP/test-failure.t
247 --- $TESTTMP/test-failure.t
248 +++ $TESTTMP/test-failure.t.err
248 +++ $TESTTMP/test-failure.t.err
249 @@ -1,11 +1,9 @@
249 @@ -1,11 +1,9 @@
250 $ true
250 $ true
251 - should go away (true !)
251 - should go away (true !)
252 $ true
252 $ true
253 should stay (false !)
253 should stay (false !)
254
254
255 Should remove first line, not second or third
255 Should remove first line, not second or third
256 $ echo 'testing'
256 $ echo 'testing'
257 - baz*foo (glob) (true !)
257 - baz*foo (glob) (true !)
258 foobar*foo (glob) (false !)
258 foobar*foo (glob) (false !)
259 te*ting (glob) (true !)
259 te*ting (glob) (true !)
260
260
261 foo.ar (re) (false !)
261 foo.ar (re) (false !)
262 missing (?)
262 missing (?)
263 @@ -13,13 +11,10 @@
263 @@ -13,13 +11,10 @@
264 $ echo 'testing'
264 $ echo 'testing'
265 test.ng (re) (true !)
265 test.ng (re) (true !)
266 foo.ar (re) (false !)
266 foo.ar (re) (false !)
267 - b.r (re) (true !)
267 - b.r (re) (true !)
268 missing (?)
268 missing (?)
269 - awol (true !)
269 - awol (true !)
270
270
271 The "missing" line should stay, even though awol is dropped
271 The "missing" line should stay, even though awol is dropped
272 $ echo 'testing'
272 $ echo 'testing'
273 test.ng (re) (true !)
273 test.ng (re) (true !)
274 foo.ar (?)
274 foo.ar (?)
275 - awol
275 - awol
276 missing (?)
276 missing (?)
277
277
278 ERROR: test-failure.t output changed
278 ERROR: test-failure.t output changed
279 !
279 !
280 Failed test-failure.t: output changed
280 Failed test-failure.t: output changed
281 # Ran 1 tests, 0 skipped, 1 failed.
281 # Ran 1 tests, 0 skipped, 1 failed.
282 python hash seed: * (glob)
282 python hash seed: * (glob)
283 [1]
283 [1]
284
284
285 basic failing test
285 basic failing test
286 $ cat > test-failure.t << EOF
286 $ cat > test-failure.t << EOF
287 > $ echo babar
287 > $ echo babar
288 > rataxes
288 > rataxes
289 > This is a noop statement so that
289 > This is a noop statement so that
290 > this test is still more bytes than success.
290 > this test is still more bytes than success.
291 > pad pad pad pad............................................................
291 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
297 > EOF
297 > EOF
298
298
299 >>> fh = open('test-failure-unicode.t', 'wb')
299 >>> fh = open('test-failure-unicode.t', 'wb')
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
302
302
303 $ rt
303 $ rt
304 running 3 tests using 1 parallel processes
304 running 3 tests using 1 parallel processes
305
305
306 --- $TESTTMP/test-failure.t
306 --- $TESTTMP/test-failure.t
307 +++ $TESTTMP/test-failure.t.err
307 +++ $TESTTMP/test-failure.t.err
308 @@ -1,5 +1,5 @@
308 @@ -1,5 +1,5 @@
309 $ echo babar
309 $ echo babar
310 - rataxes
310 - rataxes
311 + babar
311 + babar
312 This is a noop statement so that
312 This is a noop statement so that
313 this test is still more bytes than success.
313 this test is still more bytes than success.
314 pad pad pad pad............................................................
314 pad pad pad pad............................................................
315
315
316 ERROR: test-failure.t output changed
316 ERROR: test-failure.t output changed
317 !.
317 !.
318 --- $TESTTMP/test-failure-unicode.t
318 --- $TESTTMP/test-failure-unicode.t
319 +++ $TESTTMP/test-failure-unicode.t.err
319 +++ $TESTTMP/test-failure-unicode.t.err
320 @@ -1,2 +1,2 @@
320 @@ -1,2 +1,2 @@
321 $ echo babar\xce\xb1 (esc)
321 $ echo babar\xce\xb1 (esc)
322 - l\xce\xb5\xce\xb5t (esc)
322 - l\xce\xb5\xce\xb5t (esc)
323 + babar\xce\xb1 (esc)
323 + babar\xce\xb1 (esc)
324
324
325 ERROR: test-failure-unicode.t output changed
325 ERROR: test-failure-unicode.t output changed
326 !
326 !
327 Failed test-failure-unicode.t: output changed
327 Failed test-failure-unicode.t: output changed
328 Failed test-failure.t: output changed
328 Failed test-failure.t: output changed
329 # Ran 3 tests, 0 skipped, 2 failed.
329 # Ran 3 tests, 0 skipped, 2 failed.
330 python hash seed: * (glob)
330 python hash seed: * (glob)
331 [1]
331 [1]
332
332
333 test --outputdir
333 test --outputdir
334 $ mkdir output
334 $ mkdir output
335 $ rt --outputdir output
335 $ rt --outputdir output
336 running 3 tests using 1 parallel processes
336 running 3 tests using 1 parallel processes
337
337
338 --- $TESTTMP/test-failure.t
338 --- $TESTTMP/test-failure.t
339 +++ $TESTTMP/output/test-failure.t.err
339 +++ $TESTTMP/output/test-failure.t.err
340 @@ -1,5 +1,5 @@
340 @@ -1,5 +1,5 @@
341 $ echo babar
341 $ echo babar
342 - rataxes
342 - rataxes
343 + babar
343 + babar
344 This is a noop statement so that
344 This is a noop statement so that
345 this test is still more bytes than success.
345 this test is still more bytes than success.
346 pad pad pad pad............................................................
346 pad pad pad pad............................................................
347
347
348 ERROR: test-failure.t output changed
348 ERROR: test-failure.t output changed
349 !.
349 !.
350 --- $TESTTMP/test-failure-unicode.t
350 --- $TESTTMP/test-failure-unicode.t
351 +++ $TESTTMP/output/test-failure-unicode.t.err
351 +++ $TESTTMP/output/test-failure-unicode.t.err
352 @@ -1,2 +1,2 @@
352 @@ -1,2 +1,2 @@
353 $ echo babar\xce\xb1 (esc)
353 $ echo babar\xce\xb1 (esc)
354 - l\xce\xb5\xce\xb5t (esc)
354 - l\xce\xb5\xce\xb5t (esc)
355 + babar\xce\xb1 (esc)
355 + babar\xce\xb1 (esc)
356
356
357 ERROR: test-failure-unicode.t output changed
357 ERROR: test-failure-unicode.t output changed
358 !
358 !
359 Failed test-failure-unicode.t: output changed
359 Failed test-failure-unicode.t: output changed
360 Failed test-failure.t: output changed
360 Failed test-failure.t: output changed
361 # Ran 3 tests, 0 skipped, 2 failed.
361 # Ran 3 tests, 0 skipped, 2 failed.
362 python hash seed: * (glob)
362 python hash seed: * (glob)
363 [1]
363 [1]
364 $ ls -a output
364 $ ls -a output
365 .
365 .
366 ..
366 ..
367 .testtimes
367 .testtimes
368 test-failure-unicode.t.err
368 test-failure-unicode.t.err
369 test-failure.t.err
369 test-failure.t.err
370
370
371 test --xunit support
371 test --xunit support
372 $ rt --xunit=xunit.xml
372 $ rt --xunit=xunit.xml
373 running 3 tests using 1 parallel processes
373 running 3 tests using 1 parallel processes
374
374
375 --- $TESTTMP/test-failure.t
375 --- $TESTTMP/test-failure.t
376 +++ $TESTTMP/test-failure.t.err
376 +++ $TESTTMP/test-failure.t.err
377 @@ -1,5 +1,5 @@
377 @@ -1,5 +1,5 @@
378 $ echo babar
378 $ echo babar
379 - rataxes
379 - rataxes
380 + babar
380 + babar
381 This is a noop statement so that
381 This is a noop statement so that
382 this test is still more bytes than success.
382 this test is still more bytes than success.
383 pad pad pad pad............................................................
383 pad pad pad pad............................................................
384
384
385 ERROR: test-failure.t output changed
385 ERROR: test-failure.t output changed
386 !.
386 !.
387 --- $TESTTMP/test-failure-unicode.t
387 --- $TESTTMP/test-failure-unicode.t
388 +++ $TESTTMP/test-failure-unicode.t.err
388 +++ $TESTTMP/test-failure-unicode.t.err
389 @@ -1,2 +1,2 @@
389 @@ -1,2 +1,2 @@
390 $ echo babar\xce\xb1 (esc)
390 $ echo babar\xce\xb1 (esc)
391 - l\xce\xb5\xce\xb5t (esc)
391 - l\xce\xb5\xce\xb5t (esc)
392 + babar\xce\xb1 (esc)
392 + babar\xce\xb1 (esc)
393
393
394 ERROR: test-failure-unicode.t output changed
394 ERROR: test-failure-unicode.t output changed
395 !
395 !
396 Failed test-failure-unicode.t: output changed
396 Failed test-failure-unicode.t: output changed
397 Failed test-failure.t: output changed
397 Failed test-failure.t: output changed
398 # Ran 3 tests, 0 skipped, 2 failed.
398 # Ran 3 tests, 0 skipped, 2 failed.
399 python hash seed: * (glob)
399 python hash seed: * (glob)
400 [1]
400 [1]
401 $ cat xunit.xml
401 $ cat xunit.xml
402 <?xml version="1.0" encoding="utf-8"?>
402 <?xml version="1.0" encoding="utf-8"?>
403 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
403 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
404 <testcase name="test-success.t" time="*"/> (glob)
404 <testcase name="test-success.t" time="*"/> (glob)
405 <testcase name="test-failure-unicode.t" time="*"> (glob)
405 <testcase name="test-failure-unicode.t" time="*"> (glob)
406 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
406 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
407 <failure message="output changed" type="output-mismatch"> (no-py38 !)
407 <failure message="output changed" type="output-mismatch"> (no-py38 !)
408 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
408 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
409 +++ $TESTTMP/test-failure-unicode.t.err
409 +++ $TESTTMP/test-failure-unicode.t.err
410 @@ -1,2 +1,2 @@
410 @@ -1,2 +1,2 @@
411 $ echo babar\xce\xb1 (esc)
411 $ echo babar\xce\xb1 (esc)
412 - l\xce\xb5\xce\xb5t (esc)
412 - l\xce\xb5\xce\xb5t (esc)
413 + babar\xce\xb1 (esc)
413 + babar\xce\xb1 (esc)
414 ]]></failure> (py38 !)
414 ]]></failure> (py38 !)
415 ]]> </failure> (no-py38 !)
415 ]]> </failure> (no-py38 !)
416 </testcase>
416 </testcase>
417 <testcase name="test-failure.t" time="*"> (glob)
417 <testcase name="test-failure.t" time="*"> (glob)
418 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
418 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
419 <failure message="output changed" type="output-mismatch"> (no-py38 !)
419 <failure message="output changed" type="output-mismatch"> (no-py38 !)
420 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
420 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
421 +++ $TESTTMP/test-failure.t.err
421 +++ $TESTTMP/test-failure.t.err
422 @@ -1,5 +1,5 @@
422 @@ -1,5 +1,5 @@
423 $ echo babar
423 $ echo babar
424 - rataxes
424 - rataxes
425 + babar
425 + babar
426 This is a noop statement so that
426 This is a noop statement so that
427 this test is still more bytes than success.
427 this test is still more bytes than success.
428 pad pad pad pad............................................................
428 pad pad pad pad............................................................
429 ]]></failure> (py38 !)
429 ]]></failure> (py38 !)
430 ]]> </failure> (no-py38 !)
430 ]]> </failure> (no-py38 !)
431 </testcase>
431 </testcase>
432 </testsuite>
432 </testsuite>
433
433
434 $ cat .testtimes
434 $ cat .testtimes
435 test-empty.t * (glob)
435 test-empty.t * (glob)
436 test-failure-globs.t * (glob)
436 test-failure-globs.t * (glob)
437 test-failure-unicode.t * (glob)
437 test-failure-unicode.t * (glob)
438 test-failure.t * (glob)
438 test-failure.t * (glob)
439 test-success.t * (glob)
439 test-success.t * (glob)
440
440
441 $ rt --list-tests
441 $ rt --list-tests
442 test-failure-unicode.t
442 test-failure-unicode.t
443 test-failure.t
443 test-failure.t
444 test-success.t
444 test-success.t
445
445
446 $ rt --list-tests --json
446 $ rt --list-tests --json
447 test-failure-unicode.t
447 test-failure-unicode.t
448 test-failure.t
448 test-failure.t
449 test-success.t
449 test-success.t
450 $ cat report.json
450 $ cat report.json
451 testreport ={
451 testreport ={
452 "test-failure-unicode.t": {
452 "test-failure-unicode.t": {
453 "result": "success"
453 "result": "success"
454 },
454 },
455 "test-failure.t": {
455 "test-failure.t": {
456 "result": "success"
456 "result": "success"
457 },
457 },
458 "test-success.t": {
458 "test-success.t": {
459 "result": "success"
459 "result": "success"
460 }
460 }
461 } (no-eol)
461 } (no-eol)
462
462
463 $ rt --list-tests --xunit=xunit.xml
463 $ rt --list-tests --xunit=xunit.xml
464 test-failure-unicode.t
464 test-failure-unicode.t
465 test-failure.t
465 test-failure.t
466 test-success.t
466 test-success.t
467 $ cat xunit.xml
467 $ cat xunit.xml
468 <?xml version="1.0" encoding="utf-8"?>
468 <?xml version="1.0" encoding="utf-8"?>
469 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
469 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
470 <testcase name="test-failure-unicode.t"/>
470 <testcase name="test-failure-unicode.t"/>
471 <testcase name="test-failure.t"/>
471 <testcase name="test-failure.t"/>
472 <testcase name="test-success.t"/>
472 <testcase name="test-success.t"/>
473 </testsuite>
473 </testsuite>
474
474
475 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
475 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
476 test-failure-unicode.t
476 test-failure-unicode.t
477 test-failure.t
477 test-failure.t
478 $ cat output/report.json
478 $ cat output/report.json
479 testreport ={
479 testreport ={
480 "test-failure-unicode.t": {
480 "test-failure-unicode.t": {
481 "result": "success"
481 "result": "success"
482 },
482 },
483 "test-failure.t": {
483 "test-failure.t": {
484 "result": "success"
484 "result": "success"
485 }
485 }
486 } (no-eol)
486 } (no-eol)
487 $ cat xunit.xml
487 $ cat xunit.xml
488 <?xml version="1.0" encoding="utf-8"?>
488 <?xml version="1.0" encoding="utf-8"?>
489 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
489 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
490 <testcase name="test-failure-unicode.t"/>
490 <testcase name="test-failure-unicode.t"/>
491 <testcase name="test-failure.t"/>
491 <testcase name="test-failure.t"/>
492 </testsuite>
492 </testsuite>
493
493
494 $ rm test-failure-unicode.t
494 $ rm test-failure-unicode.t
495
495
496 test for --retest
496 test for --retest
497 ====================
497 ====================
498
498
499 $ rt --retest
499 $ rt --retest
500 running 1 tests using 1 parallel processes
500 running 1 tests using 1 parallel processes
501
501
502 --- $TESTTMP/test-failure.t
502 --- $TESTTMP/test-failure.t
503 +++ $TESTTMP/test-failure.t.err
503 +++ $TESTTMP/test-failure.t.err
504 @@ -1,5 +1,5 @@
504 @@ -1,5 +1,5 @@
505 $ echo babar
505 $ echo babar
506 - rataxes
506 - rataxes
507 + babar
507 + babar
508 This is a noop statement so that
508 This is a noop statement so that
509 this test is still more bytes than success.
509 this test is still more bytes than success.
510 pad pad pad pad............................................................
510 pad pad pad pad............................................................
511
511
512 ERROR: test-failure.t output changed
512 ERROR: test-failure.t output changed
513 !
513 !
514 Failed test-failure.t: output changed
514 Failed test-failure.t: output changed
515 # Ran 1 tests, 0 skipped, 1 failed.
515 # Ran 1 tests, 0 skipped, 1 failed.
516 python hash seed: * (glob)
516 python hash seed: * (glob)
517 [1]
517 [1]
518
518
519 --retest works with --outputdir
519 --retest works with --outputdir
520 $ rm -r output
520 $ rm -r output
521 $ mkdir output
521 $ mkdir output
522 $ mv test-failure.t.err output
522 $ mv test-failure.t.err output
523 $ rt --retest --outputdir output
523 $ rt --retest --outputdir output
524 running 1 tests using 1 parallel processes
524 running 1 tests using 1 parallel processes
525
525
526 --- $TESTTMP/test-failure.t
526 --- $TESTTMP/test-failure.t
527 +++ $TESTTMP/output/test-failure.t.err
527 +++ $TESTTMP/output/test-failure.t.err
528 @@ -1,5 +1,5 @@
528 @@ -1,5 +1,5 @@
529 $ echo babar
529 $ echo babar
530 - rataxes
530 - rataxes
531 + babar
531 + babar
532 This is a noop statement so that
532 This is a noop statement so that
533 this test is still more bytes than success.
533 this test is still more bytes than success.
534 pad pad pad pad............................................................
534 pad pad pad pad............................................................
535
535
536 ERROR: test-failure.t output changed
536 ERROR: test-failure.t output changed
537 !
537 !
538 Failed test-failure.t: output changed
538 Failed test-failure.t: output changed
539 # Ran 1 tests, 0 skipped, 1 failed.
539 # Ran 1 tests, 0 skipped, 1 failed.
540 python hash seed: * (glob)
540 python hash seed: * (glob)
541 [1]
541 [1]
542
542
543 Selecting Tests To Run
543 Selecting Tests To Run
544 ======================
544 ======================
545
545
546 successful
546 successful
547
547
548 $ rt test-success.t
548 $ rt test-success.t
549 running 1 tests using 1 parallel processes
549 running 1 tests using 1 parallel processes
550 .
550 .
551 # Ran 1 tests, 0 skipped, 0 failed.
551 # Ran 1 tests, 0 skipped, 0 failed.
552
552
553 success w/ keyword
553 success w/ keyword
554 $ rt -k xyzzy
554 $ rt -k xyzzy
555 running 2 tests using 1 parallel processes
555 running 2 tests using 1 parallel processes
556 .
556 .
557 # Ran 2 tests, 1 skipped, 0 failed.
557 # Ran 2 tests, 1 skipped, 0 failed.
558
558
559 failed
559 failed
560
560
561 $ rt test-failure.t
561 $ rt test-failure.t
562 running 1 tests using 1 parallel processes
562 running 1 tests using 1 parallel processes
563
563
564 --- $TESTTMP/test-failure.t
564 --- $TESTTMP/test-failure.t
565 +++ $TESTTMP/test-failure.t.err
565 +++ $TESTTMP/test-failure.t.err
566 @@ -1,5 +1,5 @@
566 @@ -1,5 +1,5 @@
567 $ echo babar
567 $ echo babar
568 - rataxes
568 - rataxes
569 + babar
569 + babar
570 This is a noop statement so that
570 This is a noop statement so that
571 this test is still more bytes than success.
571 this test is still more bytes than success.
572 pad pad pad pad............................................................
572 pad pad pad pad............................................................
573
573
574 ERROR: test-failure.t output changed
574 ERROR: test-failure.t output changed
575 !
575 !
576 Failed test-failure.t: output changed
576 Failed test-failure.t: output changed
577 # Ran 1 tests, 0 skipped, 1 failed.
577 # Ran 1 tests, 0 skipped, 1 failed.
578 python hash seed: * (glob)
578 python hash seed: * (glob)
579 [1]
579 [1]
580
580
581 failure w/ keyword
581 failure w/ keyword
582 $ rt -k rataxes
582 $ rt -k rataxes
583 running 2 tests using 1 parallel processes
583 running 2 tests using 1 parallel processes
584
584
585 --- $TESTTMP/test-failure.t
585 --- $TESTTMP/test-failure.t
586 +++ $TESTTMP/test-failure.t.err
586 +++ $TESTTMP/test-failure.t.err
587 @@ -1,5 +1,5 @@
587 @@ -1,5 +1,5 @@
588 $ echo babar
588 $ echo babar
589 - rataxes
589 - rataxes
590 + babar
590 + babar
591 This is a noop statement so that
591 This is a noop statement so that
592 this test is still more bytes than success.
592 this test is still more bytes than success.
593 pad pad pad pad............................................................
593 pad pad pad pad............................................................
594
594
595 ERROR: test-failure.t output changed
595 ERROR: test-failure.t output changed
596 !
596 !
597 Failed test-failure.t: output changed
597 Failed test-failure.t: output changed
598 # Ran 2 tests, 1 skipped, 1 failed.
598 # Ran 2 tests, 1 skipped, 1 failed.
599 python hash seed: * (glob)
599 python hash seed: * (glob)
600 [1]
600 [1]
601
601
602 Verify that when a process fails to start we show a useful message
602 Verify that when a process fails to start we show a useful message
603 ==================================================================
603 ==================================================================
604
604
605 $ cat > test-serve-fail.t <<EOF
605 $ cat > test-serve-fail.t <<EOF
606 > $ echo 'abort: child process failed to start blah'
606 > $ echo 'abort: child process failed to start blah'
607 > EOF
607 > EOF
608 $ rt test-serve-fail.t
608 $ rt test-serve-fail.t
609 running 1 tests using 1 parallel processes
609 running 1 tests using 1 parallel processes
610
610
611 --- $TESTTMP/test-serve-fail.t
611 --- $TESTTMP/test-serve-fail.t
612 +++ $TESTTMP/test-serve-fail.t.err
612 +++ $TESTTMP/test-serve-fail.t.err
613 @@ -1* +1,2 @@ (glob)
613 @@ -1* +1,2 @@ (glob)
614 $ echo 'abort: child process failed to start blah'
614 $ echo 'abort: child process failed to start blah'
615 + abort: child process failed to start blah
615 + abort: child process failed to start blah
616
616
617 ERROR: test-serve-fail.t output changed
617 ERROR: test-serve-fail.t output changed
618 !
618 !
619 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
619 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
620 # Ran 1 tests, 0 skipped, 1 failed.
620 # Ran 1 tests, 0 skipped, 1 failed.
621 python hash seed: * (glob)
621 python hash seed: * (glob)
622 [1]
622 [1]
623 $ rm test-serve-fail.t
623 $ rm test-serve-fail.t
624
624
625 Verify that we can try other ports
625 Verify that we can try other ports
626 ===================================
626 ===================================
627
627
628 Extensions aren't inherited by the invoked run-tests.py. An extension
628 Extensions aren't inherited by the invoked run-tests.py. An extension
629 introducing a repository requirement could cause this to fail. So we force
629 introducing a repository requirement could cause this to fail. So we force
630 HGRCPATH to get a clean environment.
630 HGRCPATH to get a clean environment.
631
631
632 $ HGRCPATH= hg init inuse
632 $ HGRCPATH= hg init inuse
633 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
633 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
634 $ cat blocks.pid >> $DAEMON_PIDS
634 $ cat blocks.pid >> $DAEMON_PIDS
635 $ cat > test-serve-inuse.t <<EOF
635 $ cat > test-serve-inuse.t <<EOF
636 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
636 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
637 > $ cat hg.pid >> \$DAEMON_PIDS
637 > $ cat hg.pid >> \$DAEMON_PIDS
638 > EOF
638 > EOF
639 $ rt test-serve-inuse.t
639 $ rt test-serve-inuse.t
640 running 1 tests using 1 parallel processes
640 running 1 tests using 1 parallel processes
641 .
641 .
642 # Ran 1 tests, 0 skipped, 0 failed.
642 # Ran 1 tests, 0 skipped, 0 failed.
643 $ rm test-serve-inuse.t
643 $ rm test-serve-inuse.t
644 $ killdaemons.py $DAEMON_PIDS
644 $ killdaemons.py $DAEMON_PIDS
645
645
646 Running In Debug Mode
646 Running In Debug Mode
647 ======================
647 ======================
648
648
649 $ rt --debug 2>&1 | grep -v pwd
649 $ rt --debug 2>&1 | grep -v pwd
650 running 2 tests using 1 parallel processes
650 running 2 tests using 1 parallel processes
651 + alias hg=hg.exe (windows !)
651 + alias hg=hg.exe (windows !)
652 + echo *SALT* 0 0 (glob)
652 + echo *SALT* 0 0 (glob)
653 *SALT* 0 0 (glob)
653 *SALT* 0 0 (glob)
654 + echo babar
654 + echo babar
655 babar
655 babar
656 + echo *SALT* 10 0 (glob)
656 + echo *SALT* 10 0 (glob)
657 *SALT* 10 0 (glob)
657 *SALT* 10 0 (glob)
658 .+ alias hg=hg.exe (windows !)
658 .+ alias hg=hg.exe (windows !)
659 *+ echo *SALT* 0 0 (glob)
659 *+ echo *SALT* 0 0 (glob)
660 *SALT* 0 0 (glob)
660 *SALT* 0 0 (glob)
661 + echo babar
661 + echo babar
662 babar
662 babar
663 + echo *SALT* 2 0 (glob)
663 + echo *SALT* 2 0 (glob)
664 *SALT* 2 0 (glob)
664 *SALT* 2 0 (glob)
665 + echo xyzzy
665 + echo xyzzy
666 xyzzy
666 xyzzy
667 + echo *SALT* 9 0 (glob)
667 + echo *SALT* 9 0 (glob)
668 *SALT* 9 0 (glob)
668 *SALT* 9 0 (glob)
669 + printf *abc\ndef\nxyz\n* (glob)
669 + printf *abc\ndef\nxyz\n* (glob)
670 abc
670 abc
671 def
671 def
672 xyz
672 xyz
673 + echo *SALT* 15 0 (glob)
673 + echo *SALT* 15 0 (glob)
674 *SALT* 15 0 (glob)
674 *SALT* 15 0 (glob)
675 + printf *zyx\nwvu\ntsr\n* (glob)
675 + printf *zyx\nwvu\ntsr\n* (glob)
676 zyx
676 zyx
677 wvu
677 wvu
678 tsr
678 tsr
679 + echo *SALT* 22 0 (glob)
679 + echo *SALT* 22 0 (glob)
680 *SALT* 22 0 (glob)
680 *SALT* 22 0 (glob)
681 .
681 .
682 # Ran 2 tests, 0 skipped, 0 failed.
682 # Ran 2 tests, 0 skipped, 0 failed.
683
683
684 Parallel runs
684 Parallel runs
685 ==============
685 ==============
686
686
687 (duplicate the failing test to get predictable output)
687 (duplicate the failing test to get predictable output)
688 $ cp test-failure.t test-failure-copy.t
688 $ cp test-failure.t test-failure-copy.t
689
689
690 $ rt --jobs 2 test-failure*.t -n
690 $ rt --jobs 2 test-failure*.t -n
691 running 2 tests using 2 parallel processes
691 running 2 tests using 2 parallel processes
692 !!
692 !!
693 Failed test-failure*.t: output changed (glob)
693 Failed test-failure*.t: output changed (glob)
694 Failed test-failure*.t: output changed (glob)
694 Failed test-failure*.t: output changed (glob)
695 # Ran 2 tests, 0 skipped, 2 failed.
695 # Ran 2 tests, 0 skipped, 2 failed.
696 python hash seed: * (glob)
696 python hash seed: * (glob)
697 [1]
697 [1]
698
698
699 failures in parallel with --first should only print one failure
699 failures in parallel with --first should only print one failure
700 $ rt --jobs 2 --first test-failure*.t
700 $ rt --jobs 2 --first test-failure*.t
701 running 2 tests using 2 parallel processes
701 running 2 tests using 2 parallel processes
702
702
703 --- $TESTTMP/test-failure*.t (glob)
703 --- $TESTTMP/test-failure*.t (glob)
704 +++ $TESTTMP/test-failure*.t.err (glob)
704 +++ $TESTTMP/test-failure*.t.err (glob)
705 @@ -1,5 +1,5 @@
705 @@ -1,5 +1,5 @@
706 $ echo babar
706 $ echo babar
707 - rataxes
707 - rataxes
708 + babar
708 + babar
709 This is a noop statement so that
709 This is a noop statement so that
710 this test is still more bytes than success.
710 this test is still more bytes than success.
711 pad pad pad pad............................................................
711 pad pad pad pad............................................................
712
712
713 Failed test-failure*.t: output changed (glob)
713 Failed test-failure*.t: output changed (glob)
714 Failed test-failure*.t: output changed (glob)
714 Failed test-failure*.t: output changed (glob)
715 # Ran 2 tests, 0 skipped, 2 failed.
715 # Ran 2 tests, 0 skipped, 2 failed.
716 python hash seed: * (glob)
716 python hash seed: * (glob)
717 [1]
717 [1]
718
718
719
719
720 (delete the duplicated test file)
720 (delete the duplicated test file)
721 $ rm test-failure-copy.t
721 $ rm test-failure-copy.t
722
722
723 multiple runs per test should be parallelized
723 multiple runs per test should be parallelized
724
724
725 $ rt --jobs 2 --runs-per-test 2 test-success.t
725 $ rt --jobs 2 --runs-per-test 2 test-success.t
726 running 2 tests using 2 parallel processes
726 running 2 tests using 2 parallel processes
727 ..
727 ..
728 # Ran 2 tests, 0 skipped, 0 failed.
728 # Ran 2 tests, 0 skipped, 0 failed.
729
729
730 Interactive run
730 Interactive run
731 ===============
731 ===============
732
732
733 (backup the failing test)
733 (backup the failing test)
734 $ cp test-failure.t backup
734 $ cp test-failure.t backup
735
735
736 Refuse the fix
736 Refuse the fix
737
737
738 $ echo 'n' | rt -i
738 $ echo 'n' | rt -i
739 running 2 tests using 1 parallel processes
739 running 2 tests using 1 parallel processes
740
740
741 --- $TESTTMP/test-failure.t
741 --- $TESTTMP/test-failure.t
742 +++ $TESTTMP/test-failure.t.err
742 +++ $TESTTMP/test-failure.t.err
743 @@ -1,5 +1,5 @@
743 @@ -1,5 +1,5 @@
744 $ echo babar
744 $ echo babar
745 - rataxes
745 - rataxes
746 + babar
746 + babar
747 This is a noop statement so that
747 This is a noop statement so that
748 this test is still more bytes than success.
748 this test is still more bytes than success.
749 pad pad pad pad............................................................
749 pad pad pad pad............................................................
750 Accept this change? [y/N]
750 Accept this change? [y/N]
751 ERROR: test-failure.t output changed
751 ERROR: test-failure.t output changed
752 !.
752 !.
753 Failed test-failure.t: output changed
753 Failed test-failure.t: output changed
754 # Ran 2 tests, 0 skipped, 1 failed.
754 # Ran 2 tests, 0 skipped, 1 failed.
755 python hash seed: * (glob)
755 python hash seed: * (glob)
756 [1]
756 [1]
757
757
758 $ cat test-failure.t
758 $ cat test-failure.t
759 $ echo babar
759 $ echo babar
760 rataxes
760 rataxes
761 This is a noop statement so that
761 This is a noop statement so that
762 this test is still more bytes than success.
762 this test is still more bytes than success.
763 pad pad pad pad............................................................
763 pad pad pad pad............................................................
764 pad pad pad pad............................................................
764 pad pad pad pad............................................................
765 pad pad pad pad............................................................
765 pad pad pad pad............................................................
766 pad pad pad pad............................................................
766 pad pad pad pad............................................................
767 pad pad pad pad............................................................
767 pad pad pad pad............................................................
768 pad pad pad pad............................................................
768 pad pad pad pad............................................................
769
769
770 Interactive with custom view
770 Interactive with custom view
771
771
772 $ echo 'n' | rt -i --view echo
772 $ echo 'n' | rt -i --view echo
773 running 2 tests using 1 parallel processes
773 running 2 tests using 1 parallel processes
774 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
774 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
775 Accept this change? [y/N]* (glob)
775 Accept this change? [y/N]* (glob)
776 ERROR: test-failure.t output changed
776 ERROR: test-failure.t output changed
777 !.
777 !.
778 Failed test-failure.t: output changed
778 Failed test-failure.t: output changed
779 # Ran 2 tests, 0 skipped, 1 failed.
779 # Ran 2 tests, 0 skipped, 1 failed.
780 python hash seed: * (glob)
780 python hash seed: * (glob)
781 [1]
781 [1]
782
782
783 View the fix
783 View the fix
784
784
785 $ echo 'y' | rt --view echo
785 $ echo 'y' | rt --view echo
786 running 2 tests using 1 parallel processes
786 running 2 tests using 1 parallel processes
787 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
787 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
788
788
789 ERROR: test-failure.t output changed
789 ERROR: test-failure.t output changed
790 !.
790 !.
791 Failed test-failure.t: output changed
791 Failed test-failure.t: output changed
792 # Ran 2 tests, 0 skipped, 1 failed.
792 # Ran 2 tests, 0 skipped, 1 failed.
793 python hash seed: * (glob)
793 python hash seed: * (glob)
794 [1]
794 [1]
795
795
796 Accept the fix
796 Accept the fix
797
797
798 $ cat >> test-failure.t <<EOF
798 $ cat >> test-failure.t <<EOF
799 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
799 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
800 > saved backup bundle to \$TESTTMP/foo.hg
800 > saved backup bundle to \$TESTTMP/foo.hg
801 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
801 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
802 > saved backup bundle to $TESTTMP\\foo.hg
802 > saved backup bundle to $TESTTMP\\foo.hg
803 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
803 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
804 > saved backup bundle to \$TESTTMP/*.hg (glob)
804 > saved backup bundle to \$TESTTMP/*.hg (glob)
805 > EOF
805 > EOF
806 $ echo 'y' | rt -i 2>&1
806 $ echo 'y' | rt -i 2>&1
807 running 2 tests using 1 parallel processes
807 running 2 tests using 1 parallel processes
808
808
809 --- $TESTTMP/test-failure.t
809 --- $TESTTMP/test-failure.t
810 +++ $TESTTMP/test-failure.t.err
810 +++ $TESTTMP/test-failure.t.err
811 @@ -1,5 +1,5 @@
811 @@ -1,5 +1,5 @@
812 $ echo babar
812 $ echo babar
813 - rataxes
813 - rataxes
814 + babar
814 + babar
815 This is a noop statement so that
815 This is a noop statement so that
816 this test is still more bytes than success.
816 this test is still more bytes than success.
817 pad pad pad pad............................................................
817 pad pad pad pad............................................................
818 @@ -11,6 +11,6 @@
818 @@ -11,6 +11,6 @@
819 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
819 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
820 saved backup bundle to $TESTTMP/foo.hg
820 saved backup bundle to $TESTTMP/foo.hg
821 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
821 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
822 - saved backup bundle to $TESTTMP\foo.hg
822 - saved backup bundle to $TESTTMP\foo.hg
823 + saved backup bundle to $TESTTMP/foo.hg
823 + saved backup bundle to $TESTTMP/foo.hg
824 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
824 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
825 saved backup bundle to $TESTTMP/*.hg (glob)
825 saved backup bundle to $TESTTMP/*.hg (glob)
826 Accept this change? [y/N] ..
826 Accept this change? [y/N] ..
827 # Ran 2 tests, 0 skipped, 0 failed.
827 # Ran 2 tests, 0 skipped, 0 failed.
828
828
829 $ sed -e 's,(glob)$,&<,g' test-failure.t
829 $ sed -e 's,(glob)$,&<,g' test-failure.t
830 $ echo babar
830 $ echo babar
831 babar
831 babar
832 This is a noop statement so that
832 This is a noop statement so that
833 this test is still more bytes than success.
833 this test is still more bytes than success.
834 pad pad pad pad............................................................
834 pad pad pad pad............................................................
835 pad pad pad pad............................................................
835 pad pad pad pad............................................................
836 pad pad pad pad............................................................
836 pad pad pad pad............................................................
837 pad pad pad pad............................................................
837 pad pad pad pad............................................................
838 pad pad pad pad............................................................
838 pad pad pad pad............................................................
839 pad pad pad pad............................................................
839 pad pad pad pad............................................................
840 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
840 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
841 saved backup bundle to $TESTTMP/foo.hg
841 saved backup bundle to $TESTTMP/foo.hg
842 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
842 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
843 saved backup bundle to $TESTTMP/foo.hg
843 saved backup bundle to $TESTTMP/foo.hg
844 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
844 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
845 saved backup bundle to $TESTTMP/*.hg (glob)<
845 saved backup bundle to $TESTTMP/*.hg (glob)<
846
846
847 $ rm test-failure.t
847 $ rm test-failure.t
848
848
849 Race condition - test file was modified when test is running
849 Race condition - test file was modified when test is running
850
850
851 $ TESTRACEDIR=`pwd`
851 $ TESTRACEDIR=`pwd`
852 $ export TESTRACEDIR
852 $ export TESTRACEDIR
853 $ cat > test-race.t <<EOF
853 $ cat > test-race.t <<EOF
854 > $ echo 1
854 > $ echo 1
855 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
855 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
856 > EOF
856 > EOF
857
857
858 $ rt -i test-race.t
858 $ rt -i test-race.t
859 running 1 tests using 1 parallel processes
859 running 1 tests using 1 parallel processes
860
860
861 --- $TESTTMP/test-race.t
861 --- $TESTTMP/test-race.t
862 +++ $TESTTMP/test-race.t.err
862 +++ $TESTTMP/test-race.t.err
863 @@ -1,2 +1,3 @@
863 @@ -1,2 +1,3 @@
864 $ echo 1
864 $ echo 1
865 + 1
865 + 1
866 $ echo "# a new line" >> $TESTTMP/test-race.t
866 $ echo "# a new line" >> $TESTTMP/test-race.t
867 Reference output has changed (run again to prompt changes)
867 Reference output has changed (run again to prompt changes)
868 ERROR: test-race.t output changed
868 ERROR: test-race.t output changed
869 !
869 !
870 Failed test-race.t: output changed
870 Failed test-race.t: output changed
871 # Ran 1 tests, 0 skipped, 1 failed.
871 # Ran 1 tests, 0 skipped, 1 failed.
872 python hash seed: * (glob)
872 python hash seed: * (glob)
873 [1]
873 [1]
874
874
875 $ rm test-race.t
875 $ rm test-race.t
876
876
877 When "#testcases" is used in .t files
877 When "#testcases" is used in .t files
878
878
879 $ cat >> test-cases.t <<EOF
879 $ cat >> test-cases.t <<EOF
880 > #testcases a b
880 > #testcases a b
881 > #if a
881 > #if a
882 > $ echo 1
882 > $ echo 1
883 > #endif
883 > #endif
884 > #if b
884 > #if b
885 > $ echo 2
885 > $ echo 2
886 > #endif
886 > #endif
887 > EOF
887 > EOF
888
888
889 $ cat <<EOF | rt -i test-cases.t 2>&1
889 $ cat <<EOF | rt -i test-cases.t 2>&1
890 > y
890 > y
891 > y
891 > y
892 > EOF
892 > EOF
893 running 2 tests using 1 parallel processes
893 running 2 tests using 1 parallel processes
894
894
895 --- $TESTTMP/test-cases.t
895 --- $TESTTMP/test-cases.t
896 +++ $TESTTMP/test-cases.t#a.err
896 +++ $TESTTMP/test-cases.t#a.err
897 @@ -1,6 +1,7 @@
897 @@ -1,6 +1,7 @@
898 #testcases a b
898 #testcases a b
899 #if a
899 #if a
900 $ echo 1
900 $ echo 1
901 + 1
901 + 1
902 #endif
902 #endif
903 #if b
903 #if b
904 $ echo 2
904 $ echo 2
905 Accept this change? [y/N] .
905 Accept this change? [y/N] .
906 --- $TESTTMP/test-cases.t
906 --- $TESTTMP/test-cases.t
907 +++ $TESTTMP/test-cases.t#b.err
907 +++ $TESTTMP/test-cases.t#b.err
908 @@ -5,4 +5,5 @@
908 @@ -5,4 +5,5 @@
909 #endif
909 #endif
910 #if b
910 #if b
911 $ echo 2
911 $ echo 2
912 + 2
912 + 2
913 #endif
913 #endif
914 Accept this change? [y/N] .
914 Accept this change? [y/N] .
915 # Ran 2 tests, 0 skipped, 0 failed.
915 # Ran 2 tests, 0 skipped, 0 failed.
916
916
917 $ cat test-cases.t
917 $ cat test-cases.t
918 #testcases a b
918 #testcases a b
919 #if a
919 #if a
920 $ echo 1
920 $ echo 1
921 1
921 1
922 #endif
922 #endif
923 #if b
923 #if b
924 $ echo 2
924 $ echo 2
925 2
925 2
926 #endif
926 #endif
927
927
928 $ cat >> test-cases.t <<'EOF'
928 $ cat >> test-cases.t <<'EOF'
929 > #if a
929 > #if a
930 > $ NAME=A
930 > $ NAME=A
931 > #else
931 > #else
932 > $ NAME=B
932 > $ NAME=B
933 > #endif
933 > #endif
934 > $ echo $NAME
934 > $ echo $NAME
935 > A (a !)
935 > A (a !)
936 > B (b !)
936 > B (b !)
937 > EOF
937 > EOF
938 $ rt test-cases.t
938 $ rt test-cases.t
939 running 2 tests using 1 parallel processes
939 running 2 tests using 1 parallel processes
940 ..
940 ..
941 # Ran 2 tests, 0 skipped, 0 failed.
941 # Ran 2 tests, 0 skipped, 0 failed.
942
942
943 When using multiple dimensions of "#testcases" in .t files
943 When using multiple dimensions of "#testcases" in .t files
944
944
945 $ cat > test-cases.t <<'EOF'
945 $ cat > test-cases.t <<'EOF'
946 > #testcases a b
946 > #testcases a b
947 > #testcases c d
947 > #testcases c d
948 > #if a d
948 > #if a d
949 > $ echo $TESTCASE
949 > $ echo $TESTCASE
950 > a#d
950 > a#d
951 > #endif
951 > #endif
952 > #if b c
952 > #if b c
953 > $ echo yes
953 > $ echo yes
954 > no
954 > no
955 > #endif
955 > #endif
956 > EOF
956 > EOF
957 $ rt test-cases.t
957 $ rt test-cases.t
958 running 4 tests using 1 parallel processes
958 running 4 tests using 1 parallel processes
959 ..
959 ..
960 --- $TESTTMP/test-cases.t
960 --- $TESTTMP/test-cases.t
961 +++ $TESTTMP/test-cases.t#b#c.err
961 +++ $TESTTMP/test-cases.t#b#c.err
962 @@ -6,5 +6,5 @@
962 @@ -6,5 +6,5 @@
963 #endif
963 #endif
964 #if b c
964 #if b c
965 $ echo yes
965 $ echo yes
966 - no
966 - no
967 + yes
967 + yes
968 #endif
968 #endif
969
969
970 ERROR: test-cases.t#b#c output changed
970 ERROR: test-cases.t#b#c output changed
971 !.
971 !.
972 Failed test-cases.t#b#c: output changed
972 Failed test-cases.t#b#c: output changed
973 # Ran 4 tests, 0 skipped, 1 failed.
973 # Ran 4 tests, 0 skipped, 1 failed.
974 python hash seed: * (glob)
974 python hash seed: * (glob)
975 [1]
975 [1]
976
976
977 $ rt --retest
977 $ rt --retest
978 running 1 tests using 1 parallel processes
978 running 1 tests using 1 parallel processes
979
979
980 --- $TESTTMP/test-cases.t
980 --- $TESTTMP/test-cases.t
981 +++ $TESTTMP/test-cases.t#b#c.err
981 +++ $TESTTMP/test-cases.t#b#c.err
982 @@ -6,5 +6,5 @@
982 @@ -6,5 +6,5 @@
983 #endif
983 #endif
984 #if b c
984 #if b c
985 $ echo yes
985 $ echo yes
986 - no
986 - no
987 + yes
987 + yes
988 #endif
988 #endif
989
989
990 ERROR: test-cases.t#b#c output changed
990 ERROR: test-cases.t#b#c output changed
991 !
991 !
992 Failed test-cases.t#b#c: output changed
992 Failed test-cases.t#b#c: output changed
993 # Ran 1 tests, 0 skipped, 1 failed.
993 # Ran 1 tests, 0 skipped, 1 failed.
994 python hash seed: * (glob)
994 python hash seed: * (glob)
995 [1]
995 [1]
996 $ rm test-cases.t#b#c.err
996 $ rm test-cases.t#b#c.err
997 $ rm test-cases.t
997 $ rm test-cases.t
998
998
999 (reinstall)
999 (reinstall)
1000 $ mv backup test-failure.t
1000 $ mv backup test-failure.t
1001
1001
1002 No Diff
1002 No Diff
1003 ===============
1003 ===============
1004
1004
1005 $ rt --nodiff
1005 $ rt --nodiff
1006 running 2 tests using 1 parallel processes
1006 running 2 tests using 1 parallel processes
1007 !.
1007 !.
1008 Failed test-failure.t: output changed
1008 Failed test-failure.t: output changed
1009 # Ran 2 tests, 0 skipped, 1 failed.
1009 # Ran 2 tests, 0 skipped, 1 failed.
1010 python hash seed: * (glob)
1010 python hash seed: * (glob)
1011 [1]
1011 [1]
1012
1012
1013 test --tmpdir support
1013 test --tmpdir support
1014 $ rt --tmpdir=$TESTTMP/keep test-success.t
1014 $ rt --tmpdir=$TESTTMP/keep test-success.t
1015 running 1 tests using 1 parallel processes
1015 running 1 tests using 1 parallel processes
1016
1016
1017 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1017 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1018 Keeping threadtmp dir: $TESTTMP/keep/child1
1018 Keeping threadtmp dir: $TESTTMP/keep/child1
1019 .
1019 .
1020 # Ran 1 tests, 0 skipped, 0 failed.
1020 # Ran 1 tests, 0 skipped, 0 failed.
1021
1021
1022 timeouts
1022 timeouts
1023 ========
1023 ========
1024 $ cat > test-timeout.t <<EOF
1024 $ cat > test-timeout.t <<EOF
1025 > $ sleep 2
1025 > $ sleep 2
1026 > $ echo pass
1026 > $ echo pass
1027 > pass
1027 > pass
1028 > EOF
1028 > EOF
1029 > echo '#require slow' > test-slow-timeout.t
1029 > echo '#require slow' > test-slow-timeout.t
1030 > cat test-timeout.t >> test-slow-timeout.t
1030 > cat test-timeout.t >> test-slow-timeout.t
1031 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1031 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1032 running 2 tests using 1 parallel processes
1032 running 2 tests using 1 parallel processes
1033 st
1033 st
1034 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1034 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1035 Failed test-timeout.t: timed out
1035 Failed test-timeout.t: timed out
1036 # Ran 1 tests, 1 skipped, 1 failed.
1036 # Ran 1 tests, 1 skipped, 1 failed.
1037 python hash seed: * (glob)
1037 python hash seed: * (glob)
1038 [1]
1038 [1]
1039 $ rt --timeout=1 --slowtimeout=3 \
1039 $ rt --timeout=1 --slowtimeout=3 \
1040 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1040 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1041 running 2 tests using 1 parallel processes
1041 running 2 tests using 1 parallel processes
1042 .t
1042 .t
1043 Failed test-timeout.t: timed out
1043 Failed test-timeout.t: timed out
1044 # Ran 2 tests, 0 skipped, 1 failed.
1044 # Ran 2 tests, 0 skipped, 1 failed.
1045 python hash seed: * (glob)
1045 python hash seed: * (glob)
1046 [1]
1046 [1]
1047 $ rm test-timeout.t test-slow-timeout.t
1047 $ rm test-timeout.t test-slow-timeout.t
1048
1048
1049 test for --time
1049 test for --time
1050 ==================
1050 ==================
1051
1051
1052 $ rt test-success.t --time
1052 $ rt test-success.t --time
1053 running 1 tests using 1 parallel processes
1053 running 1 tests using 1 parallel processes
1054 .
1054 .
1055 # Ran 1 tests, 0 skipped, 0 failed.
1055 # Ran 1 tests, 0 skipped, 0 failed.
1056 # Producing time report
1056 # Producing time report
1057 start end cuser csys real Test
1057 start end cuser csys real Test
1058 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1058 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1059
1059
1060 test for --time with --job enabled
1060 test for --time with --job enabled
1061 ====================================
1061 ====================================
1062
1062
1063 $ rt test-success.t --time --jobs 2
1063 $ rt test-success.t --time --jobs 2
1064 running 1 tests using 1 parallel processes
1064 running 1 tests using 1 parallel processes
1065 .
1065 .
1066 # Ran 1 tests, 0 skipped, 0 failed.
1066 # Ran 1 tests, 0 skipped, 0 failed.
1067 # Producing time report
1067 # Producing time report
1068 start end cuser csys real Test
1068 start end cuser csys real Test
1069 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1069 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1070
1070
1071 Skips
1071 Skips
1072 ================
1072 ================
1073 $ cat > test-skip.t <<EOF
1073 $ cat > test-skip.t <<EOF
1074 > $ echo xyzzy
1074 > $ echo xyzzy
1075 > #if true
1075 > #if true
1076 > #require false
1076 > #require false
1077 > #end
1077 > #end
1078 > EOF
1078 > EOF
1079 $ cat > test-noskip.t <<EOF
1079 $ cat > test-noskip.t <<EOF
1080 > #if false
1080 > #if false
1081 > #require false
1081 > #require false
1082 > #endif
1082 > #endif
1083 > EOF
1083 > EOF
1084 $ rt --nodiff
1084 $ rt --nodiff
1085 running 4 tests using 1 parallel processes
1085 running 4 tests using 1 parallel processes
1086 !.s.
1086 !.s.
1087 Skipped test-skip.t: missing feature: nail clipper
1087 Skipped test-skip.t: missing feature: nail clipper
1088 Failed test-failure.t: output changed
1088 Failed test-failure.t: output changed
1089 # Ran 3 tests, 1 skipped, 1 failed.
1089 # Ran 3 tests, 1 skipped, 1 failed.
1090 python hash seed: * (glob)
1090 python hash seed: * (glob)
1091 [1]
1091 [1]
1092
1092
1093 $ rm test-noskip.t
1093 $ rm test-noskip.t
1094 $ rt --keyword xyzzy
1094 $ rt --keyword xyzzy
1095 running 3 tests using 1 parallel processes
1095 running 3 tests using 1 parallel processes
1096 .s
1096 .s
1097 Skipped test-skip.t: missing feature: nail clipper
1097 Skipped test-skip.t: missing feature: nail clipper
1098 # Ran 2 tests, 2 skipped, 0 failed.
1098 # Ran 2 tests, 2 skipped, 0 failed.
1099
1099
1100 Skips with xml
1100 Skips with xml
1101 $ rt --keyword xyzzy \
1101 $ rt --keyword xyzzy \
1102 > --xunit=xunit.xml
1102 > --xunit=xunit.xml
1103 running 3 tests using 1 parallel processes
1103 running 3 tests using 1 parallel processes
1104 .s
1104 .s
1105 Skipped test-skip.t: missing feature: nail clipper
1105 Skipped test-skip.t: missing feature: nail clipper
1106 # Ran 2 tests, 2 skipped, 0 failed.
1106 # Ran 2 tests, 2 skipped, 0 failed.
1107 $ cat xunit.xml
1107 $ cat xunit.xml
1108 <?xml version="1.0" encoding="utf-8"?>
1108 <?xml version="1.0" encoding="utf-8"?>
1109 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1109 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1110 <testcase name="test-success.t" time="*"/> (glob)
1110 <testcase name="test-success.t" time="*"/> (glob)
1111 <testcase name="test-skip.t">
1111 <testcase name="test-skip.t">
1112 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1112 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1113 <skipped> (no-py38 !)
1113 <skipped> (no-py38 !)
1114 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1114 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1115 </testcase>
1115 </testcase>
1116 </testsuite>
1116 </testsuite>
1117
1117
1118 Missing skips or blacklisted skips don't count as executed:
1118 Missing skips or blacklisted skips don't count as executed:
1119 $ echo test-failure.t > blacklist
1119 $ mkdir tests
1120 $ echo tests/test-failure.t > blacklist
1121 $ cp test-failure.t tests
1120 $ rt --blacklist=blacklist --json\
1122 $ rt --blacklist=blacklist --json\
1121 > test-failure.t test-bogus.t
1123 > tests/test-failure.t tests/test-bogus.t
1122 running 2 tests using 1 parallel processes
1124 running 2 tests using 1 parallel processes
1123 ss
1125 ss
1124 Skipped test-bogus.t: Doesn't exist
1126 Skipped test-bogus.t: Doesn't exist
1125 Skipped test-failure.t: blacklisted
1127 Skipped test-failure.t: blacklisted
1126 # Ran 0 tests, 2 skipped, 0 failed.
1128 # Ran 0 tests, 2 skipped, 0 failed.
1127 $ cat report.json
1129 $ cat tests/report.json
1128 testreport ={
1130 testreport ={
1129 "test-bogus.t": {
1131 "test-bogus.t": {
1130 "result": "skip"
1132 "result": "skip"
1131 },
1133 },
1132 "test-failure.t": {
1134 "test-failure.t": {
1133 "result": "skip"
1135 "result": "skip"
1134 }
1136 }
1135 } (no-eol)
1137 } (no-eol)
1138 $ rm -r tests
1139 $ echo test-failure.t > blacklist
1136
1140
1137 Whitelist trumps blacklist
1141 Whitelist trumps blacklist
1138 $ echo test-failure.t > whitelist
1142 $ echo test-failure.t > whitelist
1139 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1143 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1140 > test-failure.t test-bogus.t
1144 > test-failure.t test-bogus.t
1141 running 2 tests using 1 parallel processes
1145 running 2 tests using 1 parallel processes
1142 s
1146 s
1143 --- $TESTTMP/test-failure.t
1147 --- $TESTTMP/test-failure.t
1144 +++ $TESTTMP/test-failure.t.err
1148 +++ $TESTTMP/test-failure.t.err
1145 @@ -1,5 +1,5 @@
1149 @@ -1,5 +1,5 @@
1146 $ echo babar
1150 $ echo babar
1147 - rataxes
1151 - rataxes
1148 + babar
1152 + babar
1149 This is a noop statement so that
1153 This is a noop statement so that
1150 this test is still more bytes than success.
1154 this test is still more bytes than success.
1151 pad pad pad pad............................................................
1155 pad pad pad pad............................................................
1152
1156
1153 ERROR: test-failure.t output changed
1157 ERROR: test-failure.t output changed
1154 !
1158 !
1155 Skipped test-bogus.t: Doesn't exist
1159 Skipped test-bogus.t: Doesn't exist
1156 Failed test-failure.t: output changed
1160 Failed test-failure.t: output changed
1157 # Ran 1 tests, 1 skipped, 1 failed.
1161 # Ran 1 tests, 1 skipped, 1 failed.
1158 python hash seed: * (glob)
1162 python hash seed: * (glob)
1159 [1]
1163 [1]
1160
1164
1161 Ensure that --test-list causes only the tests listed in that file to
1165 Ensure that --test-list causes only the tests listed in that file to
1162 be executed.
1166 be executed.
1163 $ echo test-success.t >> onlytest
1167 $ echo test-success.t >> onlytest
1164 $ rt --test-list=onlytest
1168 $ rt --test-list=onlytest
1165 running 1 tests using 1 parallel processes
1169 running 1 tests using 1 parallel processes
1166 .
1170 .
1167 # Ran 1 tests, 0 skipped, 0 failed.
1171 # Ran 1 tests, 0 skipped, 0 failed.
1168 $ echo test-bogus.t >> anothertest
1172 $ echo test-bogus.t >> anothertest
1169 $ rt --test-list=onlytest --test-list=anothertest
1173 $ rt --test-list=onlytest --test-list=anothertest
1170 running 2 tests using 1 parallel processes
1174 running 2 tests using 1 parallel processes
1171 s.
1175 s.
1172 Skipped test-bogus.t: Doesn't exist
1176 Skipped test-bogus.t: Doesn't exist
1173 # Ran 1 tests, 1 skipped, 0 failed.
1177 # Ran 1 tests, 1 skipped, 0 failed.
1174 $ rm onlytest anothertest
1178 $ rm onlytest anothertest
1175
1179
1176 test for --json
1180 test for --json
1177 ==================
1181 ==================
1178
1182
1179 $ rt --json
1183 $ rt --json
1180 running 3 tests using 1 parallel processes
1184 running 3 tests using 1 parallel processes
1181
1185
1182 --- $TESTTMP/test-failure.t
1186 --- $TESTTMP/test-failure.t
1183 +++ $TESTTMP/test-failure.t.err
1187 +++ $TESTTMP/test-failure.t.err
1184 @@ -1,5 +1,5 @@
1188 @@ -1,5 +1,5 @@
1185 $ echo babar
1189 $ echo babar
1186 - rataxes
1190 - rataxes
1187 + babar
1191 + babar
1188 This is a noop statement so that
1192 This is a noop statement so that
1189 this test is still more bytes than success.
1193 this test is still more bytes than success.
1190 pad pad pad pad............................................................
1194 pad pad pad pad............................................................
1191
1195
1192 ERROR: test-failure.t output changed
1196 ERROR: test-failure.t output changed
1193 !.s
1197 !.s
1194 Skipped test-skip.t: missing feature: nail clipper
1198 Skipped test-skip.t: missing feature: nail clipper
1195 Failed test-failure.t: output changed
1199 Failed test-failure.t: output changed
1196 # Ran 2 tests, 1 skipped, 1 failed.
1200 # Ran 2 tests, 1 skipped, 1 failed.
1197 python hash seed: * (glob)
1201 python hash seed: * (glob)
1198 [1]
1202 [1]
1199
1203
1200 $ cat report.json
1204 $ cat report.json
1201 testreport ={
1205 testreport ={
1202 "test-failure.t": [\{] (re)
1206 "test-failure.t": [\{] (re)
1203 "csys": "\s*\d+\.\d{3,4}", ? (re)
1207 "csys": "\s*\d+\.\d{3,4}", ? (re)
1204 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1208 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1205 "diff": "---.+\+\+\+.+", ? (re)
1209 "diff": "---.+\+\+\+.+", ? (re)
1206 "end": "\s*\d+\.\d{3,4}", ? (re)
1210 "end": "\s*\d+\.\d{3,4}", ? (re)
1207 "result": "failure", ? (re)
1211 "result": "failure", ? (re)
1208 "start": "\s*\d+\.\d{3,4}", ? (re)
1212 "start": "\s*\d+\.\d{3,4}", ? (re)
1209 "time": "\s*\d+\.\d{3,4}" (re)
1213 "time": "\s*\d+\.\d{3,4}" (re)
1210 }, ? (re)
1214 }, ? (re)
1211 "test-skip.t": {
1215 "test-skip.t": {
1212 "csys": "\s*\d+\.\d{3,4}", ? (re)
1216 "csys": "\s*\d+\.\d{3,4}", ? (re)
1213 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1217 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1214 "diff": "", ? (re)
1218 "diff": "", ? (re)
1215 "end": "\s*\d+\.\d{3,4}", ? (re)
1219 "end": "\s*\d+\.\d{3,4}", ? (re)
1216 "result": "skip", ? (re)
1220 "result": "skip", ? (re)
1217 "start": "\s*\d+\.\d{3,4}", ? (re)
1221 "start": "\s*\d+\.\d{3,4}", ? (re)
1218 "time": "\s*\d+\.\d{3,4}" (re)
1222 "time": "\s*\d+\.\d{3,4}" (re)
1219 }, ? (re)
1223 }, ? (re)
1220 "test-success.t": [\{] (re)
1224 "test-success.t": [\{] (re)
1221 "csys": "\s*\d+\.\d{3,4}", ? (re)
1225 "csys": "\s*\d+\.\d{3,4}", ? (re)
1222 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1226 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1223 "diff": "", ? (re)
1227 "diff": "", ? (re)
1224 "end": "\s*\d+\.\d{3,4}", ? (re)
1228 "end": "\s*\d+\.\d{3,4}", ? (re)
1225 "result": "success", ? (re)
1229 "result": "success", ? (re)
1226 "start": "\s*\d+\.\d{3,4}", ? (re)
1230 "start": "\s*\d+\.\d{3,4}", ? (re)
1227 "time": "\s*\d+\.\d{3,4}" (re)
1231 "time": "\s*\d+\.\d{3,4}" (re)
1228 }
1232 }
1229 } (no-eol)
1233 } (no-eol)
1230 --json with --outputdir
1234 --json with --outputdir
1231
1235
1232 $ rm report.json
1236 $ rm report.json
1233 $ rm -r output
1237 $ rm -r output
1234 $ mkdir output
1238 $ mkdir output
1235 $ rt --json --outputdir output
1239 $ rt --json --outputdir output
1236 running 3 tests using 1 parallel processes
1240 running 3 tests using 1 parallel processes
1237
1241
1238 --- $TESTTMP/test-failure.t
1242 --- $TESTTMP/test-failure.t
1239 +++ $TESTTMP/output/test-failure.t.err
1243 +++ $TESTTMP/output/test-failure.t.err
1240 @@ -1,5 +1,5 @@
1244 @@ -1,5 +1,5 @@
1241 $ echo babar
1245 $ echo babar
1242 - rataxes
1246 - rataxes
1243 + babar
1247 + babar
1244 This is a noop statement so that
1248 This is a noop statement so that
1245 this test is still more bytes than success.
1249 this test is still more bytes than success.
1246 pad pad pad pad............................................................
1250 pad pad pad pad............................................................
1247
1251
1248 ERROR: test-failure.t output changed
1252 ERROR: test-failure.t output changed
1249 !.s
1253 !.s
1250 Skipped test-skip.t: missing feature: nail clipper
1254 Skipped test-skip.t: missing feature: nail clipper
1251 Failed test-failure.t: output changed
1255 Failed test-failure.t: output changed
1252 # Ran 2 tests, 1 skipped, 1 failed.
1256 # Ran 2 tests, 1 skipped, 1 failed.
1253 python hash seed: * (glob)
1257 python hash seed: * (glob)
1254 [1]
1258 [1]
1255 $ f report.json
1259 $ f report.json
1256 report.json: file not found
1260 report.json: file not found
1257 $ cat output/report.json
1261 $ cat output/report.json
1258 testreport ={
1262 testreport ={
1259 "test-failure.t": [\{] (re)
1263 "test-failure.t": [\{] (re)
1260 "csys": "\s*\d+\.\d{3,4}", ? (re)
1264 "csys": "\s*\d+\.\d{3,4}", ? (re)
1261 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1265 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1262 "diff": "---.+\+\+\+.+", ? (re)
1266 "diff": "---.+\+\+\+.+", ? (re)
1263 "end": "\s*\d+\.\d{3,4}", ? (re)
1267 "end": "\s*\d+\.\d{3,4}", ? (re)
1264 "result": "failure", ? (re)
1268 "result": "failure", ? (re)
1265 "start": "\s*\d+\.\d{3,4}", ? (re)
1269 "start": "\s*\d+\.\d{3,4}", ? (re)
1266 "time": "\s*\d+\.\d{3,4}" (re)
1270 "time": "\s*\d+\.\d{3,4}" (re)
1267 }, ? (re)
1271 }, ? (re)
1268 "test-skip.t": {
1272 "test-skip.t": {
1269 "csys": "\s*\d+\.\d{3,4}", ? (re)
1273 "csys": "\s*\d+\.\d{3,4}", ? (re)
1270 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1274 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1271 "diff": "", ? (re)
1275 "diff": "", ? (re)
1272 "end": "\s*\d+\.\d{3,4}", ? (re)
1276 "end": "\s*\d+\.\d{3,4}", ? (re)
1273 "result": "skip", ? (re)
1277 "result": "skip", ? (re)
1274 "start": "\s*\d+\.\d{3,4}", ? (re)
1278 "start": "\s*\d+\.\d{3,4}", ? (re)
1275 "time": "\s*\d+\.\d{3,4}" (re)
1279 "time": "\s*\d+\.\d{3,4}" (re)
1276 }, ? (re)
1280 }, ? (re)
1277 "test-success.t": [\{] (re)
1281 "test-success.t": [\{] (re)
1278 "csys": "\s*\d+\.\d{3,4}", ? (re)
1282 "csys": "\s*\d+\.\d{3,4}", ? (re)
1279 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1283 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1280 "diff": "", ? (re)
1284 "diff": "", ? (re)
1281 "end": "\s*\d+\.\d{3,4}", ? (re)
1285 "end": "\s*\d+\.\d{3,4}", ? (re)
1282 "result": "success", ? (re)
1286 "result": "success", ? (re)
1283 "start": "\s*\d+\.\d{3,4}", ? (re)
1287 "start": "\s*\d+\.\d{3,4}", ? (re)
1284 "time": "\s*\d+\.\d{3,4}" (re)
1288 "time": "\s*\d+\.\d{3,4}" (re)
1285 }
1289 }
1286 } (no-eol)
1290 } (no-eol)
1287 $ ls -a output
1291 $ ls -a output
1288 .
1292 .
1289 ..
1293 ..
1290 .testtimes
1294 .testtimes
1291 report.json
1295 report.json
1292 test-failure.t.err
1296 test-failure.t.err
1293
1297
1294 Test that failed test accepted through interactive are properly reported:
1298 Test that failed test accepted through interactive are properly reported:
1295
1299
1296 $ cp test-failure.t backup
1300 $ cp test-failure.t backup
1297 $ echo y | rt --json -i
1301 $ echo y | rt --json -i
1298 running 3 tests using 1 parallel processes
1302 running 3 tests using 1 parallel processes
1299
1303
1300 --- $TESTTMP/test-failure.t
1304 --- $TESTTMP/test-failure.t
1301 +++ $TESTTMP/test-failure.t.err
1305 +++ $TESTTMP/test-failure.t.err
1302 @@ -1,5 +1,5 @@
1306 @@ -1,5 +1,5 @@
1303 $ echo babar
1307 $ echo babar
1304 - rataxes
1308 - rataxes
1305 + babar
1309 + babar
1306 This is a noop statement so that
1310 This is a noop statement so that
1307 this test is still more bytes than success.
1311 this test is still more bytes than success.
1308 pad pad pad pad............................................................
1312 pad pad pad pad............................................................
1309 Accept this change? [y/N] ..s
1313 Accept this change? [y/N] ..s
1310 Skipped test-skip.t: missing feature: nail clipper
1314 Skipped test-skip.t: missing feature: nail clipper
1311 # Ran 2 tests, 1 skipped, 0 failed.
1315 # Ran 2 tests, 1 skipped, 0 failed.
1312
1316
1313 $ cat report.json
1317 $ cat report.json
1314 testreport ={
1318 testreport ={
1315 "test-failure.t": [\{] (re)
1319 "test-failure.t": [\{] (re)
1316 "csys": "\s*\d+\.\d{3,4}", ? (re)
1320 "csys": "\s*\d+\.\d{3,4}", ? (re)
1317 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1321 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1318 "diff": "", ? (re)
1322 "diff": "", ? (re)
1319 "end": "\s*\d+\.\d{3,4}", ? (re)
1323 "end": "\s*\d+\.\d{3,4}", ? (re)
1320 "result": "success", ? (re)
1324 "result": "success", ? (re)
1321 "start": "\s*\d+\.\d{3,4}", ? (re)
1325 "start": "\s*\d+\.\d{3,4}", ? (re)
1322 "time": "\s*\d+\.\d{3,4}" (re)
1326 "time": "\s*\d+\.\d{3,4}" (re)
1323 }, ? (re)
1327 }, ? (re)
1324 "test-skip.t": {
1328 "test-skip.t": {
1325 "csys": "\s*\d+\.\d{3,4}", ? (re)
1329 "csys": "\s*\d+\.\d{3,4}", ? (re)
1326 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1330 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1327 "diff": "", ? (re)
1331 "diff": "", ? (re)
1328 "end": "\s*\d+\.\d{3,4}", ? (re)
1332 "end": "\s*\d+\.\d{3,4}", ? (re)
1329 "result": "skip", ? (re)
1333 "result": "skip", ? (re)
1330 "start": "\s*\d+\.\d{3,4}", ? (re)
1334 "start": "\s*\d+\.\d{3,4}", ? (re)
1331 "time": "\s*\d+\.\d{3,4}" (re)
1335 "time": "\s*\d+\.\d{3,4}" (re)
1332 }, ? (re)
1336 }, ? (re)
1333 "test-success.t": [\{] (re)
1337 "test-success.t": [\{] (re)
1334 "csys": "\s*\d+\.\d{3,4}", ? (re)
1338 "csys": "\s*\d+\.\d{3,4}", ? (re)
1335 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1339 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1336 "diff": "", ? (re)
1340 "diff": "", ? (re)
1337 "end": "\s*\d+\.\d{3,4}", ? (re)
1341 "end": "\s*\d+\.\d{3,4}", ? (re)
1338 "result": "success", ? (re)
1342 "result": "success", ? (re)
1339 "start": "\s*\d+\.\d{3,4}", ? (re)
1343 "start": "\s*\d+\.\d{3,4}", ? (re)
1340 "time": "\s*\d+\.\d{3,4}" (re)
1344 "time": "\s*\d+\.\d{3,4}" (re)
1341 }
1345 }
1342 } (no-eol)
1346 } (no-eol)
1343 $ mv backup test-failure.t
1347 $ mv backup test-failure.t
1344
1348
1345 backslash on end of line with glob matching is handled properly
1349 backslash on end of line with glob matching is handled properly
1346
1350
1347 $ cat > test-glob-backslash.t << EOF
1351 $ cat > test-glob-backslash.t << EOF
1348 > $ echo 'foo bar \\'
1352 > $ echo 'foo bar \\'
1349 > foo * \ (glob)
1353 > foo * \ (glob)
1350 > EOF
1354 > EOF
1351
1355
1352 $ rt test-glob-backslash.t
1356 $ rt test-glob-backslash.t
1353 running 1 tests using 1 parallel processes
1357 running 1 tests using 1 parallel processes
1354 .
1358 .
1355 # Ran 1 tests, 0 skipped, 0 failed.
1359 # Ran 1 tests, 0 skipped, 0 failed.
1356
1360
1357 $ rm -f test-glob-backslash.t
1361 $ rm -f test-glob-backslash.t
1358
1362
1359 Test globbing of local IP addresses
1363 Test globbing of local IP addresses
1360 $ echo 172.16.18.1
1364 $ echo 172.16.18.1
1361 $LOCALIP (glob)
1365 $LOCALIP (glob)
1362 $ echo dead:beef::1
1366 $ echo dead:beef::1
1363 $LOCALIP (glob)
1367 $LOCALIP (glob)
1364
1368
1365 Add support for external test formatter
1369 Add support for external test formatter
1366 =======================================
1370 =======================================
1367
1371
1368 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@" test-success.t test-failure.t
1372 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@" test-success.t test-failure.t
1369 running 2 tests using 1 parallel processes
1373 running 2 tests using 1 parallel processes
1370
1374
1371 # Ran 2 tests, 0 skipped, 0 failed.
1375 # Ran 2 tests, 0 skipped, 0 failed.
1372 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1376 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1373 FAILURE! test-failure.t output changed
1377 FAILURE! test-failure.t output changed
1374 SUCCESS! test-success.t
1378 SUCCESS! test-success.t
1375 ON_END!
1379 ON_END!
1376
1380
1377 Test reusability for third party tools
1381 Test reusability for third party tools
1378 ======================================
1382 ======================================
1379
1383
1380 $ mkdir "$TESTTMP"/anothertests
1384 $ mkdir "$TESTTMP"/anothertests
1381 $ cd "$TESTTMP"/anothertests
1385 $ cd "$TESTTMP"/anothertests
1382
1386
1383 test that `run-tests.py` can execute hghave, even if it runs not in
1387 test that `run-tests.py` can execute hghave, even if it runs not in
1384 Mercurial source tree.
1388 Mercurial source tree.
1385
1389
1386 $ cat > test-hghave.t <<EOF
1390 $ cat > test-hghave.t <<EOF
1387 > #require true
1391 > #require true
1388 > $ echo foo
1392 > $ echo foo
1389 > foo
1393 > foo
1390 > EOF
1394 > EOF
1391 $ rt test-hghave.t
1395 $ rt test-hghave.t
1392 running 1 tests using 1 parallel processes
1396 running 1 tests using 1 parallel processes
1393 .
1397 .
1394 # Ran 1 tests, 0 skipped, 0 failed.
1398 # Ran 1 tests, 0 skipped, 0 failed.
1395
1399
1396 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1400 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1397 running is placed.
1401 running is placed.
1398
1402
1399 $ cat > test-runtestdir.t <<EOF
1403 $ cat > test-runtestdir.t <<EOF
1400 > - $TESTDIR, in which test-run-tests.t is placed
1404 > - $TESTDIR, in which test-run-tests.t is placed
1401 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1405 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1402 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1406 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1403 >
1407 >
1404 > #if windows
1408 > #if windows
1405 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1409 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1406 > #else
1410 > #else
1407 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1411 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1408 > #endif
1412 > #endif
1409 > If this prints a path, that means RUNTESTDIR didn't equal
1413 > If this prints a path, that means RUNTESTDIR didn't equal
1410 > TESTDIR as it should have.
1414 > TESTDIR as it should have.
1411 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1415 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1412 > This should print the start of check-code. If this passes but the
1416 > This should print the start of check-code. If this passes but the
1413 > previous check failed, that means we found a copy of check-code at whatever
1417 > previous check failed, that means we found a copy of check-code at whatever
1414 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1418 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1415 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1419 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1416 > #!USRBINENVPY
1420 > #!USRBINENVPY
1417 > #
1421 > #
1418 > # check-code - a style and portability checker for Mercurial
1422 > # check-code - a style and portability checker for Mercurial
1419 > EOF
1423 > EOF
1420 $ rt test-runtestdir.t
1424 $ rt test-runtestdir.t
1421 running 1 tests using 1 parallel processes
1425 running 1 tests using 1 parallel processes
1422 .
1426 .
1423 # Ran 1 tests, 0 skipped, 0 failed.
1427 # Ran 1 tests, 0 skipped, 0 failed.
1424
1428
1425 #if execbit
1429 #if execbit
1426
1430
1427 test that TESTDIR is referred in PATH
1431 test that TESTDIR is referred in PATH
1428
1432
1429 $ cat > custom-command.sh <<EOF
1433 $ cat > custom-command.sh <<EOF
1430 > #!/bin/sh
1434 > #!/bin/sh
1431 > echo "hello world"
1435 > echo "hello world"
1432 > EOF
1436 > EOF
1433 $ chmod +x custom-command.sh
1437 $ chmod +x custom-command.sh
1434 $ cat > test-testdir-path.t <<EOF
1438 $ cat > test-testdir-path.t <<EOF
1435 > $ custom-command.sh
1439 > $ custom-command.sh
1436 > hello world
1440 > hello world
1437 > EOF
1441 > EOF
1438 $ rt test-testdir-path.t
1442 $ rt test-testdir-path.t
1439 running 1 tests using 1 parallel processes
1443 running 1 tests using 1 parallel processes
1440 .
1444 .
1441 # Ran 1 tests, 0 skipped, 0 failed.
1445 # Ran 1 tests, 0 skipped, 0 failed.
1442
1446
1443 #endif
1447 #endif
1444
1448
1445 test support for --allow-slow-tests
1449 test support for --allow-slow-tests
1446 $ cat > test-very-slow-test.t <<EOF
1450 $ cat > test-very-slow-test.t <<EOF
1447 > #require slow
1451 > #require slow
1448 > $ echo pass
1452 > $ echo pass
1449 > pass
1453 > pass
1450 > EOF
1454 > EOF
1451 $ rt test-very-slow-test.t
1455 $ rt test-very-slow-test.t
1452 running 1 tests using 1 parallel processes
1456 running 1 tests using 1 parallel processes
1453 s
1457 s
1454 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1458 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1455 # Ran 0 tests, 1 skipped, 0 failed.
1459 # Ran 0 tests, 1 skipped, 0 failed.
1456 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1460 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1457 running 1 tests using 1 parallel processes
1461 running 1 tests using 1 parallel processes
1458 .
1462 .
1459 # Ran 1 tests, 0 skipped, 0 failed.
1463 # Ran 1 tests, 0 skipped, 0 failed.
1460
1464
1461 support for running a test outside the current directory
1465 support for running a test outside the current directory
1462 $ mkdir nonlocal
1466 $ mkdir nonlocal
1463 $ cat > nonlocal/test-is-not-here.t << EOF
1467 $ cat > nonlocal/test-is-not-here.t << EOF
1464 > $ echo pass
1468 > $ echo pass
1465 > pass
1469 > pass
1466 > EOF
1470 > EOF
1467 $ rt nonlocal/test-is-not-here.t
1471 $ rt nonlocal/test-is-not-here.t
1468 running 1 tests using 1 parallel processes
1472 running 1 tests using 1 parallel processes
1469 .
1473 .
1470 # Ran 1 tests, 0 skipped, 0 failed.
1474 # Ran 1 tests, 0 skipped, 0 failed.
1471
1475
1472 support for automatically discovering test if arg is a folder
1476 support for automatically discovering test if arg is a folder
1473 $ mkdir tmp && cd tmp
1477 $ mkdir tmp && cd tmp
1474
1478
1475 $ cat > test-uno.t << EOF
1479 $ cat > test-uno.t << EOF
1476 > $ echo line
1480 > $ echo line
1477 > line
1481 > line
1478 > EOF
1482 > EOF
1479
1483
1480 $ cp test-uno.t test-dos.t
1484 $ cp test-uno.t test-dos.t
1481 $ cd ..
1485 $ cd ..
1482 $ cp -R tmp tmpp
1486 $ cp -R tmp tmpp
1483 $ cp tmp/test-uno.t test-solo.t
1487 $ cp tmp/test-uno.t test-solo.t
1484
1488
1485 $ rt tmp/ test-solo.t tmpp
1489 $ rt tmp/ test-solo.t tmpp
1486 running 5 tests using 1 parallel processes
1490 running 5 tests using 1 parallel processes
1487 .....
1491 .....
1488 # Ran 5 tests, 0 skipped, 0 failed.
1492 # Ran 5 tests, 0 skipped, 0 failed.
1489 $ rm -rf tmp tmpp
1493 $ rm -rf tmp tmpp
1490
1494
1491 support for running run-tests.py from another directory
1495 support for running run-tests.py from another directory
1492 $ mkdir tmp && cd tmp
1496 $ mkdir tmp && cd tmp
1493
1497
1494 $ cat > useful-file.sh << EOF
1498 $ cat > useful-file.sh << EOF
1495 > important command
1499 > important command
1496 > EOF
1500 > EOF
1497
1501
1498 $ cat > test-folder.t << EOF
1502 $ cat > test-folder.t << EOF
1499 > $ cat \$TESTDIR/useful-file.sh
1503 > $ cat \$TESTDIR/useful-file.sh
1500 > important command
1504 > important command
1501 > EOF
1505 > EOF
1502
1506
1503 $ cat > test-folder-fail.t << EOF
1507 $ cat > test-folder-fail.t << EOF
1504 > $ cat \$TESTDIR/useful-file.sh
1508 > $ cat \$TESTDIR/useful-file.sh
1505 > important commando
1509 > important commando
1506 > EOF
1510 > EOF
1507
1511
1508 $ cd ..
1512 $ cd ..
1509 $ rt tmp/test-*.t
1513 $ rt tmp/test-*.t
1510 running 2 tests using 1 parallel processes
1514 running 2 tests using 1 parallel processes
1511
1515
1512 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1516 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1513 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1517 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1514 @@ -1,2 +1,2 @@
1518 @@ -1,2 +1,2 @@
1515 $ cat $TESTDIR/useful-file.sh
1519 $ cat $TESTDIR/useful-file.sh
1516 - important commando
1520 - important commando
1517 + important command
1521 + important command
1518
1522
1519 ERROR: test-folder-fail.t output changed
1523 ERROR: test-folder-fail.t output changed
1520 !.
1524 !.
1521 Failed test-folder-fail.t: output changed
1525 Failed test-folder-fail.t: output changed
1522 # Ran 2 tests, 0 skipped, 1 failed.
1526 # Ran 2 tests, 0 skipped, 1 failed.
1523 python hash seed: * (glob)
1527 python hash seed: * (glob)
1524 [1]
1528 [1]
1525
1529
1526 support for bisecting failed tests automatically
1530 support for bisecting failed tests automatically
1527 $ hg init bisect
1531 $ hg init bisect
1528 $ cd bisect
1532 $ cd bisect
1529 $ cat >> test-bisect.t <<EOF
1533 $ cat >> test-bisect.t <<EOF
1530 > $ echo pass
1534 > $ echo pass
1531 > pass
1535 > pass
1532 > EOF
1536 > EOF
1533 $ hg add test-bisect.t
1537 $ hg add test-bisect.t
1534 $ hg ci -m 'good'
1538 $ hg ci -m 'good'
1535 $ cat >> test-bisect.t <<EOF
1539 $ cat >> test-bisect.t <<EOF
1536 > $ echo pass
1540 > $ echo pass
1537 > fail
1541 > fail
1538 > EOF
1542 > EOF
1539 $ hg ci -m 'bad'
1543 $ hg ci -m 'bad'
1540 $ rt --known-good-rev=0 test-bisect.t
1544 $ rt --known-good-rev=0 test-bisect.t
1541 running 1 tests using 1 parallel processes
1545 running 1 tests using 1 parallel processes
1542
1546
1543 --- $TESTTMP/anothertests/bisect/test-bisect.t
1547 --- $TESTTMP/anothertests/bisect/test-bisect.t
1544 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1548 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1545 @@ -1,4 +1,4 @@
1549 @@ -1,4 +1,4 @@
1546 $ echo pass
1550 $ echo pass
1547 pass
1551 pass
1548 $ echo pass
1552 $ echo pass
1549 - fail
1553 - fail
1550 + pass
1554 + pass
1551
1555
1552 ERROR: test-bisect.t output changed
1556 ERROR: test-bisect.t output changed
1553 !
1557 !
1554 Failed test-bisect.t: output changed
1558 Failed test-bisect.t: output changed
1555 test-bisect.t broken by 72cbf122d116 (bad)
1559 test-bisect.t broken by 72cbf122d116 (bad)
1556 # Ran 1 tests, 0 skipped, 1 failed.
1560 # Ran 1 tests, 0 skipped, 1 failed.
1557 python hash seed: * (glob)
1561 python hash seed: * (glob)
1558 [1]
1562 [1]
1559
1563
1560 $ cd ..
1564 $ cd ..
1561
1565
1562 support bisecting a separate repo
1566 support bisecting a separate repo
1563
1567
1564 $ hg init bisect-dependent
1568 $ hg init bisect-dependent
1565 $ cd bisect-dependent
1569 $ cd bisect-dependent
1566 $ cat > test-bisect-dependent.t <<EOF
1570 $ cat > test-bisect-dependent.t <<EOF
1567 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1571 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1568 > pass
1572 > pass
1569 > EOF
1573 > EOF
1570 $ hg commit -Am dependent test-bisect-dependent.t
1574 $ hg commit -Am dependent test-bisect-dependent.t
1571
1575
1572 $ rt --known-good-rev=0 test-bisect-dependent.t
1576 $ rt --known-good-rev=0 test-bisect-dependent.t
1573 running 1 tests using 1 parallel processes
1577 running 1 tests using 1 parallel processes
1574
1578
1575 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1579 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1576 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1580 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1577 @@ -1,2 +1,2 @@
1581 @@ -1,2 +1,2 @@
1578 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1582 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1579 - pass
1583 - pass
1580 + fail
1584 + fail
1581
1585
1582 ERROR: test-bisect-dependent.t output changed
1586 ERROR: test-bisect-dependent.t output changed
1583 !
1587 !
1584 Failed test-bisect-dependent.t: output changed
1588 Failed test-bisect-dependent.t: output changed
1585 Failed to identify failure point for test-bisect-dependent.t
1589 Failed to identify failure point for test-bisect-dependent.t
1586 # Ran 1 tests, 0 skipped, 1 failed.
1590 # Ran 1 tests, 0 skipped, 1 failed.
1587 python hash seed: * (glob)
1591 python hash seed: * (glob)
1588 [1]
1592 [1]
1589
1593
1590 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1594 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1591 usage: run-tests.py [options] [tests]
1595 usage: run-tests.py [options] [tests]
1592 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1596 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1593 [2]
1597 [2]
1594
1598
1595 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1599 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1596 running 1 tests using 1 parallel processes
1600 running 1 tests using 1 parallel processes
1597
1601
1598 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1602 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1599 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1603 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1600 @@ -1,2 +1,2 @@
1604 @@ -1,2 +1,2 @@
1601 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1605 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1602 - pass
1606 - pass
1603 + fail
1607 + fail
1604
1608
1605 ERROR: test-bisect-dependent.t output changed
1609 ERROR: test-bisect-dependent.t output changed
1606 !
1610 !
1607 Failed test-bisect-dependent.t: output changed
1611 Failed test-bisect-dependent.t: output changed
1608 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1612 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1609 # Ran 1 tests, 0 skipped, 1 failed.
1613 # Ran 1 tests, 0 skipped, 1 failed.
1610 python hash seed: * (glob)
1614 python hash seed: * (glob)
1611 [1]
1615 [1]
1612
1616
1613 $ cd ..
1617 $ cd ..
1614
1618
1615 Test a broken #if statement doesn't break run-tests threading.
1619 Test a broken #if statement doesn't break run-tests threading.
1616 ==============================================================
1620 ==============================================================
1617 $ mkdir broken
1621 $ mkdir broken
1618 $ cd broken
1622 $ cd broken
1619 $ cat > test-broken.t <<EOF
1623 $ cat > test-broken.t <<EOF
1620 > true
1624 > true
1621 > #if notarealhghavefeature
1625 > #if notarealhghavefeature
1622 > $ false
1626 > $ false
1623 > #endif
1627 > #endif
1624 > EOF
1628 > EOF
1625 $ for f in 1 2 3 4 ; do
1629 $ for f in 1 2 3 4 ; do
1626 > cat > test-works-$f.t <<EOF
1630 > cat > test-works-$f.t <<EOF
1627 > This is test case $f
1631 > This is test case $f
1628 > $ sleep 1
1632 > $ sleep 1
1629 > EOF
1633 > EOF
1630 > done
1634 > done
1631 $ rt -j 2
1635 $ rt -j 2
1632 running 5 tests using 2 parallel processes
1636 running 5 tests using 2 parallel processes
1633 ....
1637 ....
1634 # Ran 5 tests, 0 skipped, 0 failed.
1638 # Ran 5 tests, 0 skipped, 0 failed.
1635 skipped: unknown feature: notarealhghavefeature
1639 skipped: unknown feature: notarealhghavefeature
1636
1640
1637 $ cd ..
1641 $ cd ..
1638 $ rm -rf broken
1642 $ rm -rf broken
1639
1643
1640 Test cases in .t files
1644 Test cases in .t files
1641 ======================
1645 ======================
1642 $ mkdir cases
1646 $ mkdir cases
1643 $ cd cases
1647 $ cd cases
1644 $ cat > test-cases-abc.t <<'EOF'
1648 $ cat > test-cases-abc.t <<'EOF'
1645 > #testcases A B C
1649 > #testcases A B C
1646 > $ V=B
1650 > $ V=B
1647 > #if A
1651 > #if A
1648 > $ V=A
1652 > $ V=A
1649 > #endif
1653 > #endif
1650 > #if C
1654 > #if C
1651 > $ V=C
1655 > $ V=C
1652 > #endif
1656 > #endif
1653 > $ echo $V | sed 's/A/C/'
1657 > $ echo $V | sed 's/A/C/'
1654 > C
1658 > C
1655 > #if C
1659 > #if C
1656 > $ [ $V = C ]
1660 > $ [ $V = C ]
1657 > #endif
1661 > #endif
1658 > #if A
1662 > #if A
1659 > $ [ $V = C ]
1663 > $ [ $V = C ]
1660 > [1]
1664 > [1]
1661 > #endif
1665 > #endif
1662 > #if no-C
1666 > #if no-C
1663 > $ [ $V = C ]
1667 > $ [ $V = C ]
1664 > [1]
1668 > [1]
1665 > #endif
1669 > #endif
1666 > $ [ $V = D ]
1670 > $ [ $V = D ]
1667 > [1]
1671 > [1]
1668 > EOF
1672 > EOF
1669 $ rt
1673 $ rt
1670 running 3 tests using 1 parallel processes
1674 running 3 tests using 1 parallel processes
1671 .
1675 .
1672 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1676 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1673 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1677 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1674 @@ -7,7 +7,7 @@
1678 @@ -7,7 +7,7 @@
1675 $ V=C
1679 $ V=C
1676 #endif
1680 #endif
1677 $ echo $V | sed 's/A/C/'
1681 $ echo $V | sed 's/A/C/'
1678 - C
1682 - C
1679 + B
1683 + B
1680 #if C
1684 #if C
1681 $ [ $V = C ]
1685 $ [ $V = C ]
1682 #endif
1686 #endif
1683
1687
1684 ERROR: test-cases-abc.t#B output changed
1688 ERROR: test-cases-abc.t#B output changed
1685 !.
1689 !.
1686 Failed test-cases-abc.t#B: output changed
1690 Failed test-cases-abc.t#B: output changed
1687 # Ran 3 tests, 0 skipped, 1 failed.
1691 # Ran 3 tests, 0 skipped, 1 failed.
1688 python hash seed: * (glob)
1692 python hash seed: * (glob)
1689 [1]
1693 [1]
1690
1694
1691 --restart works
1695 --restart works
1692
1696
1693 $ rt --restart
1697 $ rt --restart
1694 running 2 tests using 1 parallel processes
1698 running 2 tests using 1 parallel processes
1695
1699
1696 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1700 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1697 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1701 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1698 @@ -7,7 +7,7 @@
1702 @@ -7,7 +7,7 @@
1699 $ V=C
1703 $ V=C
1700 #endif
1704 #endif
1701 $ echo $V | sed 's/A/C/'
1705 $ echo $V | sed 's/A/C/'
1702 - C
1706 - C
1703 + B
1707 + B
1704 #if C
1708 #if C
1705 $ [ $V = C ]
1709 $ [ $V = C ]
1706 #endif
1710 #endif
1707
1711
1708 ERROR: test-cases-abc.t#B output changed
1712 ERROR: test-cases-abc.t#B output changed
1709 !.
1713 !.
1710 Failed test-cases-abc.t#B: output changed
1714 Failed test-cases-abc.t#B: output changed
1711 # Ran 2 tests, 0 skipped, 1 failed.
1715 # Ran 2 tests, 0 skipped, 1 failed.
1712 python hash seed: * (glob)
1716 python hash seed: * (glob)
1713 [1]
1717 [1]
1714
1718
1715 --restart works with outputdir
1719 --restart works with outputdir
1716
1720
1717 $ mkdir output
1721 $ mkdir output
1718 $ mv test-cases-abc.t#B.err output
1722 $ mv test-cases-abc.t#B.err output
1719 $ rt --restart --outputdir output
1723 $ rt --restart --outputdir output
1720 running 2 tests using 1 parallel processes
1724 running 2 tests using 1 parallel processes
1721
1725
1722 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1726 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1723 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1727 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1724 @@ -7,7 +7,7 @@
1728 @@ -7,7 +7,7 @@
1725 $ V=C
1729 $ V=C
1726 #endif
1730 #endif
1727 $ echo $V | sed 's/A/C/'
1731 $ echo $V | sed 's/A/C/'
1728 - C
1732 - C
1729 + B
1733 + B
1730 #if C
1734 #if C
1731 $ [ $V = C ]
1735 $ [ $V = C ]
1732 #endif
1736 #endif
1733
1737
1734 ERROR: test-cases-abc.t#B output changed
1738 ERROR: test-cases-abc.t#B output changed
1735 !.
1739 !.
1736 Failed test-cases-abc.t#B: output changed
1740 Failed test-cases-abc.t#B: output changed
1737 # Ran 2 tests, 0 skipped, 1 failed.
1741 # Ran 2 tests, 0 skipped, 1 failed.
1738 python hash seed: * (glob)
1742 python hash seed: * (glob)
1739 [1]
1743 [1]
1740
1744
1741 Test TESTCASE variable
1745 Test TESTCASE variable
1742
1746
1743 $ cat > test-cases-ab.t <<'EOF'
1747 $ cat > test-cases-ab.t <<'EOF'
1744 > $ dostuff() {
1748 > $ dostuff() {
1745 > > echo "In case $TESTCASE"
1749 > > echo "In case $TESTCASE"
1746 > > }
1750 > > }
1747 > #testcases A B
1751 > #testcases A B
1748 > #if A
1752 > #if A
1749 > $ dostuff
1753 > $ dostuff
1750 > In case A
1754 > In case A
1751 > #endif
1755 > #endif
1752 > #if B
1756 > #if B
1753 > $ dostuff
1757 > $ dostuff
1754 > In case B
1758 > In case B
1755 > #endif
1759 > #endif
1756 > EOF
1760 > EOF
1757 $ rt test-cases-ab.t
1761 $ rt test-cases-ab.t
1758 running 2 tests using 1 parallel processes
1762 running 2 tests using 1 parallel processes
1759 ..
1763 ..
1760 # Ran 2 tests, 0 skipped, 0 failed.
1764 # Ran 2 tests, 0 skipped, 0 failed.
1761
1765
1762 Support running a specific test case
1766 Support running a specific test case
1763
1767
1764 $ rt "test-cases-abc.t#B"
1768 $ rt "test-cases-abc.t#B"
1765 running 1 tests using 1 parallel processes
1769 running 1 tests using 1 parallel processes
1766
1770
1767 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1771 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1768 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1772 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1769 @@ -7,7 +7,7 @@
1773 @@ -7,7 +7,7 @@
1770 $ V=C
1774 $ V=C
1771 #endif
1775 #endif
1772 $ echo $V | sed 's/A/C/'
1776 $ echo $V | sed 's/A/C/'
1773 - C
1777 - C
1774 + B
1778 + B
1775 #if C
1779 #if C
1776 $ [ $V = C ]
1780 $ [ $V = C ]
1777 #endif
1781 #endif
1778
1782
1779 ERROR: test-cases-abc.t#B output changed
1783 ERROR: test-cases-abc.t#B output changed
1780 !
1784 !
1781 Failed test-cases-abc.t#B: output changed
1785 Failed test-cases-abc.t#B: output changed
1782 # Ran 1 tests, 0 skipped, 1 failed.
1786 # Ran 1 tests, 0 skipped, 1 failed.
1783 python hash seed: * (glob)
1787 python hash seed: * (glob)
1784 [1]
1788 [1]
1785
1789
1786 Support running multiple test cases in the same file
1790 Support running multiple test cases in the same file
1787
1791
1788 $ rt test-cases-abc.t#B test-cases-abc.t#C
1792 $ rt test-cases-abc.t#B test-cases-abc.t#C
1789 running 2 tests using 1 parallel processes
1793 running 2 tests using 1 parallel processes
1790
1794
1791 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1795 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1792 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1796 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1793 @@ -7,7 +7,7 @@
1797 @@ -7,7 +7,7 @@
1794 $ V=C
1798 $ V=C
1795 #endif
1799 #endif
1796 $ echo $V | sed 's/A/C/'
1800 $ echo $V | sed 's/A/C/'
1797 - C
1801 - C
1798 + B
1802 + B
1799 #if C
1803 #if C
1800 $ [ $V = C ]
1804 $ [ $V = C ]
1801 #endif
1805 #endif
1802
1806
1803 ERROR: test-cases-abc.t#B output changed
1807 ERROR: test-cases-abc.t#B output changed
1804 !.
1808 !.
1805 Failed test-cases-abc.t#B: output changed
1809 Failed test-cases-abc.t#B: output changed
1806 # Ran 2 tests, 0 skipped, 1 failed.
1810 # Ran 2 tests, 0 skipped, 1 failed.
1807 python hash seed: * (glob)
1811 python hash seed: * (glob)
1808 [1]
1812 [1]
1809
1813
1810 Support ignoring invalid test cases
1814 Support ignoring invalid test cases
1811
1815
1812 $ rt test-cases-abc.t#B test-cases-abc.t#D
1816 $ rt test-cases-abc.t#B test-cases-abc.t#D
1813 running 1 tests using 1 parallel processes
1817 running 1 tests using 1 parallel processes
1814
1818
1815 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1819 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1816 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1820 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1817 @@ -7,7 +7,7 @@
1821 @@ -7,7 +7,7 @@
1818 $ V=C
1822 $ V=C
1819 #endif
1823 #endif
1820 $ echo $V | sed 's/A/C/'
1824 $ echo $V | sed 's/A/C/'
1821 - C
1825 - C
1822 + B
1826 + B
1823 #if C
1827 #if C
1824 $ [ $V = C ]
1828 $ [ $V = C ]
1825 #endif
1829 #endif
1826
1830
1827 ERROR: test-cases-abc.t#B output changed
1831 ERROR: test-cases-abc.t#B output changed
1828 !
1832 !
1829 Failed test-cases-abc.t#B: output changed
1833 Failed test-cases-abc.t#B: output changed
1830 # Ran 1 tests, 0 skipped, 1 failed.
1834 # Ran 1 tests, 0 skipped, 1 failed.
1831 python hash seed: * (glob)
1835 python hash seed: * (glob)
1832 [1]
1836 [1]
1833
1837
1834 Support running complex test cases names
1838 Support running complex test cases names
1835
1839
1836 $ cat > test-cases-advanced-cases.t <<'EOF'
1840 $ cat > test-cases-advanced-cases.t <<'EOF'
1837 > #testcases simple case-with-dashes casewith_-.chars
1841 > #testcases simple case-with-dashes casewith_-.chars
1838 > $ echo $TESTCASE
1842 > $ echo $TESTCASE
1839 > simple
1843 > simple
1840 > EOF
1844 > EOF
1841
1845
1842 $ cat test-cases-advanced-cases.t
1846 $ cat test-cases-advanced-cases.t
1843 #testcases simple case-with-dashes casewith_-.chars
1847 #testcases simple case-with-dashes casewith_-.chars
1844 $ echo $TESTCASE
1848 $ echo $TESTCASE
1845 simple
1849 simple
1846
1850
1847 $ rt test-cases-advanced-cases.t
1851 $ rt test-cases-advanced-cases.t
1848 running 3 tests using 1 parallel processes
1852 running 3 tests using 1 parallel processes
1849
1853
1850 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1854 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1851 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1855 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1852 @@ -1,3 +1,3 @@
1856 @@ -1,3 +1,3 @@
1853 #testcases simple case-with-dashes casewith_-.chars
1857 #testcases simple case-with-dashes casewith_-.chars
1854 $ echo $TESTCASE
1858 $ echo $TESTCASE
1855 - simple
1859 - simple
1856 + case-with-dashes
1860 + case-with-dashes
1857
1861
1858 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1862 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1859 !
1863 !
1860 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1864 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1861 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1865 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1862 @@ -1,3 +1,3 @@
1866 @@ -1,3 +1,3 @@
1863 #testcases simple case-with-dashes casewith_-.chars
1867 #testcases simple case-with-dashes casewith_-.chars
1864 $ echo $TESTCASE
1868 $ echo $TESTCASE
1865 - simple
1869 - simple
1866 + casewith_-.chars
1870 + casewith_-.chars
1867
1871
1868 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1872 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1869 !.
1873 !.
1870 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1874 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1871 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1875 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1872 # Ran 3 tests, 0 skipped, 2 failed.
1876 # Ran 3 tests, 0 skipped, 2 failed.
1873 python hash seed: * (glob)
1877 python hash seed: * (glob)
1874 [1]
1878 [1]
1875
1879
1876 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1880 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1877 running 1 tests using 1 parallel processes
1881 running 1 tests using 1 parallel processes
1878
1882
1879 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1883 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1880 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1884 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1881 @@ -1,3 +1,3 @@
1885 @@ -1,3 +1,3 @@
1882 #testcases simple case-with-dashes casewith_-.chars
1886 #testcases simple case-with-dashes casewith_-.chars
1883 $ echo $TESTCASE
1887 $ echo $TESTCASE
1884 - simple
1888 - simple
1885 + case-with-dashes
1889 + case-with-dashes
1886
1890
1887 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1891 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1888 !
1892 !
1889 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1893 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1890 # Ran 1 tests, 0 skipped, 1 failed.
1894 # Ran 1 tests, 0 skipped, 1 failed.
1891 python hash seed: * (glob)
1895 python hash seed: * (glob)
1892 [1]
1896 [1]
1893
1897
1894 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1898 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1895 running 1 tests using 1 parallel processes
1899 running 1 tests using 1 parallel processes
1896
1900
1897 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1901 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1898 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1902 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1899 @@ -1,3 +1,3 @@
1903 @@ -1,3 +1,3 @@
1900 #testcases simple case-with-dashes casewith_-.chars
1904 #testcases simple case-with-dashes casewith_-.chars
1901 $ echo $TESTCASE
1905 $ echo $TESTCASE
1902 - simple
1906 - simple
1903 + casewith_-.chars
1907 + casewith_-.chars
1904
1908
1905 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1909 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1906 !
1910 !
1907 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1911 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1908 # Ran 1 tests, 0 skipped, 1 failed.
1912 # Ran 1 tests, 0 skipped, 1 failed.
1909 python hash seed: * (glob)
1913 python hash seed: * (glob)
1910 [1]
1914 [1]
1911
1915
1912 Test automatic pattern replacement
1916 Test automatic pattern replacement
1913 ==================================
1917 ==================================
1914
1918
1915 $ cat << EOF >> common-pattern.py
1919 $ cat << EOF >> common-pattern.py
1916 > substitutions = [
1920 > substitutions = [
1917 > (br'foo-(.*)\\b',
1921 > (br'foo-(.*)\\b',
1918 > br'\$XXX=\\1\$'),
1922 > br'\$XXX=\\1\$'),
1919 > (br'bar\\n',
1923 > (br'bar\\n',
1920 > br'\$YYY$\\n'),
1924 > br'\$YYY$\\n'),
1921 > ]
1925 > ]
1922 > EOF
1926 > EOF
1923
1927
1924 $ cat << EOF >> test-substitution.t
1928 $ cat << EOF >> test-substitution.t
1925 > $ echo foo-12
1929 > $ echo foo-12
1926 > \$XXX=12$
1930 > \$XXX=12$
1927 > $ echo foo-42
1931 > $ echo foo-42
1928 > \$XXX=42$
1932 > \$XXX=42$
1929 > $ echo bar prior
1933 > $ echo bar prior
1930 > bar prior
1934 > bar prior
1931 > $ echo lastbar
1935 > $ echo lastbar
1932 > last\$YYY$
1936 > last\$YYY$
1933 > $ echo foo-bar foo-baz
1937 > $ echo foo-bar foo-baz
1934 > EOF
1938 > EOF
1935
1939
1936 $ rt test-substitution.t
1940 $ rt test-substitution.t
1937 running 1 tests using 1 parallel processes
1941 running 1 tests using 1 parallel processes
1938
1942
1939 --- $TESTTMP/anothertests/cases/test-substitution.t
1943 --- $TESTTMP/anothertests/cases/test-substitution.t
1940 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1944 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1941 @@ -7,3 +7,4 @@
1945 @@ -7,3 +7,4 @@
1942 $ echo lastbar
1946 $ echo lastbar
1943 last$YYY$
1947 last$YYY$
1944 $ echo foo-bar foo-baz
1948 $ echo foo-bar foo-baz
1945 + $XXX=bar foo-baz$
1949 + $XXX=bar foo-baz$
1946
1950
1947 ERROR: test-substitution.t output changed
1951 ERROR: test-substitution.t output changed
1948 !
1952 !
1949 Failed test-substitution.t: output changed
1953 Failed test-substitution.t: output changed
1950 # Ran 1 tests, 0 skipped, 1 failed.
1954 # Ran 1 tests, 0 skipped, 1 failed.
1951 python hash seed: * (glob)
1955 python hash seed: * (glob)
1952 [1]
1956 [1]
1953
1957
1954 --extra-config-opt works
1958 --extra-config-opt works
1955
1959
1956 $ cat << EOF >> test-config-opt.t
1960 $ cat << EOF >> test-config-opt.t
1957 > $ hg init test-config-opt
1961 > $ hg init test-config-opt
1958 > $ hg -R test-config-opt purge
1962 > $ hg -R test-config-opt purge
1959 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1963 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1960 > HGTESTEXTRAEXTENSIONS: purge
1964 > HGTESTEXTRAEXTENSIONS: purge
1961 > EOF
1965 > EOF
1962
1966
1963 $ rt --extra-config-opt extensions.purge= \
1967 $ rt --extra-config-opt extensions.purge= \
1964 > --extra-config-opt not.an.extension=True test-config-opt.t
1968 > --extra-config-opt not.an.extension=True test-config-opt.t
1965 running 1 tests using 1 parallel processes
1969 running 1 tests using 1 parallel processes
1966 .
1970 .
1967 # Ran 1 tests, 0 skipped, 0 failed.
1971 # Ran 1 tests, 0 skipped, 0 failed.
1968
1972
1969 Test conditional output matching
1973 Test conditional output matching
1970 ================================
1974 ================================
1971
1975
1972 $ cat << EOF >> test-conditional-matching.t
1976 $ cat << EOF >> test-conditional-matching.t
1973 > #testcases foo bar
1977 > #testcases foo bar
1974 > $ echo richtig
1978 > $ echo richtig
1975 > richtig (true !)
1979 > richtig (true !)
1976 > $ echo falsch
1980 > $ echo falsch
1977 > falsch (false !)
1981 > falsch (false !)
1978 > #if foo
1982 > #if foo
1979 > $ echo arthur
1983 > $ echo arthur
1980 > arthur (bar !)
1984 > arthur (bar !)
1981 > #endif
1985 > #endif
1982 > $ echo celeste
1986 > $ echo celeste
1983 > celeste (foo !)
1987 > celeste (foo !)
1984 > $ echo zephir
1988 > $ echo zephir
1985 > zephir (bar !)
1989 > zephir (bar !)
1986 > EOF
1990 > EOF
1987
1991
1988 $ rt test-conditional-matching.t
1992 $ rt test-conditional-matching.t
1989 running 2 tests using 1 parallel processes
1993 running 2 tests using 1 parallel processes
1990
1994
1991 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
1995 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
1992 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
1996 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
1993 @@ -3,11 +3,13 @@
1997 @@ -3,11 +3,13 @@
1994 richtig (true !)
1998 richtig (true !)
1995 $ echo falsch
1999 $ echo falsch
1996 falsch (false !)
2000 falsch (false !)
1997 + falsch
2001 + falsch
1998 #if foo
2002 #if foo
1999 $ echo arthur
2003 $ echo arthur
2000 arthur \(bar !\) (re)
2004 arthur \(bar !\) (re)
2001 #endif
2005 #endif
2002 $ echo celeste
2006 $ echo celeste
2003 celeste \(foo !\) (re)
2007 celeste \(foo !\) (re)
2004 + celeste
2008 + celeste
2005 $ echo zephir
2009 $ echo zephir
2006 zephir \(bar !\) (re)
2010 zephir \(bar !\) (re)
2007
2011
2008 ERROR: test-conditional-matching.t#bar output changed
2012 ERROR: test-conditional-matching.t#bar output changed
2009 !
2013 !
2010 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2014 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2011 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2015 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2012 @@ -3,11 +3,14 @@
2016 @@ -3,11 +3,14 @@
2013 richtig (true !)
2017 richtig (true !)
2014 $ echo falsch
2018 $ echo falsch
2015 falsch (false !)
2019 falsch (false !)
2016 + falsch
2020 + falsch
2017 #if foo
2021 #if foo
2018 $ echo arthur
2022 $ echo arthur
2019 arthur \(bar !\) (re)
2023 arthur \(bar !\) (re)
2020 + arthur
2024 + arthur
2021 #endif
2025 #endif
2022 $ echo celeste
2026 $ echo celeste
2023 celeste \(foo !\) (re)
2027 celeste \(foo !\) (re)
2024 $ echo zephir
2028 $ echo zephir
2025 zephir \(bar !\) (re)
2029 zephir \(bar !\) (re)
2026 + zephir
2030 + zephir
2027
2031
2028 ERROR: test-conditional-matching.t#foo output changed
2032 ERROR: test-conditional-matching.t#foo output changed
2029 !
2033 !
2030 Failed test-conditional-matching.t#bar: output changed
2034 Failed test-conditional-matching.t#bar: output changed
2031 Failed test-conditional-matching.t#foo: output changed
2035 Failed test-conditional-matching.t#foo: output changed
2032 # Ran 2 tests, 0 skipped, 2 failed.
2036 # Ran 2 tests, 0 skipped, 2 failed.
2033 python hash seed: * (glob)
2037 python hash seed: * (glob)
2034 [1]
2038 [1]
General Comments 0
You need to be logged in to leave comments. Login now