##// END OF EJS Templates
run-tests: add color to the progress output...
marmoute -
r52730:7933bcb0 default
parent child Browse files
Show More
@@ -1,4082 +1,4146
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
46
47 import argparse
47 import argparse
48 import collections
48 import collections
49 import contextlib
49 import contextlib
50 import difflib
50 import difflib
51
51
52 import errno
52 import errno
53 import functools
53 import functools
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 queue
58 import queue
59 import random
59 import random
60 import re
60 import re
61 import shlex
61 import shlex
62 import shutil
62 import shutil
63 import signal
63 import signal
64 import socket
64 import socket
65 import subprocess
65 import subprocess
66 import sys
66 import sys
67 import sysconfig
67 import sysconfig
68 import tempfile
68 import tempfile
69 import threading
69 import threading
70 import time
70 import time
71 import unittest
71 import unittest
72 import uuid
72 import uuid
73 import xml.dom.minidom as minidom
73 import xml.dom.minidom as minidom
74
74
75
75
76 if sys.version_info < (3, 5, 0):
76 if sys.version_info < (3, 5, 0):
77 print(
77 print(
78 '%s is only supported on Python 3.5+, not %s'
78 '%s is only supported on Python 3.5+, not %s'
79 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
79 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
80 )
80 )
81 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
81 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
82
82
83 MACOS = sys.platform == 'darwin'
83 MACOS = sys.platform == 'darwin'
84 WINDOWS = os.name == r'nt'
84 WINDOWS = os.name == r'nt'
85 shellquote = shlex.quote
85 shellquote = shlex.quote
86
86
87
87
88 processlock = threading.Lock()
88 processlock = threading.Lock()
89
89
90 pygmentspresent = False
90 pygmentspresent = False
91 try: # is pygments installed
91 try: # is pygments installed
92 import pygments
92 import pygments
93 import pygments.lexers as lexers
93 import pygments.lexers as lexers
94 import pygments.lexer as lexer
94 import pygments.lexer as lexer
95 import pygments.formatters as formatters
95 import pygments.formatters as formatters
96 import pygments.token as token
96 import pygments.token as token
97 import pygments.style as style
97 import pygments.style as style
98
98
99 if WINDOWS:
99 if WINDOWS:
100 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
100 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
101 sys.path.append(hgpath)
101 sys.path.append(hgpath)
102 try:
102 try:
103 from mercurial import win32 # pytype: disable=import-error
103 from mercurial import win32 # pytype: disable=import-error
104
104
105 # Don't check the result code because it fails on heptapod, but
105 # Don't check the result code because it fails on heptapod, but
106 # something is able to convert to color anyway.
106 # something is able to convert to color anyway.
107 win32.enablevtmode()
107 win32.enablevtmode()
108 finally:
108 finally:
109 sys.path = sys.path[:-1]
109 sys.path = sys.path[:-1]
110
110
111 pygmentspresent = True
111 pygmentspresent = True
112 difflexer = lexers.DiffLexer()
112 difflexer = lexers.DiffLexer()
113 terminal256formatter = formatters.Terminal256Formatter()
113 terminal256formatter = formatters.Terminal256Formatter()
114 except ImportError:
114 except ImportError:
115 pass
115 pass
116
116
117 progress_type = {}
118
117 if pygmentspresent:
119 if pygmentspresent:
120 _T_ERROR = token.string_to_tokentype("Token.Generic.Error")
121 _T_FAILED = token.string_to_tokentype("Token.Generic.Failed")
122 _T_FNAME = token.string_to_tokentype("Token.Generic.FName")
123 _T_IGNORED = token.string_to_tokentype("Token.Generic.Ignored")
124 _T_SKIPPED = token.string_to_tokentype("Token.Generic.Skipped")
125 _T_SNAME = token.string_to_tokentype("Token.Generic.SName")
126 _T_SKIPPED_DOT = token.string_to_tokentype("Token.Generic.SkippedDot")
127 _T_SUCCESS = token.string_to_tokentype("Token.Generic.Success")
128 _T_TIMEDOUT = token.string_to_tokentype("Token.Generic.TimedOut")
118
129
119 class TestRunnerStyle(style.Style):
130 class TestRunnerStyle(style.Style):
120 default_style = ""
131 default_style = ""
121 skipped = token.string_to_tokentype("Token.Generic.Skipped")
122 failed = token.string_to_tokentype("Token.Generic.Failed")
123 skippedname = token.string_to_tokentype("Token.Generic.SName")
124 failedname = token.string_to_tokentype("Token.Generic.FName")
125 styles = {
132 styles = {
126 skipped: '#e5e5e5',
133 _T_ERROR: '#cd00cd',
127 skippedname: '#00ffff',
134 _T_FAILED: '#7f0000',
128 failed: '#7f0000',
135 _T_FNAME: '#ff0000',
129 failedname: '#ff0000',
136 _T_IGNORED: '#cdcd00',
137 _T_SKIPPED: '#e5e5e5',
138 _T_SNAME: '#00ffff',
139 _T_SKIPPED_DOT: '#00ffff',
140 _T_SUCCESS: '#00cd00',
141 _T_TIMEDOUT: '#ff00ff',
130 }
142 }
131
143
132 class TestRunnerLexer(lexer.RegexLexer):
144 class TestRunnerLexer(lexer.RegexLexer):
133 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
145 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
134 tokens = {
146 tokens = {
135 'root': [
147 'root': [
136 (r'^Skipped', token.Generic.Skipped, 'skipped'),
148 (r'^Skipped', _T_SKIPPED, 'skipped'),
137 (r'^Failed ', token.Generic.Failed, 'failed'),
149 (r'^Failed ', _T_FAILED, 'failed'),
138 (r'^ERROR: ', token.Generic.Failed, 'failed'),
150 (r'^ERROR: ', _T_FAILED, 'failed'),
139 ],
151 ],
140 'skipped': [
152 'skipped': [
141 (testpattern, token.Generic.SName),
153 (testpattern, _T_SNAME),
142 (r':.*', token.Generic.Skipped),
154 (r':.*', _T_SKIPPED),
143 ],
155 ],
144 'failed': [
156 'failed': [
145 (testpattern, token.Generic.FName),
157 (testpattern, _T_FNAME),
146 (r'(:| ).*', token.Generic.Failed),
158 (r'(:| ).*', _T_FAILED),
159 ],
160 }
161
162 progress_type['.'] = _T_SUCCESS
163 progress_type['s'] = _T_SKIPPED_DOT
164 progress_type['i'] = _T_IGNORED
165 progress_type['!'] = _T_FAILED
166 progress_type['E'] = _T_ERROR
167 progress_type['t'] = _T_TIMEDOUT
168
169 class progressLexer(lexer.RegexLexer):
170 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
171 tokens = {
172 'root': [
173 (r'^Skipped', _T_SKIPPED, 'skipped'),
174 (r'^Failed ', _T_FAILED, 'failed'),
175 (r'^ERROR: ', _T_FAILED, 'failed'),
176 ],
177 'skipped': [
178 (testpattern, _T_SNAME),
179 (r':.*', _T_SKIPPED),
180 ],
181 'failed': [
182 (testpattern, _T_FNAME),
183 (r'(:| ).*', _T_FAILED),
147 ],
184 ],
148 }
185 }
149
186
150 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
187 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
151 runnerlexer = TestRunnerLexer()
188 runnerlexer = TestRunnerLexer()
152
189
153 origenviron = os.environ.copy()
190 origenviron = os.environ.copy()
154
191
155
192
156 def _sys2bytes(p):
193 def _sys2bytes(p):
157 if p is None:
194 if p is None:
158 return p
195 return p
159 return p.encode('utf-8')
196 return p.encode('utf-8')
160
197
161
198
162 def _bytes2sys(p):
199 def _bytes2sys(p):
163 if p is None:
200 if p is None:
164 return p
201 return p
165 return p.decode('utf-8')
202 return p.decode('utf-8')
166
203
167
204
168 original_env = os.environ.copy()
205 original_env = os.environ.copy()
169 osenvironb = getattr(os, 'environb', None)
206 osenvironb = getattr(os, 'environb', None)
170 if osenvironb is None:
207 if osenvironb is None:
171 # Windows lacks os.environb, for instance. A proxy over the real thing
208 # Windows lacks os.environb, for instance. A proxy over the real thing
172 # instead of a copy allows the environment to be updated via bytes on
209 # instead of a copy allows the environment to be updated via bytes on
173 # all platforms.
210 # all platforms.
174 class environbytes:
211 class environbytes:
175 def __init__(self, strenv):
212 def __init__(self, strenv):
176 self.__len__ = strenv.__len__
213 self.__len__ = strenv.__len__
177 self.clear = strenv.clear
214 self.clear = strenv.clear
178 self._strenv = strenv
215 self._strenv = strenv
179
216
180 def __getitem__(self, k):
217 def __getitem__(self, k):
181 v = self._strenv.__getitem__(_bytes2sys(k))
218 v = self._strenv.__getitem__(_bytes2sys(k))
182 return _sys2bytes(v)
219 return _sys2bytes(v)
183
220
184 def __setitem__(self, k, v):
221 def __setitem__(self, k, v):
185 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
222 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
186
223
187 def __delitem__(self, k):
224 def __delitem__(self, k):
188 self._strenv.__delitem__(_bytes2sys(k))
225 self._strenv.__delitem__(_bytes2sys(k))
189
226
190 def __contains__(self, k):
227 def __contains__(self, k):
191 return self._strenv.__contains__(_bytes2sys(k))
228 return self._strenv.__contains__(_bytes2sys(k))
192
229
193 def __iter__(self):
230 def __iter__(self):
194 return iter([_sys2bytes(k) for k in iter(self._strenv)])
231 return iter([_sys2bytes(k) for k in iter(self._strenv)])
195
232
196 def get(self, k, default=None):
233 def get(self, k, default=None):
197 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
234 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
198 return _sys2bytes(v)
235 return _sys2bytes(v)
199
236
200 def pop(self, k, default=None):
237 def pop(self, k, default=None):
201 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
238 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
202 return _sys2bytes(v)
239 return _sys2bytes(v)
203
240
204 osenvironb = environbytes(os.environ)
241 osenvironb = environbytes(os.environ)
205
242
206 getcwdb = getattr(os, 'getcwdb')
243 getcwdb = getattr(os, 'getcwdb')
207 if not getcwdb or WINDOWS:
244 if not getcwdb or WINDOWS:
208 getcwdb = lambda: _sys2bytes(os.getcwd())
245 getcwdb = lambda: _sys2bytes(os.getcwd())
209
246
210
247
211 if WINDOWS:
248 if WINDOWS:
212 _getcwdb = getcwdb
249 _getcwdb = getcwdb
213
250
214 def getcwdb():
251 def getcwdb():
215 cwd = _getcwdb()
252 cwd = _getcwdb()
216 if re.match(b'^[a-z]:', cwd):
253 if re.match(b'^[a-z]:', cwd):
217 # os.getcwd() is inconsistent on the capitalization of the drive
254 # os.getcwd() is inconsistent on the capitalization of the drive
218 # letter, so adjust it. see https://bugs.python.org/issue40368
255 # letter, so adjust it. see https://bugs.python.org/issue40368
219 cwd = cwd[0:1].upper() + cwd[1:]
256 cwd = cwd[0:1].upper() + cwd[1:]
220 return cwd
257 return cwd
221
258
222
259
223 # For Windows support
260 # For Windows support
224 wifexited = getattr(os, "WIFEXITED", lambda x: False)
261 wifexited = getattr(os, "WIFEXITED", lambda x: False)
225
262
226
263
227 # Whether to use IPv6
264 # Whether to use IPv6
228 def checksocketfamily(name, port=20058):
265 def checksocketfamily(name, port=20058):
229 """return true if we can listen on localhost using family=name
266 """return true if we can listen on localhost using family=name
230
267
231 name should be either 'AF_INET', or 'AF_INET6'.
268 name should be either 'AF_INET', or 'AF_INET6'.
232 port being used is okay - EADDRINUSE is considered as successful.
269 port being used is okay - EADDRINUSE is considered as successful.
233 """
270 """
234 family = getattr(socket, name, None)
271 family = getattr(socket, name, None)
235 if family is None:
272 if family is None:
236 return False
273 return False
237 try:
274 try:
238 s = socket.socket(family, socket.SOCK_STREAM)
275 s = socket.socket(family, socket.SOCK_STREAM)
239 s.bind(('localhost', port))
276 s.bind(('localhost', port))
240 s.close()
277 s.close()
241 return True
278 return True
242 except (socket.error, OSError) as exc:
279 except (socket.error, OSError) as exc:
243 if exc.errno == errno.EADDRINUSE:
280 if exc.errno == errno.EADDRINUSE:
244 return True
281 return True
245 elif exc.errno in (
282 elif exc.errno in (
246 errno.EADDRNOTAVAIL,
283 errno.EADDRNOTAVAIL,
247 errno.EPROTONOSUPPORT,
284 errno.EPROTONOSUPPORT,
248 errno.EAFNOSUPPORT,
285 errno.EAFNOSUPPORT,
249 ):
286 ):
250 return False
287 return False
251 else:
288 else:
252 raise
289 raise
253 else:
290 else:
254 return False
291 return False
255
292
256
293
257 # useipv6 will be set by parseargs
294 # useipv6 will be set by parseargs
258 useipv6 = None
295 useipv6 = None
259
296
260
297
261 def checkportisavailable(port):
298 def checkportisavailable(port):
262 """return true if a port seems free to bind on localhost"""
299 """return true if a port seems free to bind on localhost"""
263 if useipv6:
300 if useipv6:
264 family = socket.AF_INET6
301 family = socket.AF_INET6
265 else:
302 else:
266 family = socket.AF_INET
303 family = socket.AF_INET
267 try:
304 try:
268 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
305 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
269 s.bind(('localhost', port))
306 s.bind(('localhost', port))
270 return True
307 return True
271 except PermissionError:
308 except PermissionError:
272 return False
309 return False
273 except socket.error as exc:
310 except socket.error as exc:
274 if WINDOWS and exc.errno == errno.WSAEACCES:
311 if WINDOWS and exc.errno == errno.WSAEACCES:
275 return False
312 return False
276 if exc.errno not in (
313 if exc.errno not in (
277 errno.EADDRINUSE,
314 errno.EADDRINUSE,
278 errno.EADDRNOTAVAIL,
315 errno.EADDRNOTAVAIL,
279 errno.EPROTONOSUPPORT,
316 errno.EPROTONOSUPPORT,
280 ):
317 ):
281 raise
318 raise
282 return False
319 return False
283
320
284
321
285 closefds = os.name == 'posix'
322 closefds = os.name == 'posix'
286
323
287
324
288 def Popen4(cmd, wd, timeout, env=None):
325 def Popen4(cmd, wd, timeout, env=None):
289 processlock.acquire()
326 processlock.acquire()
290 p = subprocess.Popen(
327 p = subprocess.Popen(
291 _bytes2sys(cmd),
328 _bytes2sys(cmd),
292 shell=True,
329 shell=True,
293 bufsize=-1,
330 bufsize=-1,
294 cwd=_bytes2sys(wd),
331 cwd=_bytes2sys(wd),
295 env=env,
332 env=env,
296 close_fds=closefds,
333 close_fds=closefds,
297 stdin=subprocess.PIPE,
334 stdin=subprocess.PIPE,
298 stdout=subprocess.PIPE,
335 stdout=subprocess.PIPE,
299 stderr=subprocess.STDOUT,
336 stderr=subprocess.STDOUT,
300 )
337 )
301 processlock.release()
338 processlock.release()
302
339
303 p.fromchild = p.stdout
340 p.fromchild = p.stdout
304 p.tochild = p.stdin
341 p.tochild = p.stdin
305 p.childerr = p.stderr
342 p.childerr = p.stderr
306
343
307 p.timeout = False
344 p.timeout = False
308 if timeout:
345 if timeout:
309
346
310 def t():
347 def t():
311 start = time.time()
348 start = time.time()
312 while time.time() - start < timeout and p.returncode is None:
349 while time.time() - start < timeout and p.returncode is None:
313 time.sleep(0.1)
350 time.sleep(0.1)
314 p.timeout = True
351 p.timeout = True
315 vlog('# Timout reached for process %d' % p.pid)
352 vlog('# Timout reached for process %d' % p.pid)
316 if p.returncode is None:
353 if p.returncode is None:
317 terminate(p)
354 terminate(p)
318
355
319 threading.Thread(target=t).start()
356 threading.Thread(target=t).start()
320
357
321 return p
358 return p
322
359
323
360
324 if sys.executable:
361 if sys.executable:
325 sysexecutable = sys.executable
362 sysexecutable = sys.executable
326 elif os.environ.get('PYTHONEXECUTABLE'):
363 elif os.environ.get('PYTHONEXECUTABLE'):
327 sysexecutable = os.environ['PYTHONEXECUTABLE']
364 sysexecutable = os.environ['PYTHONEXECUTABLE']
328 elif os.environ.get('PYTHON'):
365 elif os.environ.get('PYTHON'):
329 sysexecutable = os.environ['PYTHON']
366 sysexecutable = os.environ['PYTHON']
330 else:
367 else:
331 raise AssertionError('Could not find Python interpreter')
368 raise AssertionError('Could not find Python interpreter')
332
369
333 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
370 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
334 IMPL_PATH = b'PYTHONPATH'
371 IMPL_PATH = b'PYTHONPATH'
335 if 'java' in sys.platform:
372 if 'java' in sys.platform:
336 IMPL_PATH = b'JYTHONPATH'
373 IMPL_PATH = b'JYTHONPATH'
337
374
338 default_defaults = {
375 default_defaults = {
339 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
376 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
340 'timeout': ('HGTEST_TIMEOUT', 360),
377 'timeout': ('HGTEST_TIMEOUT', 360),
341 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
378 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
342 'port': ('HGTEST_PORT', 20059),
379 'port': ('HGTEST_PORT', 20059),
343 'shell': ('HGTEST_SHELL', 'sh'),
380 'shell': ('HGTEST_SHELL', 'sh'),
344 }
381 }
345
382
346 defaults = default_defaults.copy()
383 defaults = default_defaults.copy()
347
384
348
385
349 def canonpath(path):
386 def canonpath(path):
350 return os.path.realpath(os.path.expanduser(path))
387 return os.path.realpath(os.path.expanduser(path))
351
388
352
389
353 def which(exe):
390 def which(exe):
354 # shutil.which only accept bytes from 3.8
391 # shutil.which only accept bytes from 3.8
355 cmd = _bytes2sys(exe)
392 cmd = _bytes2sys(exe)
356 real_exec = shutil.which(cmd)
393 real_exec = shutil.which(cmd)
357 return _sys2bytes(real_exec)
394 return _sys2bytes(real_exec)
358
395
359
396
360 def parselistfiles(files, listtype, warn=True):
397 def parselistfiles(files, listtype, warn=True):
361 entries = dict()
398 entries = dict()
362 for filename in files:
399 for filename in files:
363 try:
400 try:
364 path = os.path.expanduser(os.path.expandvars(filename))
401 path = os.path.expanduser(os.path.expandvars(filename))
365 f = open(path, "rb")
402 f = open(path, "rb")
366 except FileNotFoundError:
403 except FileNotFoundError:
367 if warn:
404 if warn:
368 print("warning: no such %s file: %s" % (listtype, filename))
405 print("warning: no such %s file: %s" % (listtype, filename))
369 continue
406 continue
370
407
371 for line in f.readlines():
408 for line in f.readlines():
372 line = line.split(b'#', 1)[0].strip()
409 line = line.split(b'#', 1)[0].strip()
373 if line:
410 if line:
374 # Ensure path entries are compatible with os.path.relpath()
411 # Ensure path entries are compatible with os.path.relpath()
375 entries[os.path.normpath(line)] = filename
412 entries[os.path.normpath(line)] = filename
376
413
377 f.close()
414 f.close()
378 return entries
415 return entries
379
416
380
417
381 def parsettestcases(path):
418 def parsettestcases(path):
382 """read a .t test file, return a set of test case names
419 """read a .t test file, return a set of test case names
383
420
384 If path does not exist, return an empty set.
421 If path does not exist, return an empty set.
385 """
422 """
386 cases = []
423 cases = []
387 try:
424 try:
388 with open(path, 'rb') as f:
425 with open(path, 'rb') as f:
389 for l in f:
426 for l in f:
390 if l.startswith(b'#testcases '):
427 if l.startswith(b'#testcases '):
391 cases.append(sorted(l[11:].split()))
428 cases.append(sorted(l[11:].split()))
392 except FileNotFoundError:
429 except FileNotFoundError:
393 pass
430 pass
394 return cases
431 return cases
395
432
396
433
397 def getparser():
434 def getparser():
398 """Obtain the OptionParser used by the CLI."""
435 """Obtain the OptionParser used by the CLI."""
399 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
436 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
400
437
401 selection = parser.add_argument_group('Test Selection')
438 selection = parser.add_argument_group('Test Selection')
402 selection.add_argument(
439 selection.add_argument(
403 '--allow-slow-tests',
440 '--allow-slow-tests',
404 action='store_true',
441 action='store_true',
405 help='allow extremely slow tests',
442 help='allow extremely slow tests',
406 )
443 )
407 selection.add_argument(
444 selection.add_argument(
408 "--blacklist",
445 "--blacklist",
409 action="append",
446 action="append",
410 help="skip tests listed in the specified blacklist file",
447 help="skip tests listed in the specified blacklist file",
411 )
448 )
412 selection.add_argument(
449 selection.add_argument(
413 "--changed",
450 "--changed",
414 help="run tests that are changed in parent rev or working directory",
451 help="run tests that are changed in parent rev or working directory",
415 )
452 )
416 selection.add_argument(
453 selection.add_argument(
417 "-k", "--keywords", help="run tests matching keywords"
454 "-k", "--keywords", help="run tests matching keywords"
418 )
455 )
419 selection.add_argument(
456 selection.add_argument(
420 "-r", "--retest", action="store_true", help="retest failed tests"
457 "-r", "--retest", action="store_true", help="retest failed tests"
421 )
458 )
422 selection.add_argument(
459 selection.add_argument(
423 "--test-list",
460 "--test-list",
424 action="append",
461 action="append",
425 help="read tests to run from the specified file",
462 help="read tests to run from the specified file",
426 )
463 )
427 selection.add_argument(
464 selection.add_argument(
428 "--whitelist",
465 "--whitelist",
429 action="append",
466 action="append",
430 help="always run tests listed in the specified whitelist file",
467 help="always run tests listed in the specified whitelist file",
431 )
468 )
432 selection.add_argument(
469 selection.add_argument(
433 'tests', metavar='TESTS', nargs='*', help='Tests to run'
470 'tests', metavar='TESTS', nargs='*', help='Tests to run'
434 )
471 )
435
472
436 harness = parser.add_argument_group('Test Harness Behavior')
473 harness = parser.add_argument_group('Test Harness Behavior')
437 harness.add_argument(
474 harness.add_argument(
438 '--bisect-repo',
475 '--bisect-repo',
439 metavar='bisect_repo',
476 metavar='bisect_repo',
440 help=(
477 help=(
441 "Path of a repo to bisect. Use together with " "--known-good-rev"
478 "Path of a repo to bisect. Use together with " "--known-good-rev"
442 ),
479 ),
443 )
480 )
444 harness.add_argument(
481 harness.add_argument(
445 "-d",
482 "-d",
446 "--debug",
483 "--debug",
447 action="store_true",
484 action="store_true",
448 help="debug mode: write output of test scripts to console"
485 help="debug mode: write output of test scripts to console"
449 " rather than capturing and diffing it (disables timeout)",
486 " rather than capturing and diffing it (disables timeout)",
450 )
487 )
451 harness.add_argument(
488 harness.add_argument(
452 "-f",
489 "-f",
453 "--first",
490 "--first",
454 action="store_true",
491 action="store_true",
455 help="exit on the first test failure",
492 help="exit on the first test failure",
456 )
493 )
457 harness.add_argument(
494 harness.add_argument(
458 "-i",
495 "-i",
459 "--interactive",
496 "--interactive",
460 action="store_true",
497 action="store_true",
461 help="prompt to accept changed output",
498 help="prompt to accept changed output",
462 )
499 )
463 harness.add_argument(
500 harness.add_argument(
464 "-j",
501 "-j",
465 "--jobs",
502 "--jobs",
466 type=int,
503 type=int,
467 help="number of jobs to run in parallel"
504 help="number of jobs to run in parallel"
468 " (default: $%s or %d)" % defaults['jobs'],
505 " (default: $%s or %d)" % defaults['jobs'],
469 )
506 )
470 harness.add_argument(
507 harness.add_argument(
471 "--keep-tmpdir",
508 "--keep-tmpdir",
472 action="store_true",
509 action="store_true",
473 help="keep temporary directory after running tests",
510 help="keep temporary directory after running tests",
474 )
511 )
475 harness.add_argument(
512 harness.add_argument(
476 '--known-good-rev',
513 '--known-good-rev',
477 metavar="known_good_rev",
514 metavar="known_good_rev",
478 help=(
515 help=(
479 "Automatically bisect any failures using this "
516 "Automatically bisect any failures using this "
480 "revision as a known-good revision."
517 "revision as a known-good revision."
481 ),
518 ),
482 )
519 )
483 harness.add_argument(
520 harness.add_argument(
484 "--list-tests",
521 "--list-tests",
485 action="store_true",
522 action="store_true",
486 help="list tests instead of running them",
523 help="list tests instead of running them",
487 )
524 )
488 harness.add_argument(
525 harness.add_argument(
489 "--loop", action="store_true", help="loop tests repeatedly"
526 "--loop", action="store_true", help="loop tests repeatedly"
490 )
527 )
491 harness.add_argument(
528 harness.add_argument(
492 '--random', action="store_true", help='run tests in random order'
529 '--random', action="store_true", help='run tests in random order'
493 )
530 )
494 harness.add_argument(
531 harness.add_argument(
495 '--order-by-runtime',
532 '--order-by-runtime',
496 action="store_true",
533 action="store_true",
497 help='run slowest tests first, according to .testtimes',
534 help='run slowest tests first, according to .testtimes',
498 )
535 )
499 harness.add_argument(
536 harness.add_argument(
500 "-p",
537 "-p",
501 "--port",
538 "--port",
502 type=int,
539 type=int,
503 help="port on which servers should listen"
540 help="port on which servers should listen"
504 " (default: $%s or %d)" % defaults['port'],
541 " (default: $%s or %d)" % defaults['port'],
505 )
542 )
506 harness.add_argument(
543 harness.add_argument(
507 '--profile-runner',
544 '--profile-runner',
508 action='store_true',
545 action='store_true',
509 help='run statprof on run-tests',
546 help='run statprof on run-tests',
510 )
547 )
511 harness.add_argument(
548 harness.add_argument(
512 "-R", "--restart", action="store_true", help="restart at last error"
549 "-R", "--restart", action="store_true", help="restart at last error"
513 )
550 )
514 harness.add_argument(
551 harness.add_argument(
515 "--runs-per-test",
552 "--runs-per-test",
516 type=int,
553 type=int,
517 dest="runs_per_test",
554 dest="runs_per_test",
518 help="run each test N times (default=1)",
555 help="run each test N times (default=1)",
519 default=1,
556 default=1,
520 )
557 )
521 harness.add_argument(
558 harness.add_argument(
522 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
559 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
523 )
560 )
524 harness.add_argument(
561 harness.add_argument(
525 '--showchannels', action='store_true', help='show scheduling channels'
562 '--showchannels', action='store_true', help='show scheduling channels'
526 )
563 )
527 harness.add_argument(
564 harness.add_argument(
528 "--slowtimeout",
565 "--slowtimeout",
529 type=int,
566 type=int,
530 help="kill errant slow tests after SLOWTIMEOUT seconds"
567 help="kill errant slow tests after SLOWTIMEOUT seconds"
531 " (default: $%s or %d)" % defaults['slowtimeout'],
568 " (default: $%s or %d)" % defaults['slowtimeout'],
532 )
569 )
533 harness.add_argument(
570 harness.add_argument(
534 "-t",
571 "-t",
535 "--timeout",
572 "--timeout",
536 type=int,
573 type=int,
537 help="kill errant tests after TIMEOUT seconds"
574 help="kill errant tests after TIMEOUT seconds"
538 " (default: $%s or %d)" % defaults['timeout'],
575 " (default: $%s or %d)" % defaults['timeout'],
539 )
576 )
540 harness.add_argument(
577 harness.add_argument(
541 "--tmpdir",
578 "--tmpdir",
542 help="run tests in the given temporary directory"
579 help="run tests in the given temporary directory"
543 " (implies --keep-tmpdir)",
580 " (implies --keep-tmpdir)",
544 )
581 )
545 harness.add_argument(
582 harness.add_argument(
546 "-v", "--verbose", action="store_true", help="output verbose messages"
583 "-v", "--verbose", action="store_true", help="output verbose messages"
547 )
584 )
548
585
549 hgconf = parser.add_argument_group('Mercurial Configuration')
586 hgconf = parser.add_argument_group('Mercurial Configuration')
550 hgconf.add_argument(
587 hgconf.add_argument(
551 "--chg",
588 "--chg",
552 action="store_true",
589 action="store_true",
553 help="install and use chg wrapper in place of hg",
590 help="install and use chg wrapper in place of hg",
554 )
591 )
555 hgconf.add_argument(
592 hgconf.add_argument(
556 "--chg-debug",
593 "--chg-debug",
557 action="store_true",
594 action="store_true",
558 help="show chg debug logs",
595 help="show chg debug logs",
559 )
596 )
560 hgconf.add_argument(
597 hgconf.add_argument(
561 "--rhg",
598 "--rhg",
562 action="store_true",
599 action="store_true",
563 help="install and use rhg Rust implementation in place of hg",
600 help="install and use rhg Rust implementation in place of hg",
564 )
601 )
565 hgconf.add_argument(
602 hgconf.add_argument(
566 "--pyoxidized",
603 "--pyoxidized",
567 action="store_true",
604 action="store_true",
568 help="build the hg binary using pyoxidizer",
605 help="build the hg binary using pyoxidizer",
569 )
606 )
570 hgconf.add_argument("--compiler", help="compiler to build with")
607 hgconf.add_argument("--compiler", help="compiler to build with")
571 hgconf.add_argument(
608 hgconf.add_argument(
572 '--extra-config-opt',
609 '--extra-config-opt',
573 action="append",
610 action="append",
574 default=[],
611 default=[],
575 help='set the given config opt in the test hgrc',
612 help='set the given config opt in the test hgrc',
576 )
613 )
577 hgconf.add_argument(
614 hgconf.add_argument(
578 "-l",
615 "-l",
579 "--local",
616 "--local",
580 action="store_true",
617 action="store_true",
581 help="shortcut for --with-hg=<testdir>/../hg, "
618 help="shortcut for --with-hg=<testdir>/../hg, "
582 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
619 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
583 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
620 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
584 )
621 )
585 hgconf.add_argument(
622 hgconf.add_argument(
586 "--ipv6",
623 "--ipv6",
587 action="store_true",
624 action="store_true",
588 help="prefer IPv6 to IPv4 for network related tests",
625 help="prefer IPv6 to IPv4 for network related tests",
589 )
626 )
590 hgconf.add_argument(
627 hgconf.add_argument(
591 "--pure",
628 "--pure",
592 action="store_true",
629 action="store_true",
593 help="use pure Python code instead of C extensions",
630 help="use pure Python code instead of C extensions",
594 )
631 )
595 hgconf.add_argument(
632 hgconf.add_argument(
596 "--rust",
633 "--rust",
597 action="store_true",
634 action="store_true",
598 help="use Rust code alongside C extensions",
635 help="use Rust code alongside C extensions",
599 )
636 )
600 hgconf.add_argument(
637 hgconf.add_argument(
601 "--no-rust",
638 "--no-rust",
602 action="store_true",
639 action="store_true",
603 help="do not use Rust code even if compiled",
640 help="do not use Rust code even if compiled",
604 )
641 )
605 hgconf.add_argument(
642 hgconf.add_argument(
606 "--with-chg",
643 "--with-chg",
607 metavar="CHG",
644 metavar="CHG",
608 help="use specified chg wrapper in place of hg",
645 help="use specified chg wrapper in place of hg",
609 )
646 )
610 hgconf.add_argument(
647 hgconf.add_argument(
611 "--with-rhg",
648 "--with-rhg",
612 metavar="RHG",
649 metavar="RHG",
613 help="use specified rhg Rust implementation in place of hg",
650 help="use specified rhg Rust implementation in place of hg",
614 )
651 )
615 hgconf.add_argument(
652 hgconf.add_argument(
616 "--with-hg",
653 "--with-hg",
617 metavar="HG",
654 metavar="HG",
618 help="test using specified hg script rather than a "
655 help="test using specified hg script rather than a "
619 "temporary installation",
656 "temporary installation",
620 )
657 )
621
658
622 reporting = parser.add_argument_group('Results Reporting')
659 reporting = parser.add_argument_group('Results Reporting')
623 reporting.add_argument(
660 reporting.add_argument(
624 "-C",
661 "-C",
625 "--annotate",
662 "--annotate",
626 action="store_true",
663 action="store_true",
627 help="output files annotated with coverage",
664 help="output files annotated with coverage",
628 )
665 )
629 reporting.add_argument(
666 reporting.add_argument(
630 "--color",
667 "--color",
631 choices=["always", "auto", "never"],
668 choices=["always", "auto", "never"],
632 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
669 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
633 help="colorisation: always|auto|never (default: auto)",
670 help="colorisation: always|auto|never (default: auto)",
634 )
671 )
635 reporting.add_argument(
672 reporting.add_argument(
636 "-c",
673 "-c",
637 "--cover",
674 "--cover",
638 action="store_true",
675 action="store_true",
639 help="print a test coverage report",
676 help="print a test coverage report",
640 )
677 )
641 reporting.add_argument(
678 reporting.add_argument(
642 '--exceptions',
679 '--exceptions',
643 action='store_true',
680 action='store_true',
644 help='log all exceptions and generate an exception report',
681 help='log all exceptions and generate an exception report',
645 )
682 )
646 reporting.add_argument(
683 reporting.add_argument(
647 "-H",
684 "-H",
648 "--htmlcov",
685 "--htmlcov",
649 action="store_true",
686 action="store_true",
650 help="create an HTML report of the coverage of the files",
687 help="create an HTML report of the coverage of the files",
651 )
688 )
652 reporting.add_argument(
689 reporting.add_argument(
653 "--json",
690 "--json",
654 action="store_true",
691 action="store_true",
655 help="store test result data in 'report.json' file",
692 help="store test result data in 'report.json' file",
656 )
693 )
657 reporting.add_argument(
694 reporting.add_argument(
658 "--outputdir",
695 "--outputdir",
659 help="directory to write error logs to (default=test directory)",
696 help="directory to write error logs to (default=test directory)",
660 )
697 )
661 reporting.add_argument(
698 reporting.add_argument(
662 "-n", "--nodiff", action="store_true", help="skip showing test changes"
699 "-n", "--nodiff", action="store_true", help="skip showing test changes"
663 )
700 )
664 reporting.add_argument(
701 reporting.add_argument(
665 "-S",
702 "-S",
666 "--noskips",
703 "--noskips",
667 action="store_true",
704 action="store_true",
668 help="don't report skip tests verbosely",
705 help="don't report skip tests verbosely",
669 )
706 )
670 reporting.add_argument(
707 reporting.add_argument(
671 "--time", action="store_true", help="time how long each test takes"
708 "--time", action="store_true", help="time how long each test takes"
672 )
709 )
673 reporting.add_argument("--view", help="external diff viewer")
710 reporting.add_argument("--view", help="external diff viewer")
674 reporting.add_argument(
711 reporting.add_argument(
675 "--xunit", help="record xunit results at specified path"
712 "--xunit", help="record xunit results at specified path"
676 )
713 )
677
714
678 for option, (envvar, default) in defaults.items():
715 for option, (envvar, default) in defaults.items():
679 defaults[option] = type(default)(os.environ.get(envvar, default))
716 defaults[option] = type(default)(os.environ.get(envvar, default))
680 parser.set_defaults(**defaults)
717 parser.set_defaults(**defaults)
681
718
682 return parser
719 return parser
683
720
684
721
685 def parseargs(args, parser):
722 def parseargs(args, parser):
686 """Parse arguments with our OptionParser and validate results."""
723 """Parse arguments with our OptionParser and validate results."""
687 options = parser.parse_args(args)
724 options = parser.parse_args(args)
688
725
689 # jython is always pure
726 # jython is always pure
690 if 'java' in sys.platform or '__pypy__' in sys.modules:
727 if 'java' in sys.platform or '__pypy__' in sys.modules:
691 options.pure = True
728 options.pure = True
692
729
693 if platform.python_implementation() != 'CPython' and options.rust:
730 if platform.python_implementation() != 'CPython' and options.rust:
694 parser.error('Rust extensions are only available with CPython')
731 parser.error('Rust extensions are only available with CPython')
695
732
696 if options.pure and options.rust:
733 if options.pure and options.rust:
697 parser.error('--rust cannot be used with --pure')
734 parser.error('--rust cannot be used with --pure')
698
735
699 if options.rust and options.no_rust:
736 if options.rust and options.no_rust:
700 parser.error('--rust cannot be used with --no-rust')
737 parser.error('--rust cannot be used with --no-rust')
701
738
702 if options.local:
739 if options.local:
703 if options.with_hg or options.with_rhg or options.with_chg:
740 if options.with_hg or options.with_rhg or options.with_chg:
704 parser.error(
741 parser.error(
705 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
742 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
706 )
743 )
707 if options.pyoxidized:
744 if options.pyoxidized:
708 parser.error('--pyoxidized does not work with --local (yet)')
745 parser.error('--pyoxidized does not work with --local (yet)')
709 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
746 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
710 reporootdir = os.path.dirname(testdir)
747 reporootdir = os.path.dirname(testdir)
711 pathandattrs = [(b'hg', 'with_hg')]
748 pathandattrs = [(b'hg', 'with_hg')]
712 if options.chg:
749 if options.chg:
713 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
750 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
714 if options.rhg:
751 if options.rhg:
715 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
752 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
716 for relpath, attr in pathandattrs:
753 for relpath, attr in pathandattrs:
717 binpath = os.path.join(reporootdir, relpath)
754 binpath = os.path.join(reporootdir, relpath)
718 if not (WINDOWS or os.access(binpath, os.X_OK)):
755 if not (WINDOWS or os.access(binpath, os.X_OK)):
719 parser.error(
756 parser.error(
720 '--local specified, but %r not found or '
757 '--local specified, but %r not found or '
721 'not executable' % binpath
758 'not executable' % binpath
722 )
759 )
723 setattr(options, attr, _bytes2sys(binpath))
760 setattr(options, attr, _bytes2sys(binpath))
724
761
725 if options.with_hg:
762 if options.with_hg:
726 options.with_hg = canonpath(_sys2bytes(options.with_hg))
763 options.with_hg = canonpath(_sys2bytes(options.with_hg))
727 if not (
764 if not (
728 os.path.isfile(options.with_hg)
765 os.path.isfile(options.with_hg)
729 and os.access(options.with_hg, os.X_OK)
766 and os.access(options.with_hg, os.X_OK)
730 ):
767 ):
731 parser.error('--with-hg must specify an executable hg script')
768 parser.error('--with-hg must specify an executable hg script')
732 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
769 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
733 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
770 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
734 msg %= _bytes2sys(os.path.basename(options.with_hg))
771 msg %= _bytes2sys(os.path.basename(options.with_hg))
735 sys.stderr.write(msg)
772 sys.stderr.write(msg)
736 sys.stderr.flush()
773 sys.stderr.flush()
737
774
738 if (options.chg or options.with_chg) and WINDOWS:
775 if (options.chg or options.with_chg) and WINDOWS:
739 parser.error('chg does not work on %s' % os.name)
776 parser.error('chg does not work on %s' % os.name)
740 if (options.rhg or options.with_rhg) and WINDOWS:
777 if (options.rhg or options.with_rhg) and WINDOWS:
741 parser.error('rhg does not work on %s' % os.name)
778 parser.error('rhg does not work on %s' % os.name)
742 if options.pyoxidized and not (MACOS or WINDOWS):
779 if options.pyoxidized and not (MACOS or WINDOWS):
743 parser.error('--pyoxidized is currently macOS and Windows only')
780 parser.error('--pyoxidized is currently macOS and Windows only')
744 if options.with_chg:
781 if options.with_chg:
745 options.chg = False # no installation to temporary location
782 options.chg = False # no installation to temporary location
746 options.with_chg = canonpath(_sys2bytes(options.with_chg))
783 options.with_chg = canonpath(_sys2bytes(options.with_chg))
747 if not (
784 if not (
748 os.path.isfile(options.with_chg)
785 os.path.isfile(options.with_chg)
749 and os.access(options.with_chg, os.X_OK)
786 and os.access(options.with_chg, os.X_OK)
750 ):
787 ):
751 parser.error('--with-chg must specify a chg executable')
788 parser.error('--with-chg must specify a chg executable')
752 if options.with_rhg:
789 if options.with_rhg:
753 options.rhg = False # no installation to temporary location
790 options.rhg = False # no installation to temporary location
754 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
791 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
755 if not (
792 if not (
756 os.path.isfile(options.with_rhg)
793 os.path.isfile(options.with_rhg)
757 and os.access(options.with_rhg, os.X_OK)
794 and os.access(options.with_rhg, os.X_OK)
758 ):
795 ):
759 parser.error('--with-rhg must specify a rhg executable')
796 parser.error('--with-rhg must specify a rhg executable')
760 if options.chg and options.with_hg:
797 if options.chg and options.with_hg:
761 # chg shares installation location with hg
798 # chg shares installation location with hg
762 parser.error(
799 parser.error(
763 '--chg does not work when --with-hg is specified '
800 '--chg does not work when --with-hg is specified '
764 '(use --with-chg instead)'
801 '(use --with-chg instead)'
765 )
802 )
766 if options.rhg and options.with_hg:
803 if options.rhg and options.with_hg:
767 # rhg shares installation location with hg
804 # rhg shares installation location with hg
768 parser.error(
805 parser.error(
769 '--rhg does not work when --with-hg is specified '
806 '--rhg does not work when --with-hg is specified '
770 '(use --with-rhg instead)'
807 '(use --with-rhg instead)'
771 )
808 )
772 if options.rhg and options.chg:
809 if options.rhg and options.chg:
773 parser.error('--rhg and --chg do not work together')
810 parser.error('--rhg and --chg do not work together')
774
811
775 if options.color == 'always' and not pygmentspresent:
812 if options.color == 'always' and not pygmentspresent:
776 sys.stderr.write(
813 sys.stderr.write(
777 'warning: --color=always ignored because '
814 'warning: --color=always ignored because '
778 'pygments is not installed\n'
815 'pygments is not installed\n'
779 )
816 )
780
817
781 if options.bisect_repo and not options.known_good_rev:
818 if options.bisect_repo and not options.known_good_rev:
782 parser.error("--bisect-repo cannot be used without --known-good-rev")
819 parser.error("--bisect-repo cannot be used without --known-good-rev")
783
820
784 global useipv6
821 global useipv6
785 if options.ipv6:
822 if options.ipv6:
786 useipv6 = checksocketfamily('AF_INET6')
823 useipv6 = checksocketfamily('AF_INET6')
787 else:
824 else:
788 # only use IPv6 if IPv4 is unavailable and IPv6 is available
825 # only use IPv6 if IPv4 is unavailable and IPv6 is available
789 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
826 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
790 'AF_INET6'
827 'AF_INET6'
791 )
828 )
792
829
793 options.anycoverage = options.cover or options.annotate or options.htmlcov
830 options.anycoverage = options.cover or options.annotate or options.htmlcov
794 if options.anycoverage:
831 if options.anycoverage:
795 try:
832 try:
796 import coverage
833 import coverage
797
834
798 coverage.__version__ # silence unused import warning
835 coverage.__version__ # silence unused import warning
799 except ImportError:
836 except ImportError:
800 parser.error('coverage options now require the coverage package')
837 parser.error('coverage options now require the coverage package')
801
838
802 if options.anycoverage and options.local:
839 if options.anycoverage and options.local:
803 # this needs some path mangling somewhere, I guess
840 # this needs some path mangling somewhere, I guess
804 parser.error(
841 parser.error(
805 "sorry, coverage options do not work when --local " "is specified"
842 "sorry, coverage options do not work when --local " "is specified"
806 )
843 )
807
844
808 if options.anycoverage and options.with_hg:
845 if options.anycoverage and options.with_hg:
809 parser.error(
846 parser.error(
810 "sorry, coverage options do not work when --with-hg " "is specified"
847 "sorry, coverage options do not work when --with-hg " "is specified"
811 )
848 )
812
849
813 global verbose
850 global verbose
814 if options.verbose:
851 if options.verbose:
815 verbose = ''
852 verbose = ''
816
853
817 if options.tmpdir:
854 if options.tmpdir:
818 options.tmpdir = canonpath(options.tmpdir)
855 options.tmpdir = canonpath(options.tmpdir)
819
856
820 if options.jobs < 1:
857 if options.jobs < 1:
821 parser.error('--jobs must be positive')
858 parser.error('--jobs must be positive')
822 if options.interactive and options.debug:
859 if options.interactive and options.debug:
823 parser.error("-i/--interactive and -d/--debug are incompatible")
860 parser.error("-i/--interactive and -d/--debug are incompatible")
824 if options.debug:
861 if options.debug:
825 if options.timeout != defaults['timeout']:
862 if options.timeout != defaults['timeout']:
826 sys.stderr.write('warning: --timeout option ignored with --debug\n')
863 sys.stderr.write('warning: --timeout option ignored with --debug\n')
827 if options.slowtimeout != defaults['slowtimeout']:
864 if options.slowtimeout != defaults['slowtimeout']:
828 sys.stderr.write(
865 sys.stderr.write(
829 'warning: --slowtimeout option ignored with --debug\n'
866 'warning: --slowtimeout option ignored with --debug\n'
830 )
867 )
831 options.timeout = 0
868 options.timeout = 0
832 options.slowtimeout = 0
869 options.slowtimeout = 0
833
870
834 if options.blacklist:
871 if options.blacklist:
835 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
872 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
836 if options.whitelist:
873 if options.whitelist:
837 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
874 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
838 else:
875 else:
839 options.whitelisted = {}
876 options.whitelisted = {}
840
877
841 if options.showchannels:
878 if options.showchannels:
842 options.nodiff = True
879 options.nodiff = True
843
880
844 return options
881 return options
845
882
846
883
847 def rename(src, dst):
884 def rename(src, dst):
848 """Like os.rename(), trade atomicity and opened files friendliness
885 """Like os.rename(), trade atomicity and opened files friendliness
849 for existing destination support.
886 for existing destination support.
850 """
887 """
851 shutil.copy(src, dst)
888 shutil.copy(src, dst)
852 os.remove(src)
889 os.remove(src)
853
890
854
891
855 def makecleanable(path):
892 def makecleanable(path):
856 """Try to fix directory permission recursively so that the entire tree
893 """Try to fix directory permission recursively so that the entire tree
857 can be deleted"""
894 can be deleted"""
858 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
895 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
859 for d in dirnames:
896 for d in dirnames:
860 p = os.path.join(dirpath, d)
897 p = os.path.join(dirpath, d)
861 try:
898 try:
862 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
899 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
863 except OSError:
900 except OSError:
864 pass
901 pass
865
902
866
903
867 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
904 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
868
905
869
906
870 def getdiff(expected, output, ref, err):
907 def getdiff(expected, output, ref, err):
871 servefail = False
908 servefail = False
872 lines = []
909 lines = []
873 for line in _unified_diff(expected, output, ref, err):
910 for line in _unified_diff(expected, output, ref, err):
874 if line.startswith(b'+++') or line.startswith(b'---'):
911 if line.startswith(b'+++') or line.startswith(b'---'):
875 line = line.replace(b'\\', b'/')
912 line = line.replace(b'\\', b'/')
876 if line.endswith(b' \n'):
913 if line.endswith(b' \n'):
877 line = line[:-2] + b'\n'
914 line = line[:-2] + b'\n'
878 lines.append(line)
915 lines.append(line)
879 if not servefail and line.startswith(
916 if not servefail and line.startswith(
880 b'+ abort: child process failed to start'
917 b'+ abort: child process failed to start'
881 ):
918 ):
882 servefail = True
919 servefail = True
883
920
884 return servefail, lines
921 return servefail, lines
885
922
886
923
887 verbose = False
924 verbose = False
888
925
889
926
890 def vlog(*msg):
927 def vlog(*msg):
891 """Log only when in verbose mode."""
928 """Log only when in verbose mode."""
892 if verbose is False:
929 if verbose is False:
893 return
930 return
894
931
895 return log(*msg)
932 return log(*msg)
896
933
897
934
898 # Bytes that break XML even in a CDATA block: control characters 0-31
935 # Bytes that break XML even in a CDATA block: control characters 0-31
899 # sans \t, \n and \r
936 # sans \t, \n and \r
900 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
937 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
901
938
902 # Match feature conditionalized output lines in the form, capturing the feature
939 # Match feature conditionalized output lines in the form, capturing the feature
903 # list in group 2, and the preceeding line output in group 1:
940 # list in group 2, and the preceeding line output in group 1:
904 #
941 #
905 # output..output (feature !)\n
942 # output..output (feature !)\n
906 optline = re.compile(br'(.*) \((.+?) !\)\n$')
943 optline = re.compile(br'(.*) \((.+?) !\)\n$')
907
944
908
945
909 def cdatasafe(data):
946 def cdatasafe(data):
910 """Make a string safe to include in a CDATA block.
947 """Make a string safe to include in a CDATA block.
911
948
912 Certain control characters are illegal in a CDATA block, and
949 Certain control characters are illegal in a CDATA block, and
913 there's no way to include a ]]> in a CDATA either. This function
950 there's no way to include a ]]> in a CDATA either. This function
914 replaces illegal bytes with ? and adds a space between the ]] so
951 replaces illegal bytes with ? and adds a space between the ]] so
915 that it won't break the CDATA block.
952 that it won't break the CDATA block.
916 """
953 """
917 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
954 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
918
955
919
956
920 def log(*msg):
957 def log(*msg):
921 """Log something to stdout.
958 """Log something to stdout.
922
959
923 Arguments are strings to print.
960 Arguments are strings to print.
924 """
961 """
925 with iolock:
962 with iolock:
926 if verbose:
963 if verbose:
927 print(verbose, end=' ')
964 print(verbose, end=' ')
928 for m in msg:
965 for m in msg:
929 print(m, end=' ')
966 print(m, end=' ')
930 print()
967 print()
931 sys.stdout.flush()
968 sys.stdout.flush()
932
969
933
970
934 def highlightdiff(line, color):
971 def highlightdiff(line, color):
935 if not color:
972 if not color:
936 return line
973 return line
937 assert pygmentspresent
974 assert pygmentspresent
938 return pygments.highlight(
975 return pygments.highlight(
939 line.decode('latin1'), difflexer, terminal256formatter
976 line.decode('latin1'), difflexer, terminal256formatter
940 ).encode('latin1')
977 ).encode('latin1')
941
978
942
979
943 def highlightmsg(msg, color):
980 def highlightmsg(msg, color):
944 if not color:
981 if not color:
945 return msg
982 return msg
946 assert pygmentspresent
983 assert pygmentspresent
947 return pygments.highlight(msg, runnerlexer, runnerformatter)
984 return pygments.highlight(msg, runnerlexer, runnerformatter)
948
985
949
986
987 def highlight_progress(progress, color):
988 if not color:
989 return progress
990 assert pygmentspresent
991 token = progress_type.get(progress)
992 if token is None:
993 return progress
994 style = runnerformatter.style_string.get(str(token))
995 if style is None:
996 return progress
997 else:
998 return style[0] + progress + style[1]
999
1000
950 def terminate(proc):
1001 def terminate(proc):
951 """Terminate subprocess"""
1002 """Terminate subprocess"""
952 vlog('# Terminating process %d' % proc.pid)
1003 vlog('# Terminating process %d' % proc.pid)
953 try:
1004 try:
954 proc.terminate()
1005 proc.terminate()
955 except OSError:
1006 except OSError:
956 pass
1007 pass
957
1008
958
1009
959 def killdaemons(pidfile):
1010 def killdaemons(pidfile):
960 import killdaemons as killmod
1011 import killdaemons as killmod
961
1012
962 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
1013 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
963
1014
964
1015
965 # sysconfig is not thread-safe (https://github.com/python/cpython/issues/92452)
1016 # sysconfig is not thread-safe (https://github.com/python/cpython/issues/92452)
966 sysconfiglock = threading.Lock()
1017 sysconfiglock = threading.Lock()
967
1018
968
1019
969 class Test(unittest.TestCase):
1020 class Test(unittest.TestCase):
970 """Encapsulates a single, runnable test.
1021 """Encapsulates a single, runnable test.
971
1022
972 While this class conforms to the unittest.TestCase API, it differs in that
1023 While this class conforms to the unittest.TestCase API, it differs in that
973 instances need to be instantiated manually. (Typically, unittest.TestCase
1024 instances need to be instantiated manually. (Typically, unittest.TestCase
974 classes are instantiated automatically by scanning modules.)
1025 classes are instantiated automatically by scanning modules.)
975 """
1026 """
976
1027
977 # Status code reserved for skipped tests (used by hghave).
1028 # Status code reserved for skipped tests (used by hghave).
978 SKIPPED_STATUS = 80
1029 SKIPPED_STATUS = 80
979
1030
980 def __init__(
1031 def __init__(
981 self,
1032 self,
982 path,
1033 path,
983 outputdir,
1034 outputdir,
984 tmpdir,
1035 tmpdir,
985 keeptmpdir=False,
1036 keeptmpdir=False,
986 debug=False,
1037 debug=False,
987 first=False,
1038 first=False,
988 timeout=None,
1039 timeout=None,
989 startport=None,
1040 startport=None,
990 extraconfigopts=None,
1041 extraconfigopts=None,
991 shell=None,
1042 shell=None,
992 hgcommand=None,
1043 hgcommand=None,
993 slowtimeout=None,
1044 slowtimeout=None,
994 usechg=False,
1045 usechg=False,
995 chgdebug=False,
1046 chgdebug=False,
996 useipv6=False,
1047 useipv6=False,
997 ):
1048 ):
998 """Create a test from parameters.
1049 """Create a test from parameters.
999
1050
1000 path is the full path to the file defining the test.
1051 path is the full path to the file defining the test.
1001
1052
1002 tmpdir is the main temporary directory to use for this test.
1053 tmpdir is the main temporary directory to use for this test.
1003
1054
1004 keeptmpdir determines whether to keep the test's temporary directory
1055 keeptmpdir determines whether to keep the test's temporary directory
1005 after execution. It defaults to removal (False).
1056 after execution. It defaults to removal (False).
1006
1057
1007 debug mode will make the test execute verbosely, with unfiltered
1058 debug mode will make the test execute verbosely, with unfiltered
1008 output.
1059 output.
1009
1060
1010 timeout controls the maximum run time of the test. It is ignored when
1061 timeout controls the maximum run time of the test. It is ignored when
1011 debug is True. See slowtimeout for tests with #require slow.
1062 debug is True. See slowtimeout for tests with #require slow.
1012
1063
1013 slowtimeout overrides timeout if the test has #require slow.
1064 slowtimeout overrides timeout if the test has #require slow.
1014
1065
1015 startport controls the starting port number to use for this test. Each
1066 startport controls the starting port number to use for this test. Each
1016 test will reserve 3 port numbers for execution. It is the caller's
1067 test will reserve 3 port numbers for execution. It is the caller's
1017 responsibility to allocate a non-overlapping port range to Test
1068 responsibility to allocate a non-overlapping port range to Test
1018 instances.
1069 instances.
1019
1070
1020 extraconfigopts is an iterable of extra hgrc config options. Values
1071 extraconfigopts is an iterable of extra hgrc config options. Values
1021 must have the form "key=value" (something understood by hgrc). Values
1072 must have the form "key=value" (something understood by hgrc). Values
1022 of the form "foo.key=value" will result in "[foo] key=value".
1073 of the form "foo.key=value" will result in "[foo] key=value".
1023
1074
1024 shell is the shell to execute tests in.
1075 shell is the shell to execute tests in.
1025 """
1076 """
1026 if timeout is None:
1077 if timeout is None:
1027 timeout = defaults['timeout']
1078 timeout = defaults['timeout']
1028 if startport is None:
1079 if startport is None:
1029 startport = defaults['port']
1080 startport = defaults['port']
1030 if slowtimeout is None:
1081 if slowtimeout is None:
1031 slowtimeout = defaults['slowtimeout']
1082 slowtimeout = defaults['slowtimeout']
1032 self.path = path
1083 self.path = path
1033 self.relpath = os.path.relpath(path)
1084 self.relpath = os.path.relpath(path)
1034 self.bname = os.path.basename(path)
1085 self.bname = os.path.basename(path)
1035 self.name = _bytes2sys(self.bname)
1086 self.name = _bytes2sys(self.bname)
1036 self._testdir = os.path.dirname(path)
1087 self._testdir = os.path.dirname(path)
1037 self._outputdir = outputdir
1088 self._outputdir = outputdir
1038 self._tmpname = os.path.basename(path)
1089 self._tmpname = os.path.basename(path)
1039 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1090 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1040
1091
1041 self._threadtmp = tmpdir
1092 self._threadtmp = tmpdir
1042 self._keeptmpdir = keeptmpdir
1093 self._keeptmpdir = keeptmpdir
1043 self._debug = debug
1094 self._debug = debug
1044 self._first = first
1095 self._first = first
1045 self._timeout = timeout
1096 self._timeout = timeout
1046 self._slowtimeout = slowtimeout
1097 self._slowtimeout = slowtimeout
1047 self._startport = startport
1098 self._startport = startport
1048 self._extraconfigopts = extraconfigopts or []
1099 self._extraconfigopts = extraconfigopts or []
1049 self._shell = _sys2bytes(shell)
1100 self._shell = _sys2bytes(shell)
1050 self._hgcommand = hgcommand or b'hg'
1101 self._hgcommand = hgcommand or b'hg'
1051 self._usechg = usechg
1102 self._usechg = usechg
1052 self._chgdebug = chgdebug
1103 self._chgdebug = chgdebug
1053 self._useipv6 = useipv6
1104 self._useipv6 = useipv6
1054
1105
1055 self._aborted = False
1106 self._aborted = False
1056 self._daemonpids = []
1107 self._daemonpids = []
1057 self._finished = None
1108 self._finished = None
1058 self._ret = None
1109 self._ret = None
1059 self._out = None
1110 self._out = None
1060 self._skipped = None
1111 self._skipped = None
1061 self._testtmp = None
1112 self._testtmp = None
1062 self._chgsockdir = None
1113 self._chgsockdir = None
1063
1114
1064 self._refout = self.readrefout()
1115 self._refout = self.readrefout()
1065
1116
1066 def readrefout(self):
1117 def readrefout(self):
1067 """read reference output"""
1118 """read reference output"""
1068 # If we're not in --debug mode and reference output file exists,
1119 # If we're not in --debug mode and reference output file exists,
1069 # check test output against it.
1120 # check test output against it.
1070 if self._debug:
1121 if self._debug:
1071 return None # to match "out is None"
1122 return None # to match "out is None"
1072 elif os.path.exists(self.refpath):
1123 elif os.path.exists(self.refpath):
1073 with open(self.refpath, 'rb') as f:
1124 with open(self.refpath, 'rb') as f:
1074 return f.read().splitlines(True)
1125 return f.read().splitlines(True)
1075 else:
1126 else:
1076 return []
1127 return []
1077
1128
1078 # needed to get base class __repr__ running
1129 # needed to get base class __repr__ running
1079 @property
1130 @property
1080 def _testMethodName(self):
1131 def _testMethodName(self):
1081 return self.name
1132 return self.name
1082
1133
1083 def __str__(self):
1134 def __str__(self):
1084 return self.name
1135 return self.name
1085
1136
1086 def shortDescription(self):
1137 def shortDescription(self):
1087 return self.name
1138 return self.name
1088
1139
1089 def setUp(self):
1140 def setUp(self):
1090 """Tasks to perform before run()."""
1141 """Tasks to perform before run()."""
1091 self._finished = False
1142 self._finished = False
1092 self._ret = None
1143 self._ret = None
1093 self._out = None
1144 self._out = None
1094 self._skipped = None
1145 self._skipped = None
1095
1146
1096 try:
1147 try:
1097 os.mkdir(self._threadtmp)
1148 os.mkdir(self._threadtmp)
1098 except FileExistsError:
1149 except FileExistsError:
1099 pass
1150 pass
1100
1151
1101 name = self._tmpname
1152 name = self._tmpname
1102 self._testtmp = os.path.join(self._threadtmp, name)
1153 self._testtmp = os.path.join(self._threadtmp, name)
1103 os.mkdir(self._testtmp)
1154 os.mkdir(self._testtmp)
1104
1155
1105 # Remove any previous output files.
1156 # Remove any previous output files.
1106 if os.path.exists(self.errpath):
1157 if os.path.exists(self.errpath):
1107 try:
1158 try:
1108 os.remove(self.errpath)
1159 os.remove(self.errpath)
1109 except FileNotFoundError:
1160 except FileNotFoundError:
1110 # We might have raced another test to clean up a .err file,
1161 # We might have raced another test to clean up a .err file,
1111 # so ignore FileNotFoundError when removing a previous .err
1162 # so ignore FileNotFoundError when removing a previous .err
1112 # file.
1163 # file.
1113 pass
1164 pass
1114
1165
1115 if self._usechg:
1166 if self._usechg:
1116 self._chgsockdir = os.path.join(
1167 self._chgsockdir = os.path.join(
1117 self._threadtmp, b'%s.chgsock' % name
1168 self._threadtmp, b'%s.chgsock' % name
1118 )
1169 )
1119 os.mkdir(self._chgsockdir)
1170 os.mkdir(self._chgsockdir)
1120
1171
1121 def run(self, result):
1172 def run(self, result):
1122 """Run this test and report results against a TestResult instance."""
1173 """Run this test and report results against a TestResult instance."""
1123 # This function is extremely similar to unittest.TestCase.run(). Once
1174 # This function is extremely similar to unittest.TestCase.run(). Once
1124 # we require Python 2.7 (or at least its version of unittest), this
1175 # we require Python 2.7 (or at least its version of unittest), this
1125 # function can largely go away.
1176 # function can largely go away.
1126 self._result = result
1177 self._result = result
1127 result.startTest(self)
1178 result.startTest(self)
1128 try:
1179 try:
1129 try:
1180 try:
1130 self.setUp()
1181 self.setUp()
1131 except (KeyboardInterrupt, SystemExit):
1182 except (KeyboardInterrupt, SystemExit):
1132 self._aborted = True
1183 self._aborted = True
1133 raise
1184 raise
1134 except Exception:
1185 except Exception:
1135 result.addError(self, sys.exc_info())
1186 result.addError(self, sys.exc_info())
1136 return
1187 return
1137
1188
1138 success = False
1189 success = False
1139 try:
1190 try:
1140 self.runTest()
1191 self.runTest()
1141 except KeyboardInterrupt:
1192 except KeyboardInterrupt:
1142 self._aborted = True
1193 self._aborted = True
1143 raise
1194 raise
1144 except unittest.SkipTest as e:
1195 except unittest.SkipTest as e:
1145 result.addSkip(self, str(e))
1196 result.addSkip(self, str(e))
1146 # The base class will have already counted this as a
1197 # The base class will have already counted this as a
1147 # test we "ran", but we want to exclude skipped tests
1198 # test we "ran", but we want to exclude skipped tests
1148 # from those we count towards those run.
1199 # from those we count towards those run.
1149 result.testsRun -= 1
1200 result.testsRun -= 1
1150 except self.failureException as e:
1201 except self.failureException as e:
1151 # This differs from unittest in that we don't capture
1202 # This differs from unittest in that we don't capture
1152 # the stack trace. This is for historical reasons and
1203 # the stack trace. This is for historical reasons and
1153 # this decision could be revisited in the future,
1204 # this decision could be revisited in the future,
1154 # especially for PythonTest instances.
1205 # especially for PythonTest instances.
1155 if result.addFailure(self, str(e)):
1206 if result.addFailure(self, str(e)):
1156 success = True
1207 success = True
1157 except Exception:
1208 except Exception:
1158 result.addError(self, sys.exc_info())
1209 result.addError(self, sys.exc_info())
1159 else:
1210 else:
1160 success = True
1211 success = True
1161
1212
1162 try:
1213 try:
1163 self.tearDown()
1214 self.tearDown()
1164 except (KeyboardInterrupt, SystemExit):
1215 except (KeyboardInterrupt, SystemExit):
1165 self._aborted = True
1216 self._aborted = True
1166 raise
1217 raise
1167 except Exception:
1218 except Exception:
1168 result.addError(self, sys.exc_info())
1219 result.addError(self, sys.exc_info())
1169 success = False
1220 success = False
1170
1221
1171 if success:
1222 if success:
1172 result.addSuccess(self)
1223 result.addSuccess(self)
1173 finally:
1224 finally:
1174 result.stopTest(self, interrupted=self._aborted)
1225 result.stopTest(self, interrupted=self._aborted)
1175
1226
1176 def runTest(self):
1227 def runTest(self):
1177 """Run this test instance.
1228 """Run this test instance.
1178
1229
1179 This will return a tuple describing the result of the test.
1230 This will return a tuple describing the result of the test.
1180 """
1231 """
1181 env = self._getenv()
1232 env = self._getenv()
1182 self._genrestoreenv(env)
1233 self._genrestoreenv(env)
1183 self._daemonpids.append(env['DAEMON_PIDS'])
1234 self._daemonpids.append(env['DAEMON_PIDS'])
1184 self._createhgrc(env['HGRCPATH'])
1235 self._createhgrc(env['HGRCPATH'])
1185
1236
1186 vlog('# Test', self.name)
1237 vlog('# Test', self.name)
1187
1238
1188 ret, out = self._run(env)
1239 ret, out = self._run(env)
1189 self._finished = True
1240 self._finished = True
1190 self._ret = ret
1241 self._ret = ret
1191 self._out = out
1242 self._out = out
1192
1243
1193 def describe(ret):
1244 def describe(ret):
1194 if ret < 0:
1245 if ret < 0:
1195 return 'killed by signal: %d' % -ret
1246 return 'killed by signal: %d' % -ret
1196 return 'returned error code %d' % ret
1247 return 'returned error code %d' % ret
1197
1248
1198 self._skipped = False
1249 self._skipped = False
1199
1250
1200 if ret == self.SKIPPED_STATUS:
1251 if ret == self.SKIPPED_STATUS:
1201 if out is None: # Debug mode, nothing to parse.
1252 if out is None: # Debug mode, nothing to parse.
1202 missing = ['unknown']
1253 missing = ['unknown']
1203 failed = None
1254 failed = None
1204 else:
1255 else:
1205 missing, failed = TTest.parsehghaveoutput(out)
1256 missing, failed = TTest.parsehghaveoutput(out)
1206
1257
1207 if not missing:
1258 if not missing:
1208 missing = ['skipped']
1259 missing = ['skipped']
1209
1260
1210 if failed:
1261 if failed:
1211 self.fail('hg have failed checking for %s' % failed[-1])
1262 self.fail('hg have failed checking for %s' % failed[-1])
1212 else:
1263 else:
1213 self._skipped = True
1264 self._skipped = True
1214 raise unittest.SkipTest(missing[-1])
1265 raise unittest.SkipTest(missing[-1])
1215 elif ret == 'timeout':
1266 elif ret == 'timeout':
1216 self.fail('timed out')
1267 self.fail('timed out')
1217 elif ret is False:
1268 elif ret is False:
1218 self.fail('no result code from test')
1269 self.fail('no result code from test')
1219 elif out != self._refout:
1270 elif out != self._refout:
1220 # Diff generation may rely on written .err file.
1271 # Diff generation may rely on written .err file.
1221 if (
1272 if (
1222 (ret != 0 or out != self._refout)
1273 (ret != 0 or out != self._refout)
1223 and not self._skipped
1274 and not self._skipped
1224 and not self._debug
1275 and not self._debug
1225 ):
1276 ):
1226 with open(self.errpath, 'wb') as f:
1277 with open(self.errpath, 'wb') as f:
1227 for line in out:
1278 for line in out:
1228 f.write(line)
1279 f.write(line)
1229
1280
1230 # The result object handles diff calculation for us.
1281 # The result object handles diff calculation for us.
1231 with firstlock:
1282 with firstlock:
1232 if self._result.addOutputMismatch(self, ret, out, self._refout):
1283 if self._result.addOutputMismatch(self, ret, out, self._refout):
1233 # change was accepted, skip failing
1284 # change was accepted, skip failing
1234 return
1285 return
1235 if self._first:
1286 if self._first:
1236 global firsterror
1287 global firsterror
1237 firsterror = True
1288 firsterror = True
1238
1289
1239 if ret:
1290 if ret:
1240 msg = 'output changed and ' + describe(ret)
1291 msg = 'output changed and ' + describe(ret)
1241 else:
1292 else:
1242 msg = 'output changed'
1293 msg = 'output changed'
1243
1294
1244 self.fail(msg)
1295 self.fail(msg)
1245 elif ret:
1296 elif ret:
1246 self.fail(describe(ret))
1297 self.fail(describe(ret))
1247
1298
1248 def tearDown(self):
1299 def tearDown(self):
1249 """Tasks to perform after run()."""
1300 """Tasks to perform after run()."""
1250 for entry in self._daemonpids:
1301 for entry in self._daemonpids:
1251 killdaemons(entry)
1302 killdaemons(entry)
1252 self._daemonpids = []
1303 self._daemonpids = []
1253
1304
1254 if self._keeptmpdir:
1305 if self._keeptmpdir:
1255 log(
1306 log(
1256 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1307 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1257 % (
1308 % (
1258 _bytes2sys(self._testtmp),
1309 _bytes2sys(self._testtmp),
1259 _bytes2sys(self._threadtmp),
1310 _bytes2sys(self._threadtmp),
1260 )
1311 )
1261 )
1312 )
1262 else:
1313 else:
1263 try:
1314 try:
1264 shutil.rmtree(self._testtmp)
1315 shutil.rmtree(self._testtmp)
1265 except OSError:
1316 except OSError:
1266 # unreadable directory may be left in $TESTTMP; fix permission
1317 # unreadable directory may be left in $TESTTMP; fix permission
1267 # and try again
1318 # and try again
1268 makecleanable(self._testtmp)
1319 makecleanable(self._testtmp)
1269 shutil.rmtree(self._testtmp, True)
1320 shutil.rmtree(self._testtmp, True)
1270 shutil.rmtree(self._threadtmp, True)
1321 shutil.rmtree(self._threadtmp, True)
1271
1322
1272 if self._usechg:
1323 if self._usechg:
1273 # chgservers will stop automatically after they find the socket
1324 # chgservers will stop automatically after they find the socket
1274 # files are deleted
1325 # files are deleted
1275 shutil.rmtree(self._chgsockdir, True)
1326 shutil.rmtree(self._chgsockdir, True)
1276
1327
1277 if (
1328 if (
1278 (self._ret != 0 or self._out != self._refout)
1329 (self._ret != 0 or self._out != self._refout)
1279 and not self._skipped
1330 and not self._skipped
1280 and not self._debug
1331 and not self._debug
1281 and self._out
1332 and self._out
1282 ):
1333 ):
1283 with open(self.errpath, 'wb') as f:
1334 with open(self.errpath, 'wb') as f:
1284 for line in self._out:
1335 for line in self._out:
1285 f.write(line)
1336 f.write(line)
1286
1337
1287 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1338 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1288
1339
1289 def _run(self, env):
1340 def _run(self, env):
1290 # This should be implemented in child classes to run tests.
1341 # This should be implemented in child classes to run tests.
1291 raise unittest.SkipTest('unknown test type')
1342 raise unittest.SkipTest('unknown test type')
1292
1343
1293 def abort(self):
1344 def abort(self):
1294 """Terminate execution of this test."""
1345 """Terminate execution of this test."""
1295 self._aborted = True
1346 self._aborted = True
1296
1347
1297 def _portmap(self, i):
1348 def _portmap(self, i):
1298 offset = b'' if i == 0 else b'%d' % i
1349 offset = b'' if i == 0 else b'%d' % i
1299 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1350 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1300
1351
1301 def _getreplacements(self):
1352 def _getreplacements(self):
1302 """Obtain a mapping of text replacements to apply to test output.
1353 """Obtain a mapping of text replacements to apply to test output.
1303
1354
1304 Test output needs to be normalized so it can be compared to expected
1355 Test output needs to be normalized so it can be compared to expected
1305 output. This function defines how some of that normalization will
1356 output. This function defines how some of that normalization will
1306 occur.
1357 occur.
1307 """
1358 """
1308 r = [
1359 r = [
1309 # This list should be parallel to defineport in _getenv
1360 # This list should be parallel to defineport in _getenv
1310 self._portmap(0),
1361 self._portmap(0),
1311 self._portmap(1),
1362 self._portmap(1),
1312 self._portmap(2),
1363 self._portmap(2),
1313 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1364 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1314 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1365 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1315 ]
1366 ]
1316 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1367 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1317 if WINDOWS:
1368 if WINDOWS:
1318 # JSON output escapes backslashes in Windows paths, so also catch a
1369 # JSON output escapes backslashes in Windows paths, so also catch a
1319 # double-escape.
1370 # double-escape.
1320 replaced = self._testtmp.replace(b'\\', br'\\')
1371 replaced = self._testtmp.replace(b'\\', br'\\')
1321 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1372 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1322
1373
1323 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1374 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1324
1375
1325 if os.path.exists(replacementfile):
1376 if os.path.exists(replacementfile):
1326 data = {}
1377 data = {}
1327 with open(replacementfile, mode='rb') as source:
1378 with open(replacementfile, mode='rb') as source:
1328 # the intermediate 'compile' step help with debugging
1379 # the intermediate 'compile' step help with debugging
1329 code = compile(source.read(), replacementfile, 'exec')
1380 code = compile(source.read(), replacementfile, 'exec')
1330 exec(code, data)
1381 exec(code, data)
1331 for value in data.get('substitutions', ()):
1382 for value in data.get('substitutions', ()):
1332 if len(value) != 2:
1383 if len(value) != 2:
1333 msg = 'malformatted substitution in %s: %r'
1384 msg = 'malformatted substitution in %s: %r'
1334 msg %= (replacementfile, value)
1385 msg %= (replacementfile, value)
1335 raise ValueError(msg)
1386 raise ValueError(msg)
1336 r.append(value)
1387 r.append(value)
1337 return r
1388 return r
1338
1389
1339 def _escapepath(self, p):
1390 def _escapepath(self, p):
1340 if WINDOWS:
1391 if WINDOWS:
1341 return b''.join(
1392 return b''.join(
1342 c.isalpha()
1393 c.isalpha()
1343 and b'[%s%s]' % (c.lower(), c.upper())
1394 and b'[%s%s]' % (c.lower(), c.upper())
1344 or c in b'/\\'
1395 or c in b'/\\'
1345 and br'[/\\]'
1396 and br'[/\\]'
1346 or c.isdigit()
1397 or c.isdigit()
1347 and c
1398 and c
1348 or b'\\' + c
1399 or b'\\' + c
1349 for c in [p[i : i + 1] for i in range(len(p))]
1400 for c in [p[i : i + 1] for i in range(len(p))]
1350 )
1401 )
1351 else:
1402 else:
1352 return re.escape(p)
1403 return re.escape(p)
1353
1404
1354 def _localip(self):
1405 def _localip(self):
1355 if self._useipv6:
1406 if self._useipv6:
1356 return b'::1'
1407 return b'::1'
1357 else:
1408 else:
1358 return b'127.0.0.1'
1409 return b'127.0.0.1'
1359
1410
1360 def _genrestoreenv(self, testenv):
1411 def _genrestoreenv(self, testenv):
1361 """Generate a script that can be used by tests to restore the original
1412 """Generate a script that can be used by tests to restore the original
1362 environment."""
1413 environment."""
1363 # Put the restoreenv script inside self._threadtmp
1414 # Put the restoreenv script inside self._threadtmp
1364 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1415 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1365 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1416 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1366
1417
1367 # Only restore environment variable names that the shell allows
1418 # Only restore environment variable names that the shell allows
1368 # us to export.
1419 # us to export.
1369 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1420 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1370
1421
1371 # Do not restore these variables; otherwise tests would fail.
1422 # Do not restore these variables; otherwise tests would fail.
1372 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1423 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1373
1424
1374 with open(scriptpath, 'w') as envf:
1425 with open(scriptpath, 'w') as envf:
1375 for name, value in origenviron.items():
1426 for name, value in origenviron.items():
1376 if not name_regex.match(name):
1427 if not name_regex.match(name):
1377 # Skip environment variables with unusual names not
1428 # Skip environment variables with unusual names not
1378 # allowed by most shells.
1429 # allowed by most shells.
1379 continue
1430 continue
1380 if name in reqnames:
1431 if name in reqnames:
1381 continue
1432 continue
1382 envf.write('%s=%s\n' % (name, shellquote(value)))
1433 envf.write('%s=%s\n' % (name, shellquote(value)))
1383
1434
1384 for name in testenv:
1435 for name in testenv:
1385 if name in origenviron or name in reqnames:
1436 if name in origenviron or name in reqnames:
1386 continue
1437 continue
1387 envf.write('unset %s\n' % (name,))
1438 envf.write('unset %s\n' % (name,))
1388
1439
1389 def _getenv(self):
1440 def _getenv(self):
1390 """Obtain environment variables to use during test execution."""
1441 """Obtain environment variables to use during test execution."""
1391
1442
1392 def defineport(i):
1443 def defineport(i):
1393 offset = '' if i == 0 else '%s' % i
1444 offset = '' if i == 0 else '%s' % i
1394 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1445 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1395
1446
1396 env = os.environ.copy()
1447 env = os.environ.copy()
1397 with sysconfiglock:
1448 with sysconfiglock:
1398 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1449 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1399 env['HGEMITWARNINGS'] = '1'
1450 env['HGEMITWARNINGS'] = '1'
1400 env['TESTTMP'] = _bytes2sys(self._testtmp)
1451 env['TESTTMP'] = _bytes2sys(self._testtmp)
1401 # the FORWARD_SLASH version is useful when running `sh` on non unix
1452 # the FORWARD_SLASH version is useful when running `sh` on non unix
1402 # system (e.g. Windows)
1453 # system (e.g. Windows)
1403 env['TESTTMP_FORWARD_SLASH'] = env['TESTTMP'].replace(os.sep, '/')
1454 env['TESTTMP_FORWARD_SLASH'] = env['TESTTMP'].replace(os.sep, '/')
1404 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1455 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1405 env['HGTEST_UUIDFILE'] = uid_file
1456 env['HGTEST_UUIDFILE'] = uid_file
1406 env['TESTNAME'] = self.name
1457 env['TESTNAME'] = self.name
1407 env['HOME'] = _bytes2sys(self._testtmp)
1458 env['HOME'] = _bytes2sys(self._testtmp)
1408 if WINDOWS:
1459 if WINDOWS:
1409 env['REALUSERPROFILE'] = env['USERPROFILE']
1460 env['REALUSERPROFILE'] = env['USERPROFILE']
1410 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1461 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1411 env['USERPROFILE'] = env['HOME']
1462 env['USERPROFILE'] = env['HOME']
1412 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1463 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1413 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1464 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1414 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1465 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1415 # This number should match portneeded in _getport
1466 # This number should match portneeded in _getport
1416 for port in range(3):
1467 for port in range(3):
1417 # This list should be parallel to _portmap in _getreplacements
1468 # This list should be parallel to _portmap in _getreplacements
1418 defineport(port)
1469 defineport(port)
1419 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1470 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1420 env["DAEMON_PIDS"] = _bytes2sys(
1471 env["DAEMON_PIDS"] = _bytes2sys(
1421 os.path.join(self._threadtmp, b'daemon.pids')
1472 os.path.join(self._threadtmp, b'daemon.pids')
1422 )
1473 )
1423 env["HGEDITOR"] = (
1474 env["HGEDITOR"] = (
1424 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1475 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1425 )
1476 )
1426 env["HGUSER"] = "test"
1477 env["HGUSER"] = "test"
1427 env["HGENCODING"] = "ascii"
1478 env["HGENCODING"] = "ascii"
1428 env["HGENCODINGMODE"] = "strict"
1479 env["HGENCODINGMODE"] = "strict"
1429 env["HGHOSTNAME"] = "test-hostname"
1480 env["HGHOSTNAME"] = "test-hostname"
1430 env['HGIPV6'] = str(int(self._useipv6))
1481 env['HGIPV6'] = str(int(self._useipv6))
1431 # See contrib/catapipe.py for how to use this functionality.
1482 # See contrib/catapipe.py for how to use this functionality.
1432 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1483 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1433 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1484 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1434 # non-test one in as a default, otherwise set to devnull
1485 # non-test one in as a default, otherwise set to devnull
1435 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1486 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1436 'HGCATAPULTSERVERPIPE', os.devnull
1487 'HGCATAPULTSERVERPIPE', os.devnull
1437 )
1488 )
1438
1489
1439 extraextensions = []
1490 extraextensions = []
1440 for opt in self._extraconfigopts:
1491 for opt in self._extraconfigopts:
1441 section, key = opt.split('.', 1)
1492 section, key = opt.split('.', 1)
1442 if section != 'extensions':
1493 if section != 'extensions':
1443 continue
1494 continue
1444 name = key.split('=', 1)[0]
1495 name = key.split('=', 1)[0]
1445 extraextensions.append(name)
1496 extraextensions.append(name)
1446
1497
1447 if extraextensions:
1498 if extraextensions:
1448 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1499 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1449
1500
1450 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1501 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1451 # IP addresses.
1502 # IP addresses.
1452 env['LOCALIP'] = _bytes2sys(self._localip())
1503 env['LOCALIP'] = _bytes2sys(self._localip())
1453
1504
1454 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1505 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1455 # but this is needed for testing python instances like dummyssh,
1506 # but this is needed for testing python instances like dummyssh,
1456 # dummysmtpd.py, and dumbhttp.py.
1507 # dummysmtpd.py, and dumbhttp.py.
1457 if WINDOWS:
1508 if WINDOWS:
1458 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1509 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1459
1510
1460 # Modified HOME in test environment can confuse Rust tools. So set
1511 # Modified HOME in test environment can confuse Rust tools. So set
1461 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1512 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1462 # present and these variables aren't already defined.
1513 # present and these variables aren't already defined.
1463 cargo_home_path = os.path.expanduser('~/.cargo')
1514 cargo_home_path = os.path.expanduser('~/.cargo')
1464 rustup_home_path = os.path.expanduser('~/.rustup')
1515 rustup_home_path = os.path.expanduser('~/.rustup')
1465
1516
1466 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1517 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1467 env['CARGO_HOME'] = cargo_home_path
1518 env['CARGO_HOME'] = cargo_home_path
1468 if (
1519 if (
1469 os.path.exists(rustup_home_path)
1520 os.path.exists(rustup_home_path)
1470 and b'RUSTUP_HOME' not in osenvironb
1521 and b'RUSTUP_HOME' not in osenvironb
1471 ):
1522 ):
1472 env['RUSTUP_HOME'] = rustup_home_path
1523 env['RUSTUP_HOME'] = rustup_home_path
1473
1524
1474 # Reset some environment variables to well-known values so that
1525 # Reset some environment variables to well-known values so that
1475 # the tests produce repeatable output.
1526 # the tests produce repeatable output.
1476 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1527 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1477 env['TZ'] = 'GMT'
1528 env['TZ'] = 'GMT'
1478 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1529 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1479 env['COLUMNS'] = '80'
1530 env['COLUMNS'] = '80'
1480 env['TERM'] = 'xterm'
1531 env['TERM'] = 'xterm'
1481
1532
1482 dropped = [
1533 dropped = [
1483 'CDPATH',
1534 'CDPATH',
1484 'CHGDEBUG',
1535 'CHGDEBUG',
1485 'EDITOR',
1536 'EDITOR',
1486 'GREP_OPTIONS',
1537 'GREP_OPTIONS',
1487 'HG',
1538 'HG',
1488 'HGMERGE',
1539 'HGMERGE',
1489 'HGPLAIN',
1540 'HGPLAIN',
1490 'HGPLAINEXCEPT',
1541 'HGPLAINEXCEPT',
1491 'HGPROF',
1542 'HGPROF',
1492 'http_proxy',
1543 'http_proxy',
1493 'no_proxy',
1544 'no_proxy',
1494 'NO_PROXY',
1545 'NO_PROXY',
1495 'PAGER',
1546 'PAGER',
1496 'VISUAL',
1547 'VISUAL',
1497 ]
1548 ]
1498
1549
1499 for k in dropped:
1550 for k in dropped:
1500 if k in env:
1551 if k in env:
1501 del env[k]
1552 del env[k]
1502
1553
1503 # unset env related to hooks
1554 # unset env related to hooks
1504 for k in list(env):
1555 for k in list(env):
1505 if k.startswith('HG_'):
1556 if k.startswith('HG_'):
1506 del env[k]
1557 del env[k]
1507
1558
1508 if self._usechg:
1559 if self._usechg:
1509 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1560 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1510 if self._chgdebug:
1561 if self._chgdebug:
1511 env['CHGDEBUG'] = 'true'
1562 env['CHGDEBUG'] = 'true'
1512
1563
1513 return env
1564 return env
1514
1565
1515 def _createhgrc(self, path):
1566 def _createhgrc(self, path):
1516 """Create an hgrc file for this test."""
1567 """Create an hgrc file for this test."""
1517 with open(path, 'wb') as hgrc:
1568 with open(path, 'wb') as hgrc:
1518 hgrc.write(b'[ui]\n')
1569 hgrc.write(b'[ui]\n')
1519 hgrc.write(b'slash = True\n')
1570 hgrc.write(b'slash = True\n')
1520 hgrc.write(b'interactive = False\n')
1571 hgrc.write(b'interactive = False\n')
1521 hgrc.write(b'detailed-exit-code = True\n')
1572 hgrc.write(b'detailed-exit-code = True\n')
1522 hgrc.write(b'merge = internal:merge\n')
1573 hgrc.write(b'merge = internal:merge\n')
1523 hgrc.write(b'mergemarkers = detailed\n')
1574 hgrc.write(b'mergemarkers = detailed\n')
1524 hgrc.write(b'promptecho = True\n')
1575 hgrc.write(b'promptecho = True\n')
1525 dummyssh = os.path.join(self._testdir, b'dummyssh')
1576 dummyssh = os.path.join(self._testdir, b'dummyssh')
1526 hgrc.write(b'ssh = "%s" "%s"\n' % (PYTHON, dummyssh))
1577 hgrc.write(b'ssh = "%s" "%s"\n' % (PYTHON, dummyssh))
1527 hgrc.write(b'timeout.warn=15\n')
1578 hgrc.write(b'timeout.warn=15\n')
1528 hgrc.write(b'[chgserver]\n')
1579 hgrc.write(b'[chgserver]\n')
1529 hgrc.write(b'idletimeout=60\n')
1580 hgrc.write(b'idletimeout=60\n')
1530 hgrc.write(b'[defaults]\n')
1581 hgrc.write(b'[defaults]\n')
1531 hgrc.write(b'[devel]\n')
1582 hgrc.write(b'[devel]\n')
1532 hgrc.write(b'all-warnings = true\n')
1583 hgrc.write(b'all-warnings = true\n')
1533 hgrc.write(b'default-date = 0 0\n')
1584 hgrc.write(b'default-date = 0 0\n')
1534 hgrc.write(b'[largefiles]\n')
1585 hgrc.write(b'[largefiles]\n')
1535 hgrc.write(
1586 hgrc.write(
1536 b'usercache = %s\n'
1587 b'usercache = %s\n'
1537 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1588 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1538 )
1589 )
1539 hgrc.write(b'[lfs]\n')
1590 hgrc.write(b'[lfs]\n')
1540 hgrc.write(
1591 hgrc.write(
1541 b'usercache = %s\n'
1592 b'usercache = %s\n'
1542 % (os.path.join(self._testtmp, b'.cache/lfs'))
1593 % (os.path.join(self._testtmp, b'.cache/lfs'))
1543 )
1594 )
1544 hgrc.write(b'[web]\n')
1595 hgrc.write(b'[web]\n')
1545 hgrc.write(b'address = localhost\n')
1596 hgrc.write(b'address = localhost\n')
1546 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1597 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1547 hgrc.write(b'server-header = testing stub value\n')
1598 hgrc.write(b'server-header = testing stub value\n')
1548
1599
1549 for opt in self._extraconfigopts:
1600 for opt in self._extraconfigopts:
1550 section, key = _sys2bytes(opt).split(b'.', 1)
1601 section, key = _sys2bytes(opt).split(b'.', 1)
1551 assert b'=' in key, (
1602 assert b'=' in key, (
1552 'extra config opt %s must ' 'have an = for assignment' % opt
1603 'extra config opt %s must ' 'have an = for assignment' % opt
1553 )
1604 )
1554 hgrc.write(b'[%s]\n%s\n' % (section, key))
1605 hgrc.write(b'[%s]\n%s\n' % (section, key))
1555
1606
1556 def fail(self, msg):
1607 def fail(self, msg):
1557 # unittest differentiates between errored and failed.
1608 # unittest differentiates between errored and failed.
1558 # Failed is denoted by AssertionError (by default at least).
1609 # Failed is denoted by AssertionError (by default at least).
1559 raise AssertionError(msg)
1610 raise AssertionError(msg)
1560
1611
1561 def _runcommand(self, cmd, env, normalizenewlines=False):
1612 def _runcommand(self, cmd, env, normalizenewlines=False):
1562 """Run command in a sub-process, capturing the output (stdout and
1613 """Run command in a sub-process, capturing the output (stdout and
1563 stderr).
1614 stderr).
1564
1615
1565 Return a tuple (exitcode, output). output is None in debug mode.
1616 Return a tuple (exitcode, output). output is None in debug mode.
1566 """
1617 """
1567 if self._debug:
1618 if self._debug:
1568 proc = subprocess.Popen(
1619 proc = subprocess.Popen(
1569 _bytes2sys(cmd),
1620 _bytes2sys(cmd),
1570 shell=True,
1621 shell=True,
1571 close_fds=closefds,
1622 close_fds=closefds,
1572 cwd=_bytes2sys(self._testtmp),
1623 cwd=_bytes2sys(self._testtmp),
1573 env=env,
1624 env=env,
1574 )
1625 )
1575 ret = proc.wait()
1626 ret = proc.wait()
1576 return (ret, None)
1627 return (ret, None)
1577
1628
1578 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1629 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1579
1630
1580 def cleanup():
1631 def cleanup():
1581 terminate(proc)
1632 terminate(proc)
1582 ret = proc.wait()
1633 ret = proc.wait()
1583 if ret == 0:
1634 if ret == 0:
1584 ret = signal.SIGTERM << 8
1635 ret = signal.SIGTERM << 8
1585 killdaemons(env['DAEMON_PIDS'])
1636 killdaemons(env['DAEMON_PIDS'])
1586 return ret
1637 return ret
1587
1638
1588 proc.tochild.close()
1639 proc.tochild.close()
1589
1640
1590 try:
1641 try:
1591 output = proc.fromchild.read()
1642 output = proc.fromchild.read()
1592 except KeyboardInterrupt:
1643 except KeyboardInterrupt:
1593 vlog('# Handling keyboard interrupt')
1644 vlog('# Handling keyboard interrupt')
1594 cleanup()
1645 cleanup()
1595 raise
1646 raise
1596
1647
1597 ret = proc.wait()
1648 ret = proc.wait()
1598 if wifexited(ret):
1649 if wifexited(ret):
1599 ret = os.WEXITSTATUS(ret)
1650 ret = os.WEXITSTATUS(ret)
1600
1651
1601 if proc.timeout:
1652 if proc.timeout:
1602 ret = 'timeout'
1653 ret = 'timeout'
1603
1654
1604 if ret:
1655 if ret:
1605 killdaemons(env['DAEMON_PIDS'])
1656 killdaemons(env['DAEMON_PIDS'])
1606
1657
1607 for s, r in self._getreplacements():
1658 for s, r in self._getreplacements():
1608 output = re.sub(s, r, output)
1659 output = re.sub(s, r, output)
1609
1660
1610 if normalizenewlines:
1661 if normalizenewlines:
1611 output = output.replace(b'\r\n', b'\n')
1662 output = output.replace(b'\r\n', b'\n')
1612
1663
1613 return ret, output.splitlines(True)
1664 return ret, output.splitlines(True)
1614
1665
1615
1666
1616 class PythonTest(Test):
1667 class PythonTest(Test):
1617 """A Python-based test."""
1668 """A Python-based test."""
1618
1669
1619 @property
1670 @property
1620 def refpath(self):
1671 def refpath(self):
1621 return os.path.join(self._testdir, b'%s.out' % self.bname)
1672 return os.path.join(self._testdir, b'%s.out' % self.bname)
1622
1673
1623 def _run(self, env):
1674 def _run(self, env):
1624 # Quote the python(3) executable for Windows
1675 # Quote the python(3) executable for Windows
1625 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1676 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1626 vlog("# Running", cmd.decode("utf-8"))
1677 vlog("# Running", cmd.decode("utf-8"))
1627 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1678 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1628 if self._aborted:
1679 if self._aborted:
1629 raise KeyboardInterrupt()
1680 raise KeyboardInterrupt()
1630
1681
1631 return result
1682 return result
1632
1683
1633
1684
1634 # Some glob patterns apply only in some circumstances, so the script
1685 # Some glob patterns apply only in some circumstances, so the script
1635 # might want to remove (glob) annotations that otherwise should be
1686 # might want to remove (glob) annotations that otherwise should be
1636 # retained.
1687 # retained.
1637 checkcodeglobpats = [
1688 checkcodeglobpats = [
1638 # On Windows it looks like \ doesn't require a (glob), but we know
1689 # On Windows it looks like \ doesn't require a (glob), but we know
1639 # better.
1690 # better.
1640 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1691 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1641 re.compile(br'^moving \S+/.*[^)]$'),
1692 re.compile(br'^moving \S+/.*[^)]$'),
1642 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1693 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1643 # Not all platforms have 127.0.0.1 as loopback (though most do),
1694 # Not all platforms have 127.0.0.1 as loopback (though most do),
1644 # so we always glob that too.
1695 # so we always glob that too.
1645 re.compile(br'.*\$LOCALIP.*$'),
1696 re.compile(br'.*\$LOCALIP.*$'),
1646 ]
1697 ]
1647
1698
1648 bchr = lambda x: bytes([x])
1699 bchr = lambda x: bytes([x])
1649
1700
1650 WARN_UNDEFINED = 1
1701 WARN_UNDEFINED = 1
1651 WARN_YES = 2
1702 WARN_YES = 2
1652 WARN_NO = 3
1703 WARN_NO = 3
1653
1704
1654 MARK_OPTIONAL = b" (?)\n"
1705 MARK_OPTIONAL = b" (?)\n"
1655
1706
1656
1707
1657 def isoptional(line):
1708 def isoptional(line):
1658 return line.endswith(MARK_OPTIONAL)
1709 return line.endswith(MARK_OPTIONAL)
1659
1710
1660
1711
1661 class TTest(Test):
1712 class TTest(Test):
1662 """A "t test" is a test backed by a .t file."""
1713 """A "t test" is a test backed by a .t file."""
1663
1714
1664 SKIPPED_PREFIX = b'skipped: '
1715 SKIPPED_PREFIX = b'skipped: '
1665 FAILED_PREFIX = b'hghave check failed: '
1716 FAILED_PREFIX = b'hghave check failed: '
1666 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1717 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1667
1718
1668 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1719 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1669 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1720 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1670 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1721 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1671
1722
1672 def __init__(self, path, *args, **kwds):
1723 def __init__(self, path, *args, **kwds):
1673 # accept an extra "case" parameter
1724 # accept an extra "case" parameter
1674 case = kwds.pop('case', [])
1725 case = kwds.pop('case', [])
1675 self._case = case
1726 self._case = case
1676 self._allcases = {x for y in parsettestcases(path) for x in y}
1727 self._allcases = {x for y in parsettestcases(path) for x in y}
1677 super(TTest, self).__init__(path, *args, **kwds)
1728 super(TTest, self).__init__(path, *args, **kwds)
1678 if case:
1729 if case:
1679 casepath = b'#'.join(case)
1730 casepath = b'#'.join(case)
1680 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1731 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1681 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1732 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1682 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1733 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1683 self._have = {}
1734 self._have = {}
1684
1735
1685 @property
1736 @property
1686 def refpath(self):
1737 def refpath(self):
1687 return os.path.join(self._testdir, self.bname)
1738 return os.path.join(self._testdir, self.bname)
1688
1739
1689 def _run(self, env):
1740 def _run(self, env):
1690 with open(self.path, 'rb') as f:
1741 with open(self.path, 'rb') as f:
1691 lines = f.readlines()
1742 lines = f.readlines()
1692
1743
1693 # .t file is both reference output and the test input, keep reference
1744 # .t file is both reference output and the test input, keep reference
1694 # output updated with the the test input. This avoids some race
1745 # output updated with the the test input. This avoids some race
1695 # conditions where the reference output does not match the actual test.
1746 # conditions where the reference output does not match the actual test.
1696 if self._refout is not None:
1747 if self._refout is not None:
1697 self._refout = lines
1748 self._refout = lines
1698
1749
1699 salt, script, after, expected = self._parsetest(lines)
1750 salt, script, after, expected = self._parsetest(lines)
1700
1751
1701 # Write out the generated script.
1752 # Write out the generated script.
1702 fname = b'%s.sh' % self._testtmp
1753 fname = b'%s.sh' % self._testtmp
1703 with open(fname, 'wb') as f:
1754 with open(fname, 'wb') as f:
1704 for l in script:
1755 for l in script:
1705 f.write(l)
1756 f.write(l)
1706
1757
1707 cmd = b'%s "%s"' % (self._shell, fname)
1758 cmd = b'%s "%s"' % (self._shell, fname)
1708 vlog("# Running", cmd.decode("utf-8"))
1759 vlog("# Running", cmd.decode("utf-8"))
1709
1760
1710 exitcode, output = self._runcommand(cmd, env)
1761 exitcode, output = self._runcommand(cmd, env)
1711
1762
1712 if self._aborted:
1763 if self._aborted:
1713 raise KeyboardInterrupt()
1764 raise KeyboardInterrupt()
1714
1765
1715 # Do not merge output if skipped. Return hghave message instead.
1766 # Do not merge output if skipped. Return hghave message instead.
1716 # Similarly, with --debug, output is None.
1767 # Similarly, with --debug, output is None.
1717 if exitcode == self.SKIPPED_STATUS or output is None:
1768 if exitcode == self.SKIPPED_STATUS or output is None:
1718 return exitcode, output
1769 return exitcode, output
1719
1770
1720 return self._processoutput(exitcode, output, salt, after, expected)
1771 return self._processoutput(exitcode, output, salt, after, expected)
1721
1772
1722 def _hghave(self, reqs):
1773 def _hghave(self, reqs):
1723 allreqs = b' '.join(reqs)
1774 allreqs = b' '.join(reqs)
1724
1775
1725 self._detectslow(reqs)
1776 self._detectslow(reqs)
1726
1777
1727 if allreqs in self._have:
1778 if allreqs in self._have:
1728 return self._have.get(allreqs)
1779 return self._have.get(allreqs)
1729
1780
1730 # TODO do something smarter when all other uses of hghave are gone.
1781 # TODO do something smarter when all other uses of hghave are gone.
1731 runtestdir = osenvironb[b'RUNTESTDIR']
1782 runtestdir = osenvironb[b'RUNTESTDIR']
1732 tdir = runtestdir.replace(b'\\', b'/')
1783 tdir = runtestdir.replace(b'\\', b'/')
1733 proc = Popen4(
1784 proc = Popen4(
1734 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1785 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1735 self._testtmp,
1786 self._testtmp,
1736 0,
1787 0,
1737 self._getenv(),
1788 self._getenv(),
1738 )
1789 )
1739 stdout, stderr = proc.communicate()
1790 stdout, stderr = proc.communicate()
1740 ret = proc.wait()
1791 ret = proc.wait()
1741 if wifexited(ret):
1792 if wifexited(ret):
1742 ret = os.WEXITSTATUS(ret)
1793 ret = os.WEXITSTATUS(ret)
1743 if ret == 2:
1794 if ret == 2:
1744 print(stdout.decode('utf-8'))
1795 print(stdout.decode('utf-8'))
1745 sys.exit(1)
1796 sys.exit(1)
1746
1797
1747 if ret != 0:
1798 if ret != 0:
1748 self._have[allreqs] = (False, stdout)
1799 self._have[allreqs] = (False, stdout)
1749 return False, stdout
1800 return False, stdout
1750
1801
1751 self._have[allreqs] = (True, None)
1802 self._have[allreqs] = (True, None)
1752 return True, None
1803 return True, None
1753
1804
1754 def _detectslow(self, reqs):
1805 def _detectslow(self, reqs):
1755 """update the timeout of slow test when appropriate"""
1806 """update the timeout of slow test when appropriate"""
1756 if b'slow' in reqs:
1807 if b'slow' in reqs:
1757 self._timeout = self._slowtimeout
1808 self._timeout = self._slowtimeout
1758
1809
1759 def _iftest(self, args):
1810 def _iftest(self, args):
1760 # implements "#if"
1811 # implements "#if"
1761 reqs = []
1812 reqs = []
1762 for arg in args:
1813 for arg in args:
1763 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1814 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1764 if arg[3:] in self._case:
1815 if arg[3:] in self._case:
1765 return False
1816 return False
1766 elif arg in self._allcases:
1817 elif arg in self._allcases:
1767 if arg not in self._case:
1818 if arg not in self._case:
1768 return False
1819 return False
1769 else:
1820 else:
1770 reqs.append(arg)
1821 reqs.append(arg)
1771 self._detectslow(reqs)
1822 self._detectslow(reqs)
1772 return self._hghave(reqs)[0]
1823 return self._hghave(reqs)[0]
1773
1824
1774 def _parsetest(self, lines):
1825 def _parsetest(self, lines):
1775 # We generate a shell script which outputs unique markers to line
1826 # We generate a shell script which outputs unique markers to line
1776 # up script results with our source. These markers include input
1827 # up script results with our source. These markers include input
1777 # line number and the last return code.
1828 # line number and the last return code.
1778 salt = b"SALT%d" % time.time()
1829 salt = b"SALT%d" % time.time()
1779
1830
1780 def addsalt(line, inpython):
1831 def addsalt(line, inpython):
1781 if inpython:
1832 if inpython:
1782 script.append(b'%s %d 0\n' % (salt, line))
1833 script.append(b'%s %d 0\n' % (salt, line))
1783 else:
1834 else:
1784 script.append(b'echo %s %d $?\n' % (salt, line))
1835 script.append(b'echo %s %d $?\n' % (salt, line))
1785
1836
1786 activetrace = []
1837 activetrace = []
1787 session = str(uuid.uuid4()).encode('ascii')
1838 session = str(uuid.uuid4()).encode('ascii')
1788 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1839 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1789 'HGCATAPULTSERVERPIPE'
1840 'HGCATAPULTSERVERPIPE'
1790 )
1841 )
1791
1842
1792 def toggletrace(cmd=None):
1843 def toggletrace(cmd=None):
1793 if not hgcatapult or hgcatapult == os.devnull:
1844 if not hgcatapult or hgcatapult == os.devnull:
1794 return
1845 return
1795
1846
1796 if activetrace:
1847 if activetrace:
1797 script.append(
1848 script.append(
1798 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1849 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1799 % (session, activetrace[0])
1850 % (session, activetrace[0])
1800 )
1851 )
1801 if cmd is None:
1852 if cmd is None:
1802 return
1853 return
1803
1854
1804 if isinstance(cmd, str):
1855 if isinstance(cmd, str):
1805 quoted = shellquote(cmd.strip())
1856 quoted = shellquote(cmd.strip())
1806 else:
1857 else:
1807 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1858 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1808 quoted = quoted.replace(b'\\', b'\\\\')
1859 quoted = quoted.replace(b'\\', b'\\\\')
1809 script.append(
1860 script.append(
1810 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1861 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1811 % (session, quoted)
1862 % (session, quoted)
1812 )
1863 )
1813 activetrace[0:] = [quoted]
1864 activetrace[0:] = [quoted]
1814
1865
1815 script = []
1866 script = []
1816
1867
1817 # After we run the shell script, we re-unify the script output
1868 # After we run the shell script, we re-unify the script output
1818 # with non-active parts of the source, with synchronization by our
1869 # with non-active parts of the source, with synchronization by our
1819 # SALT line number markers. The after table contains the non-active
1870 # SALT line number markers. The after table contains the non-active
1820 # components, ordered by line number.
1871 # components, ordered by line number.
1821 after = {}
1872 after = {}
1822
1873
1823 # Expected shell script output.
1874 # Expected shell script output.
1824 expected = {}
1875 expected = {}
1825
1876
1826 pos = prepos = -1
1877 pos = prepos = -1
1827
1878
1828 # The current stack of conditionnal section.
1879 # The current stack of conditionnal section.
1829 # Each relevant conditionnal section can have the following value:
1880 # Each relevant conditionnal section can have the following value:
1830 # - True: we should run this block
1881 # - True: we should run this block
1831 # - False: we should skip this block
1882 # - False: we should skip this block
1832 # - None: The parent block is skipped,
1883 # - None: The parent block is skipped,
1833 # (no branch of this one will ever run)
1884 # (no branch of this one will ever run)
1834 condition_stack = []
1885 condition_stack = []
1835
1886
1836 def run_line():
1887 def run_line():
1837 """return True if the current line should be run"""
1888 """return True if the current line should be run"""
1838 if not condition_stack:
1889 if not condition_stack:
1839 return True
1890 return True
1840 return bool(condition_stack[-1])
1891 return bool(condition_stack[-1])
1841
1892
1842 def push_conditional_block(should_run):
1893 def push_conditional_block(should_run):
1843 """Push a new conditional context, with its initial state
1894 """Push a new conditional context, with its initial state
1844
1895
1845 i.e. entry a #if block"""
1896 i.e. entry a #if block"""
1846 if not run_line():
1897 if not run_line():
1847 condition_stack.append(None)
1898 condition_stack.append(None)
1848 else:
1899 else:
1849 condition_stack.append(should_run)
1900 condition_stack.append(should_run)
1850
1901
1851 def flip_conditional():
1902 def flip_conditional():
1852 """reverse the current condition state
1903 """reverse the current condition state
1853
1904
1854 i.e. enter a #else
1905 i.e. enter a #else
1855 """
1906 """
1856 assert condition_stack
1907 assert condition_stack
1857 if condition_stack[-1] is not None:
1908 if condition_stack[-1] is not None:
1858 condition_stack[-1] = not condition_stack[-1]
1909 condition_stack[-1] = not condition_stack[-1]
1859
1910
1860 def pop_conditional():
1911 def pop_conditional():
1861 """exit the current skipping context
1912 """exit the current skipping context
1862
1913
1863 i.e. reach the #endif"""
1914 i.e. reach the #endif"""
1864 assert condition_stack
1915 assert condition_stack
1865 condition_stack.pop()
1916 condition_stack.pop()
1866
1917
1867 # We keep track of whether or not we're in a Python block so we
1918 # We keep track of whether or not we're in a Python block so we
1868 # can generate the surrounding doctest magic.
1919 # can generate the surrounding doctest magic.
1869 inpython = False
1920 inpython = False
1870
1921
1871 if self._debug:
1922 if self._debug:
1872 script.append(b'set -x\n')
1923 script.append(b'set -x\n')
1873 if os.getenv('MSYSTEM'):
1924 if os.getenv('MSYSTEM'):
1874 script.append(b'alias pwd="pwd -W"\n')
1925 script.append(b'alias pwd="pwd -W"\n')
1875
1926
1876 if hgcatapult and hgcatapult != os.devnull:
1927 if hgcatapult and hgcatapult != os.devnull:
1877 hgcatapult = hgcatapult.encode('utf8')
1928 hgcatapult = hgcatapult.encode('utf8')
1878 cataname = self.name.encode('utf8')
1929 cataname = self.name.encode('utf8')
1879
1930
1880 # Kludge: use a while loop to keep the pipe from getting
1931 # Kludge: use a while loop to keep the pipe from getting
1881 # closed by our echo commands. The still-running file gets
1932 # closed by our echo commands. The still-running file gets
1882 # reaped at the end of the script, which causes the while
1933 # reaped at the end of the script, which causes the while
1883 # loop to exit and closes the pipe. Sigh.
1934 # loop to exit and closes the pipe. Sigh.
1884 script.append(
1935 script.append(
1885 b'rtendtracing() {\n'
1936 b'rtendtracing() {\n'
1886 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1937 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1887 b' rm -f "$TESTTMP/.still-running"\n'
1938 b' rm -f "$TESTTMP/.still-running"\n'
1888 b'}\n'
1939 b'}\n'
1889 b'trap "rtendtracing" 0\n'
1940 b'trap "rtendtracing" 0\n'
1890 b'touch "$TESTTMP/.still-running"\n'
1941 b'touch "$TESTTMP/.still-running"\n'
1891 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1942 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1892 b'> %(catapult)s &\n'
1943 b'> %(catapult)s &\n'
1893 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1944 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1894 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1945 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1895 % {
1946 % {
1896 b'name': cataname,
1947 b'name': cataname,
1897 b'session': session,
1948 b'session': session,
1898 b'catapult': hgcatapult,
1949 b'catapult': hgcatapult,
1899 }
1950 }
1900 )
1951 )
1901
1952
1902 if self._case:
1953 if self._case:
1903 casestr = b'#'.join(self._case)
1954 casestr = b'#'.join(self._case)
1904 if isinstance(casestr, str):
1955 if isinstance(casestr, str):
1905 quoted = shellquote(casestr)
1956 quoted = shellquote(casestr)
1906 else:
1957 else:
1907 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1958 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1908 script.append(b'TESTCASE=%s\n' % quoted)
1959 script.append(b'TESTCASE=%s\n' % quoted)
1909 script.append(b'export TESTCASE\n')
1960 script.append(b'export TESTCASE\n')
1910
1961
1911 n = 0
1962 n = 0
1912 for n, l in enumerate(lines):
1963 for n, l in enumerate(lines):
1913 if not l.endswith(b'\n'):
1964 if not l.endswith(b'\n'):
1914 l += b'\n'
1965 l += b'\n'
1915 if l.startswith(b'#require'):
1966 if l.startswith(b'#require'):
1916 lsplit = l.split()
1967 lsplit = l.split()
1917 if len(lsplit) < 2 or lsplit[0] != b'#require':
1968 if len(lsplit) < 2 or lsplit[0] != b'#require':
1918 after.setdefault(pos, []).append(
1969 after.setdefault(pos, []).append(
1919 b' !!! invalid #require\n'
1970 b' !!! invalid #require\n'
1920 )
1971 )
1921 if run_line():
1972 if run_line():
1922 haveresult, message = self._hghave(lsplit[1:])
1973 haveresult, message = self._hghave(lsplit[1:])
1923 if not haveresult:
1974 if not haveresult:
1924 script = [b'echo "%s"\nexit 80\n' % message]
1975 script = [b'echo "%s"\nexit 80\n' % message]
1925 break
1976 break
1926 after.setdefault(pos, []).append(l)
1977 after.setdefault(pos, []).append(l)
1927 elif l.startswith(b'#if'):
1978 elif l.startswith(b'#if'):
1928 lsplit = l.split()
1979 lsplit = l.split()
1929 if len(lsplit) < 2 or lsplit[0] != b'#if':
1980 if len(lsplit) < 2 or lsplit[0] != b'#if':
1930 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1981 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1931 push_conditional_block(self._iftest(lsplit[1:]))
1982 push_conditional_block(self._iftest(lsplit[1:]))
1932 after.setdefault(pos, []).append(l)
1983 after.setdefault(pos, []).append(l)
1933 elif l.startswith(b'#else'):
1984 elif l.startswith(b'#else'):
1934 if not condition_stack:
1985 if not condition_stack:
1935 after.setdefault(pos, []).append(b' !!! missing #if\n')
1986 after.setdefault(pos, []).append(b' !!! missing #if\n')
1936 flip_conditional()
1987 flip_conditional()
1937 after.setdefault(pos, []).append(l)
1988 after.setdefault(pos, []).append(l)
1938 elif l.startswith(b'#endif'):
1989 elif l.startswith(b'#endif'):
1939 if not condition_stack:
1990 if not condition_stack:
1940 after.setdefault(pos, []).append(b' !!! missing #if\n')
1991 after.setdefault(pos, []).append(b' !!! missing #if\n')
1941 pop_conditional()
1992 pop_conditional()
1942 after.setdefault(pos, []).append(l)
1993 after.setdefault(pos, []).append(l)
1943 elif not run_line():
1994 elif not run_line():
1944 after.setdefault(pos, []).append(l)
1995 after.setdefault(pos, []).append(l)
1945 elif l.startswith(b' >>> '): # python inlines
1996 elif l.startswith(b' >>> '): # python inlines
1946 after.setdefault(pos, []).append(l)
1997 after.setdefault(pos, []).append(l)
1947 prepos = pos
1998 prepos = pos
1948 pos = n
1999 pos = n
1949 if not inpython:
2000 if not inpython:
1950 # We've just entered a Python block. Add the header.
2001 # We've just entered a Python block. Add the header.
1951 inpython = True
2002 inpython = True
1952 addsalt(prepos, False) # Make sure we report the exit code.
2003 addsalt(prepos, False) # Make sure we report the exit code.
1953 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
2004 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1954 addsalt(n, True)
2005 addsalt(n, True)
1955 script.append(l[2:])
2006 script.append(l[2:])
1956 elif l.startswith(b' ... '): # python inlines
2007 elif l.startswith(b' ... '): # python inlines
1957 after.setdefault(prepos, []).append(l)
2008 after.setdefault(prepos, []).append(l)
1958 script.append(l[2:])
2009 script.append(l[2:])
1959 elif l.startswith(b' $ '): # commands
2010 elif l.startswith(b' $ '): # commands
1960 if inpython:
2011 if inpython:
1961 script.append(b'EOF\n')
2012 script.append(b'EOF\n')
1962 inpython = False
2013 inpython = False
1963 after.setdefault(pos, []).append(l)
2014 after.setdefault(pos, []).append(l)
1964 prepos = pos
2015 prepos = pos
1965 pos = n
2016 pos = n
1966 addsalt(n, False)
2017 addsalt(n, False)
1967 rawcmd = l[4:]
2018 rawcmd = l[4:]
1968 cmd = rawcmd.split()
2019 cmd = rawcmd.split()
1969 toggletrace(rawcmd)
2020 toggletrace(rawcmd)
1970 if len(cmd) == 2 and cmd[0] == b'cd':
2021 if len(cmd) == 2 and cmd[0] == b'cd':
1971 rawcmd = b'cd %s || exit 1\n' % cmd[1]
2022 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1972 script.append(rawcmd)
2023 script.append(rawcmd)
1973 elif l.startswith(b' > '): # continuations
2024 elif l.startswith(b' > '): # continuations
1974 after.setdefault(prepos, []).append(l)
2025 after.setdefault(prepos, []).append(l)
1975 script.append(l[4:])
2026 script.append(l[4:])
1976 elif l.startswith(b' '): # results
2027 elif l.startswith(b' '): # results
1977 # Queue up a list of expected results.
2028 # Queue up a list of expected results.
1978 expected.setdefault(pos, []).append(l[2:])
2029 expected.setdefault(pos, []).append(l[2:])
1979 else:
2030 else:
1980 if inpython:
2031 if inpython:
1981 script.append(b'EOF\n')
2032 script.append(b'EOF\n')
1982 inpython = False
2033 inpython = False
1983 # Non-command/result. Queue up for merged output.
2034 # Non-command/result. Queue up for merged output.
1984 after.setdefault(pos, []).append(l)
2035 after.setdefault(pos, []).append(l)
1985
2036
1986 if inpython:
2037 if inpython:
1987 script.append(b'EOF\n')
2038 script.append(b'EOF\n')
1988 if condition_stack:
2039 if condition_stack:
1989 after.setdefault(pos, []).append(b' !!! missing #endif\n')
2040 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1990 addsalt(n + 1, False)
2041 addsalt(n + 1, False)
1991 # Need to end any current per-command trace
2042 # Need to end any current per-command trace
1992 if activetrace:
2043 if activetrace:
1993 toggletrace()
2044 toggletrace()
1994 return salt, script, after, expected
2045 return salt, script, after, expected
1995
2046
1996 def _processoutput(self, exitcode, output, salt, after, expected):
2047 def _processoutput(self, exitcode, output, salt, after, expected):
1997 # Merge the script output back into a unified test.
2048 # Merge the script output back into a unified test.
1998 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
2049 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1999 if exitcode != 0:
2050 if exitcode != 0:
2000 warnonly = WARN_NO
2051 warnonly = WARN_NO
2001
2052
2002 pos = -1
2053 pos = -1
2003 postout = []
2054 postout = []
2004 for out_rawline in output:
2055 for out_rawline in output:
2005 out_line, cmd_line = out_rawline, None
2056 out_line, cmd_line = out_rawline, None
2006 if salt in out_rawline:
2057 if salt in out_rawline:
2007 out_line, cmd_line = out_rawline.split(salt, 1)
2058 out_line, cmd_line = out_rawline.split(salt, 1)
2008
2059
2009 pos, postout, warnonly = self._process_out_line(
2060 pos, postout, warnonly = self._process_out_line(
2010 out_line, pos, postout, expected, warnonly
2061 out_line, pos, postout, expected, warnonly
2011 )
2062 )
2012 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2063 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
2013
2064
2014 if pos in after:
2065 if pos in after:
2015 postout += after.pop(pos)
2066 postout += after.pop(pos)
2016
2067
2017 if warnonly == WARN_YES:
2068 if warnonly == WARN_YES:
2018 exitcode = False # Set exitcode to warned.
2069 exitcode = False # Set exitcode to warned.
2019
2070
2020 return exitcode, postout
2071 return exitcode, postout
2021
2072
2022 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2073 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
2023 while out_line:
2074 while out_line:
2024 if not out_line.endswith(b'\n'):
2075 if not out_line.endswith(b'\n'):
2025 out_line += b' (no-eol)\n'
2076 out_line += b' (no-eol)\n'
2026
2077
2027 # Find the expected output at the current position.
2078 # Find the expected output at the current position.
2028 els = [None]
2079 els = [None]
2029 if expected.get(pos, None):
2080 if expected.get(pos, None):
2030 els = expected[pos]
2081 els = expected[pos]
2031
2082
2032 optional = []
2083 optional = []
2033 for i, el in enumerate(els):
2084 for i, el in enumerate(els):
2034 r = False
2085 r = False
2035 if el:
2086 if el:
2036 r, exact = self.linematch(el, out_line)
2087 r, exact = self.linematch(el, out_line)
2037 if isinstance(r, str):
2088 if isinstance(r, str):
2038 if r == '-glob':
2089 if r == '-glob':
2039 out_line = ''.join(el.rsplit(' (glob)', 1))
2090 out_line = ''.join(el.rsplit(' (glob)', 1))
2040 r = '' # Warn only this line.
2091 r = '' # Warn only this line.
2041 elif r == "retry":
2092 elif r == "retry":
2042 postout.append(b' ' + el)
2093 postout.append(b' ' + el)
2043 else:
2094 else:
2044 log('\ninfo, unknown linematch result: %r\n' % r)
2095 log('\ninfo, unknown linematch result: %r\n' % r)
2045 r = False
2096 r = False
2046 if r:
2097 if r:
2047 els.pop(i)
2098 els.pop(i)
2048 break
2099 break
2049 if el:
2100 if el:
2050 if isoptional(el):
2101 if isoptional(el):
2051 optional.append(i)
2102 optional.append(i)
2052 else:
2103 else:
2053 m = optline.match(el)
2104 m = optline.match(el)
2054 if m:
2105 if m:
2055 conditions = [c for c in m.group(2).split(b' ')]
2106 conditions = [c for c in m.group(2).split(b' ')]
2056
2107
2057 if not self._iftest(conditions):
2108 if not self._iftest(conditions):
2058 optional.append(i)
2109 optional.append(i)
2059 if exact:
2110 if exact:
2060 # Don't allow line to be matches against a later
2111 # Don't allow line to be matches against a later
2061 # line in the output
2112 # line in the output
2062 els.pop(i)
2113 els.pop(i)
2063 break
2114 break
2064
2115
2065 if r:
2116 if r:
2066 if r == "retry":
2117 if r == "retry":
2067 continue
2118 continue
2068 # clean up any optional leftovers
2119 # clean up any optional leftovers
2069 for i in optional:
2120 for i in optional:
2070 postout.append(b' ' + els[i])
2121 postout.append(b' ' + els[i])
2071 for i in reversed(optional):
2122 for i in reversed(optional):
2072 del els[i]
2123 del els[i]
2073 postout.append(b' ' + el)
2124 postout.append(b' ' + el)
2074 else:
2125 else:
2075 if self.NEEDESCAPE(out_line):
2126 if self.NEEDESCAPE(out_line):
2076 out_line = TTest._stringescape(
2127 out_line = TTest._stringescape(
2077 b'%s (esc)\n' % out_line.rstrip(b'\n')
2128 b'%s (esc)\n' % out_line.rstrip(b'\n')
2078 )
2129 )
2079 postout.append(b' ' + out_line) # Let diff deal with it.
2130 postout.append(b' ' + out_line) # Let diff deal with it.
2080 if r != '': # If line failed.
2131 if r != '': # If line failed.
2081 warnonly = WARN_NO
2132 warnonly = WARN_NO
2082 elif warnonly == WARN_UNDEFINED:
2133 elif warnonly == WARN_UNDEFINED:
2083 warnonly = WARN_YES
2134 warnonly = WARN_YES
2084 break
2135 break
2085 else:
2136 else:
2086 # clean up any optional leftovers
2137 # clean up any optional leftovers
2087 while expected.get(pos, None):
2138 while expected.get(pos, None):
2088 el = expected[pos].pop(0)
2139 el = expected[pos].pop(0)
2089 if el:
2140 if el:
2090 if not isoptional(el):
2141 if not isoptional(el):
2091 m = optline.match(el)
2142 m = optline.match(el)
2092 if m:
2143 if m:
2093 conditions = [c for c in m.group(2).split(b' ')]
2144 conditions = [c for c in m.group(2).split(b' ')]
2094
2145
2095 if self._iftest(conditions):
2146 if self._iftest(conditions):
2096 # Don't append as optional line
2147 # Don't append as optional line
2097 continue
2148 continue
2098 else:
2149 else:
2099 continue
2150 continue
2100 postout.append(b' ' + el)
2151 postout.append(b' ' + el)
2101 return pos, postout, warnonly
2152 return pos, postout, warnonly
2102
2153
2103 def _process_cmd_line(self, cmd_line, pos, postout, after):
2154 def _process_cmd_line(self, cmd_line, pos, postout, after):
2104 """process a "command" part of a line from unified test output"""
2155 """process a "command" part of a line from unified test output"""
2105 if cmd_line:
2156 if cmd_line:
2106 # Add on last return code.
2157 # Add on last return code.
2107 ret = int(cmd_line.split()[1])
2158 ret = int(cmd_line.split()[1])
2108 if ret != 0:
2159 if ret != 0:
2109 postout.append(b' [%d]\n' % ret)
2160 postout.append(b' [%d]\n' % ret)
2110 if pos in after:
2161 if pos in after:
2111 # Merge in non-active test bits.
2162 # Merge in non-active test bits.
2112 postout += after.pop(pos)
2163 postout += after.pop(pos)
2113 pos = int(cmd_line.split()[0])
2164 pos = int(cmd_line.split()[0])
2114 return pos, postout
2165 return pos, postout
2115
2166
2116 @staticmethod
2167 @staticmethod
2117 def rematch(el, l):
2168 def rematch(el, l):
2118 try:
2169 try:
2119 # parse any flags at the beginning of the regex. Only 'i' is
2170 # parse any flags at the beginning of the regex. Only 'i' is
2120 # supported right now, but this should be easy to extend.
2171 # supported right now, but this should be easy to extend.
2121 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2172 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2122 flags = flags or b''
2173 flags = flags or b''
2123 el = flags + b'(?:' + el + b')'
2174 el = flags + b'(?:' + el + b')'
2124 # use \Z to ensure that the regex matches to the end of the string
2175 # use \Z to ensure that the regex matches to the end of the string
2125 if WINDOWS:
2176 if WINDOWS:
2126 return re.match(el + br'\r?\n\Z', l)
2177 return re.match(el + br'\r?\n\Z', l)
2127 return re.match(el + br'\n\Z', l)
2178 return re.match(el + br'\n\Z', l)
2128 except re.error:
2179 except re.error:
2129 # el is an invalid regex
2180 # el is an invalid regex
2130 return False
2181 return False
2131
2182
2132 @staticmethod
2183 @staticmethod
2133 def globmatch(el, l):
2184 def globmatch(el, l):
2134 # The only supported special characters are * and ? plus / which also
2185 # The only supported special characters are * and ? plus / which also
2135 # matches \ on windows. Escaping of these characters is supported.
2186 # matches \ on windows. Escaping of these characters is supported.
2136 if el + b'\n' == l:
2187 if el + b'\n' == l:
2137 if os.altsep:
2188 if os.altsep:
2138 # matching on "/" is not needed for this line
2189 # matching on "/" is not needed for this line
2139 for pat in checkcodeglobpats:
2190 for pat in checkcodeglobpats:
2140 if pat.match(el):
2191 if pat.match(el):
2141 return True
2192 return True
2142 return b'-glob'
2193 return b'-glob'
2143 return True
2194 return True
2144 el = el.replace(b'$LOCALIP', b'*')
2195 el = el.replace(b'$LOCALIP', b'*')
2145 i, n = 0, len(el)
2196 i, n = 0, len(el)
2146 res = b''
2197 res = b''
2147 while i < n:
2198 while i < n:
2148 c = el[i : i + 1]
2199 c = el[i : i + 1]
2149 i += 1
2200 i += 1
2150 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2201 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2151 res += el[i - 1 : i + 1]
2202 res += el[i - 1 : i + 1]
2152 i += 1
2203 i += 1
2153 elif c == b'*':
2204 elif c == b'*':
2154 res += b'.*'
2205 res += b'.*'
2155 elif c == b'?':
2206 elif c == b'?':
2156 res += b'.'
2207 res += b'.'
2157 elif c == b'/' and os.altsep:
2208 elif c == b'/' and os.altsep:
2158 res += b'[/\\\\]'
2209 res += b'[/\\\\]'
2159 else:
2210 else:
2160 res += re.escape(c)
2211 res += re.escape(c)
2161 return TTest.rematch(res, l)
2212 return TTest.rematch(res, l)
2162
2213
2163 def linematch(self, el, l):
2214 def linematch(self, el, l):
2164 if el == l: # perfect match (fast)
2215 if el == l: # perfect match (fast)
2165 return True, True
2216 return True, True
2166 retry = False
2217 retry = False
2167 if isoptional(el):
2218 if isoptional(el):
2168 retry = "retry"
2219 retry = "retry"
2169 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2220 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2170 else:
2221 else:
2171 m = optline.match(el)
2222 m = optline.match(el)
2172 if m:
2223 if m:
2173 conditions = [c for c in m.group(2).split(b' ')]
2224 conditions = [c for c in m.group(2).split(b' ')]
2174
2225
2175 el = m.group(1) + b"\n"
2226 el = m.group(1) + b"\n"
2176 if not self._iftest(conditions):
2227 if not self._iftest(conditions):
2177 # listed feature missing, should not match
2228 # listed feature missing, should not match
2178 return "retry", False
2229 return "retry", False
2179
2230
2180 if el.endswith(b" (esc)\n"):
2231 if el.endswith(b" (esc)\n"):
2181 el = el[:-7].decode('unicode_escape') + '\n'
2232 el = el[:-7].decode('unicode_escape') + '\n'
2182 el = el.encode('latin-1')
2233 el = el.encode('latin-1')
2183 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2234 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2184 return True, True
2235 return True, True
2185 if el.endswith(b" (re)\n"):
2236 if el.endswith(b" (re)\n"):
2186 return (TTest.rematch(el[:-6], l) or retry), False
2237 return (TTest.rematch(el[:-6], l) or retry), False
2187 if el.endswith(b" (glob)\n"):
2238 if el.endswith(b" (glob)\n"):
2188 # ignore '(glob)' added to l by 'replacements'
2239 # ignore '(glob)' added to l by 'replacements'
2189 if l.endswith(b" (glob)\n"):
2240 if l.endswith(b" (glob)\n"):
2190 l = l[:-8] + b"\n"
2241 l = l[:-8] + b"\n"
2191 return (TTest.globmatch(el[:-8], l) or retry), False
2242 return (TTest.globmatch(el[:-8], l) or retry), False
2192 if os.altsep:
2243 if os.altsep:
2193 _l = l.replace(b'\\', b'/')
2244 _l = l.replace(b'\\', b'/')
2194 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2245 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2195 return True, True
2246 return True, True
2196 return retry, True
2247 return retry, True
2197
2248
2198 @staticmethod
2249 @staticmethod
2199 def parsehghaveoutput(lines):
2250 def parsehghaveoutput(lines):
2200 """Parse hghave log lines.
2251 """Parse hghave log lines.
2201
2252
2202 Return tuple of lists (missing, failed):
2253 Return tuple of lists (missing, failed):
2203 * the missing/unknown features
2254 * the missing/unknown features
2204 * the features for which existence check failed"""
2255 * the features for which existence check failed"""
2205 missing = []
2256 missing = []
2206 failed = []
2257 failed = []
2207 for line in lines:
2258 for line in lines:
2208 if line.startswith(TTest.SKIPPED_PREFIX):
2259 if line.startswith(TTest.SKIPPED_PREFIX):
2209 line = line.splitlines()[0]
2260 line = line.splitlines()[0]
2210 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2261 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2211 elif line.startswith(TTest.FAILED_PREFIX):
2262 elif line.startswith(TTest.FAILED_PREFIX):
2212 line = line.splitlines()[0]
2263 line = line.splitlines()[0]
2213 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2264 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2214
2265
2215 return missing, failed
2266 return missing, failed
2216
2267
2217 @staticmethod
2268 @staticmethod
2218 def _escapef(m):
2269 def _escapef(m):
2219 return TTest.ESCAPEMAP[m.group(0)]
2270 return TTest.ESCAPEMAP[m.group(0)]
2220
2271
2221 @staticmethod
2272 @staticmethod
2222 def _stringescape(s):
2273 def _stringescape(s):
2223 return TTest.ESCAPESUB(TTest._escapef, s)
2274 return TTest.ESCAPESUB(TTest._escapef, s)
2224
2275
2225
2276
2226 iolock = threading.RLock()
2277 iolock = threading.RLock()
2227 firstlock = threading.RLock()
2278 firstlock = threading.RLock()
2228 firsterror = False
2279 firsterror = False
2229
2280
2230 base_class = unittest.TextTestResult
2281 base_class = unittest.TextTestResult
2231
2282
2232
2283
2233 class TestResult(base_class):
2284 class TestResult(base_class):
2234 """Holds results when executing via unittest."""
2285 """Holds results when executing via unittest."""
2235
2286
2236 def __init__(self, options, *args, **kwargs):
2287 def __init__(self, options, *args, **kwargs):
2237 super(TestResult, self).__init__(*args, **kwargs)
2288 super(TestResult, self).__init__(*args, **kwargs)
2238
2289
2239 self._options = options
2290 self._options = options
2240
2291
2241 # unittest.TestResult didn't have skipped until 2.7. We need to
2292 # unittest.TestResult didn't have skipped until 2.7. We need to
2242 # polyfill it.
2293 # polyfill it.
2243 self.skipped = []
2294 self.skipped = []
2244
2295
2245 # We have a custom "ignored" result that isn't present in any Python
2296 # We have a custom "ignored" result that isn't present in any Python
2246 # unittest implementation. It is very similar to skipped. It may make
2297 # unittest implementation. It is very similar to skipped. It may make
2247 # sense to map it into skip some day.
2298 # sense to map it into skip some day.
2248 self.ignored = []
2299 self.ignored = []
2249
2300
2250 self.times = []
2301 self.times = []
2251 self._firststarttime = None
2302 self._firststarttime = None
2252 # Data stored for the benefit of generating xunit reports.
2303 # Data stored for the benefit of generating xunit reports.
2253 self.successes = []
2304 self.successes = []
2254 self.faildata = {}
2305 self.faildata = {}
2255
2306
2256 if options.color == 'auto':
2307 if options.color == 'auto':
2257 isatty = self.stream.isatty()
2308 isatty = self.stream.isatty()
2258 # For some reason, redirecting stdout on Windows disables the ANSI
2309 # For some reason, redirecting stdout on Windows disables the ANSI
2259 # color processing of stderr, which is what is used to print the
2310 # color processing of stderr, which is what is used to print the
2260 # output. Therefore, both must be tty on Windows to enable color.
2311 # output. Therefore, both must be tty on Windows to enable color.
2261 if WINDOWS:
2312 if WINDOWS:
2262 isatty = isatty and sys.stdout.isatty()
2313 isatty = isatty and sys.stdout.isatty()
2263 self.color = pygmentspresent and isatty
2314 self.color = pygmentspresent and isatty
2264 elif options.color == 'never':
2315 elif options.color == 'never':
2265 self.color = False
2316 self.color = False
2266 else: # 'always', for testing purposes
2317 else: # 'always', for testing purposes
2267 self.color = pygmentspresent
2318 self.color = pygmentspresent
2268
2319
2320 def _write_dot(self, progress):
2321 """write an item of the "dot" progress"""
2322 formated = highlight_progress(progress, self.color)
2323 self.stream.write(formated)
2324 self.stream.flush()
2325
2269 def onStart(self, test):
2326 def onStart(self, test):
2270 """Can be overriden by custom TestResult"""
2327 """Can be overriden by custom TestResult"""
2271
2328
2272 def onEnd(self):
2329 def onEnd(self):
2273 """Can be overriden by custom TestResult"""
2330 """Can be overriden by custom TestResult"""
2274
2331
2275 def addFailure(self, test, reason):
2332 def addFailure(self, test, reason):
2276 self.failures.append((test, reason))
2333 self.failures.append((test, reason))
2277
2334
2278 if self._options.first:
2335 if self._options.first:
2279 self.stop()
2336 self.stop()
2280 else:
2337 else:
2281 with iolock:
2338 with iolock:
2282 if reason == "timed out":
2339 if reason == "timed out":
2283 self.stream.write('t')
2340 self._write_dot('t')
2284 else:
2341 else:
2285 if not self._options.nodiff:
2342 if not self._options.nodiff:
2286 self.stream.write('\n')
2343 self.stream.write('\n')
2287 # Exclude the '\n' from highlighting to lex correctly
2344 # Exclude the '\n' from highlighting to lex correctly
2288 formatted = 'ERROR: %s output changed\n' % test
2345 formatted = 'ERROR: %s output changed\n' % test
2289 self.stream.write(highlightmsg(formatted, self.color))
2346 self.stream.write(highlightmsg(formatted, self.color))
2290 self.stream.write('!')
2347 self._write_dot('!')
2291
2348
2292 self.stream.flush()
2349 self.stream.flush()
2293
2350
2294 def addSuccess(self, test):
2351 def addSuccess(self, test):
2295 with iolock:
2352 with iolock:
2296 super(TestResult, self).addSuccess(test)
2353 # bypass the TextTestResult method as do deal with the output ourself
2354 super(base_class, self).addSuccess(test)
2355 if self.showAll:
2356 self._write_status(test, "ok")
2357 elif self.dots:
2358 self._write_dot('.')
2297 self.successes.append(test)
2359 self.successes.append(test)
2298
2360
2299 def addError(self, test, err):
2361 def addError(self, test, err):
2300 super(TestResult, self).addError(test, err)
2362 super(base_class, self).addError(test, err)
2363 if self.showAll:
2364 self._write_status(test, "ERROR")
2365 elif self.dots:
2366 self._write_dot('E')
2301 if self._options.first:
2367 if self._options.first:
2302 self.stop()
2368 self.stop()
2303
2369
2304 # Polyfill.
2370 # Polyfill.
2305 def addSkip(self, test, reason):
2371 def addSkip(self, test, reason):
2306 self.skipped.append((test, reason))
2372 self.skipped.append((test, reason))
2307 with iolock:
2373 with iolock:
2308 if self.showAll:
2374 if self.showAll:
2309 self.stream.writeln('skipped %s' % reason)
2375 self.stream.writeln('skipped %s' % reason)
2310 else:
2376 else:
2311 self.stream.write('s')
2377 self._write_dot('s')
2312 self.stream.flush()
2313
2378
2314 def addIgnore(self, test, reason):
2379 def addIgnore(self, test, reason):
2315 self.ignored.append((test, reason))
2380 self.ignored.append((test, reason))
2316 with iolock:
2381 with iolock:
2317 if self.showAll:
2382 if self.showAll:
2318 self.stream.writeln('ignored %s' % reason)
2383 self.stream.writeln('ignored %s' % reason)
2319 else:
2384 else:
2320 if reason not in ('not retesting', "doesn't match keyword"):
2385 if reason not in ('not retesting', "doesn't match keyword"):
2321 self.stream.write('i')
2386 self._write_dot('i')
2322 else:
2387 else:
2323 self.testsRun += 1
2388 self.testsRun += 1
2324 self.stream.flush()
2325
2389
2326 def addOutputMismatch(self, test, ret, got, expected):
2390 def addOutputMismatch(self, test, ret, got, expected):
2327 """Record a mismatch in test output for a particular test."""
2391 """Record a mismatch in test output for a particular test."""
2328 if self.shouldStop or firsterror:
2392 if self.shouldStop or firsterror:
2329 # don't print, some other test case already failed and
2393 # don't print, some other test case already failed and
2330 # printed, we're just stale and probably failed due to our
2394 # printed, we're just stale and probably failed due to our
2331 # temp dir getting cleaned up.
2395 # temp dir getting cleaned up.
2332 return
2396 return
2333
2397
2334 accepted = False
2398 accepted = False
2335 lines = []
2399 lines = []
2336
2400
2337 with iolock:
2401 with iolock:
2338 if self._options.nodiff:
2402 if self._options.nodiff:
2339 pass
2403 pass
2340 elif self._options.view:
2404 elif self._options.view:
2341 v = self._options.view
2405 v = self._options.view
2342 subprocess.call(
2406 subprocess.call(
2343 r'"%s" "%s" "%s"'
2407 r'"%s" "%s" "%s"'
2344 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2408 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2345 shell=True,
2409 shell=True,
2346 )
2410 )
2347 else:
2411 else:
2348 servefail, lines = getdiff(
2412 servefail, lines = getdiff(
2349 expected, got, test.refpath, test.errpath
2413 expected, got, test.refpath, test.errpath
2350 )
2414 )
2351 self.stream.write('\n')
2415 self.stream.write('\n')
2352 for line in lines:
2416 for line in lines:
2353 line = highlightdiff(line, self.color)
2417 line = highlightdiff(line, self.color)
2354 self.stream.flush()
2418 self.stream.flush()
2355 self.stream.buffer.write(line)
2419 self.stream.buffer.write(line)
2356 self.stream.buffer.flush()
2420 self.stream.buffer.flush()
2357
2421
2358 if servefail:
2422 if servefail:
2359 raise test.failureException(
2423 raise test.failureException(
2360 'server failed to start (HGPORT=%s)' % test._startport
2424 'server failed to start (HGPORT=%s)' % test._startport
2361 )
2425 )
2362
2426
2363 # handle interactive prompt without releasing iolock
2427 # handle interactive prompt without releasing iolock
2364 if self._options.interactive:
2428 if self._options.interactive:
2365 if test.readrefout() != expected:
2429 if test.readrefout() != expected:
2366 self.stream.write(
2430 self.stream.write(
2367 'Reference output has changed (run again to prompt '
2431 'Reference output has changed (run again to prompt '
2368 'changes)'
2432 'changes)'
2369 )
2433 )
2370 else:
2434 else:
2371 self.stream.write('Accept this change? [y/N] ')
2435 self.stream.write('Accept this change? [y/N] ')
2372 self.stream.flush()
2436 self.stream.flush()
2373 answer = sys.stdin.readline().strip()
2437 answer = sys.stdin.readline().strip()
2374 if answer.lower() in ('y', 'yes'):
2438 if answer.lower() in ('y', 'yes'):
2375 if test.path.endswith(b'.t'):
2439 if test.path.endswith(b'.t'):
2376 rename(test.errpath, test.path)
2440 rename(test.errpath, test.path)
2377 else:
2441 else:
2378 rename(test.errpath, b'%s.out' % test.path)
2442 rename(test.errpath, b'%s.out' % test.path)
2379 accepted = True
2443 accepted = True
2380 if not accepted:
2444 if not accepted:
2381 self.faildata[test.name] = b''.join(lines)
2445 self.faildata[test.name] = b''.join(lines)
2382
2446
2383 return accepted
2447 return accepted
2384
2448
2385 def startTest(self, test):
2449 def startTest(self, test):
2386 super(TestResult, self).startTest(test)
2450 super(TestResult, self).startTest(test)
2387
2451
2388 # os.times module computes the user time and system time spent by
2452 # os.times module computes the user time and system time spent by
2389 # child's processes along with real elapsed time taken by a process.
2453 # child's processes along with real elapsed time taken by a process.
2390 # This module has one limitation. It can only work for Linux user
2454 # This module has one limitation. It can only work for Linux user
2391 # and not for Windows. Hence why we fall back to another function
2455 # and not for Windows. Hence why we fall back to another function
2392 # for wall time calculations.
2456 # for wall time calculations.
2393 test.started_times = os.times()
2457 test.started_times = os.times()
2394 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2458 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2395 test.started_time = time.time()
2459 test.started_time = time.time()
2396 if self._firststarttime is None: # thread racy but irrelevant
2460 if self._firststarttime is None: # thread racy but irrelevant
2397 self._firststarttime = test.started_time
2461 self._firststarttime = test.started_time
2398
2462
2399 def stopTest(self, test, interrupted=False):
2463 def stopTest(self, test, interrupted=False):
2400 super(TestResult, self).stopTest(test)
2464 super(TestResult, self).stopTest(test)
2401
2465
2402 test.stopped_times = os.times()
2466 test.stopped_times = os.times()
2403 stopped_time = time.time()
2467 stopped_time = time.time()
2404
2468
2405 starttime = test.started_times
2469 starttime = test.started_times
2406 endtime = test.stopped_times
2470 endtime = test.stopped_times
2407 origin = self._firststarttime
2471 origin = self._firststarttime
2408 self.times.append(
2472 self.times.append(
2409 (
2473 (
2410 test.name,
2474 test.name,
2411 endtime[2] - starttime[2], # user space CPU time
2475 endtime[2] - starttime[2], # user space CPU time
2412 endtime[3] - starttime[3], # sys space CPU time
2476 endtime[3] - starttime[3], # sys space CPU time
2413 stopped_time - test.started_time, # real time
2477 stopped_time - test.started_time, # real time
2414 test.started_time - origin, # start date in run context
2478 test.started_time - origin, # start date in run context
2415 stopped_time - origin, # end date in run context
2479 stopped_time - origin, # end date in run context
2416 )
2480 )
2417 )
2481 )
2418
2482
2419 if interrupted:
2483 if interrupted:
2420 with iolock:
2484 with iolock:
2421 self.stream.writeln(
2485 self.stream.writeln(
2422 'INTERRUPTED: %s (after %d seconds)'
2486 'INTERRUPTED: %s (after %d seconds)'
2423 % (test.name, self.times[-1][3])
2487 % (test.name, self.times[-1][3])
2424 )
2488 )
2425
2489
2426
2490
2427 def getTestResult():
2491 def getTestResult():
2428 """
2492 """
2429 Returns the relevant test result
2493 Returns the relevant test result
2430 """
2494 """
2431 if "CUSTOM_TEST_RESULT" in os.environ:
2495 if "CUSTOM_TEST_RESULT" in os.environ:
2432 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2496 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2433 return testresultmodule.TestResult
2497 return testresultmodule.TestResult
2434 else:
2498 else:
2435 return TestResult
2499 return TestResult
2436
2500
2437
2501
2438 class TestSuite(unittest.TestSuite):
2502 class TestSuite(unittest.TestSuite):
2439 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2503 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2440
2504
2441 def __init__(
2505 def __init__(
2442 self,
2506 self,
2443 testdir,
2507 testdir,
2444 jobs=1,
2508 jobs=1,
2445 whitelist=None,
2509 whitelist=None,
2446 blacklist=None,
2510 blacklist=None,
2447 keywords=None,
2511 keywords=None,
2448 loop=False,
2512 loop=False,
2449 runs_per_test=1,
2513 runs_per_test=1,
2450 loadtest=None,
2514 loadtest=None,
2451 showchannels=False,
2515 showchannels=False,
2452 *args,
2516 *args,
2453 **kwargs
2517 **kwargs
2454 ):
2518 ):
2455 """Create a new instance that can run tests with a configuration.
2519 """Create a new instance that can run tests with a configuration.
2456
2520
2457 testdir specifies the directory where tests are executed from. This
2521 testdir specifies the directory where tests are executed from. This
2458 is typically the ``tests`` directory from Mercurial's source
2522 is typically the ``tests`` directory from Mercurial's source
2459 repository.
2523 repository.
2460
2524
2461 jobs specifies the number of jobs to run concurrently. Each test
2525 jobs specifies the number of jobs to run concurrently. Each test
2462 executes on its own thread. Tests actually spawn new processes, so
2526 executes on its own thread. Tests actually spawn new processes, so
2463 state mutation should not be an issue.
2527 state mutation should not be an issue.
2464
2528
2465 If there is only one job, it will use the main thread.
2529 If there is only one job, it will use the main thread.
2466
2530
2467 whitelist and blacklist denote tests that have been whitelisted and
2531 whitelist and blacklist denote tests that have been whitelisted and
2468 blacklisted, respectively. These arguments don't belong in TestSuite.
2532 blacklisted, respectively. These arguments don't belong in TestSuite.
2469 Instead, whitelist and blacklist should be handled by the thing that
2533 Instead, whitelist and blacklist should be handled by the thing that
2470 populates the TestSuite with tests. They are present to preserve
2534 populates the TestSuite with tests. They are present to preserve
2471 backwards compatible behavior which reports skipped tests as part
2535 backwards compatible behavior which reports skipped tests as part
2472 of the results.
2536 of the results.
2473
2537
2474 keywords denotes key words that will be used to filter which tests
2538 keywords denotes key words that will be used to filter which tests
2475 to execute. This arguably belongs outside of TestSuite.
2539 to execute. This arguably belongs outside of TestSuite.
2476
2540
2477 loop denotes whether to loop over tests forever.
2541 loop denotes whether to loop over tests forever.
2478 """
2542 """
2479 super(TestSuite, self).__init__(*args, **kwargs)
2543 super(TestSuite, self).__init__(*args, **kwargs)
2480
2544
2481 self._jobs = jobs
2545 self._jobs = jobs
2482 self._whitelist = whitelist
2546 self._whitelist = whitelist
2483 self._blacklist = blacklist
2547 self._blacklist = blacklist
2484 self._keywords = keywords
2548 self._keywords = keywords
2485 self._loop = loop
2549 self._loop = loop
2486 self._runs_per_test = runs_per_test
2550 self._runs_per_test = runs_per_test
2487 self._loadtest = loadtest
2551 self._loadtest = loadtest
2488 self._showchannels = showchannels
2552 self._showchannels = showchannels
2489
2553
2490 def run(self, result):
2554 def run(self, result):
2491 # We have a number of filters that need to be applied. We do this
2555 # We have a number of filters that need to be applied. We do this
2492 # here instead of inside Test because it makes the running logic for
2556 # here instead of inside Test because it makes the running logic for
2493 # Test simpler.
2557 # Test simpler.
2494 tests = []
2558 tests = []
2495 num_tests = [0]
2559 num_tests = [0]
2496 for test in self._tests:
2560 for test in self._tests:
2497
2561
2498 def get():
2562 def get():
2499 num_tests[0] += 1
2563 num_tests[0] += 1
2500 if getattr(test, 'should_reload', False):
2564 if getattr(test, 'should_reload', False):
2501 return self._loadtest(test, num_tests[0])
2565 return self._loadtest(test, num_tests[0])
2502 return test
2566 return test
2503
2567
2504 if not os.path.exists(test.path):
2568 if not os.path.exists(test.path):
2505 result.addSkip(test, "Doesn't exist")
2569 result.addSkip(test, "Doesn't exist")
2506 continue
2570 continue
2507
2571
2508 is_whitelisted = self._whitelist and (
2572 is_whitelisted = self._whitelist and (
2509 test.relpath in self._whitelist or test.bname in self._whitelist
2573 test.relpath in self._whitelist or test.bname in self._whitelist
2510 )
2574 )
2511 if not is_whitelisted:
2575 if not is_whitelisted:
2512 is_blacklisted = self._blacklist and (
2576 is_blacklisted = self._blacklist and (
2513 test.relpath in self._blacklist
2577 test.relpath in self._blacklist
2514 or test.bname in self._blacklist
2578 or test.bname in self._blacklist
2515 )
2579 )
2516 if is_blacklisted:
2580 if is_blacklisted:
2517 result.addSkip(test, 'blacklisted')
2581 result.addSkip(test, 'blacklisted')
2518 continue
2582 continue
2519 if self._keywords:
2583 if self._keywords:
2520 with open(test.path, 'rb') as f:
2584 with open(test.path, 'rb') as f:
2521 t = f.read().lower() + test.bname.lower()
2585 t = f.read().lower() + test.bname.lower()
2522 ignored = False
2586 ignored = False
2523 for k in self._keywords.lower().split():
2587 for k in self._keywords.lower().split():
2524 if k not in t:
2588 if k not in t:
2525 result.addIgnore(test, "doesn't match keyword")
2589 result.addIgnore(test, "doesn't match keyword")
2526 ignored = True
2590 ignored = True
2527 break
2591 break
2528
2592
2529 if ignored:
2593 if ignored:
2530 continue
2594 continue
2531 for _ in range(self._runs_per_test):
2595 for _ in range(self._runs_per_test):
2532 tests.append(get())
2596 tests.append(get())
2533
2597
2534 runtests = list(tests)
2598 runtests = list(tests)
2535 done = queue.Queue()
2599 done = queue.Queue()
2536 running = 0
2600 running = 0
2537
2601
2538 channels_lock = threading.Lock()
2602 channels_lock = threading.Lock()
2539 channels = [""] * self._jobs
2603 channels = [""] * self._jobs
2540
2604
2541 def job(test, result):
2605 def job(test, result):
2542 with channels_lock:
2606 with channels_lock:
2543 for n, v in enumerate(channels):
2607 for n, v in enumerate(channels):
2544 if not v:
2608 if not v:
2545 channel = n
2609 channel = n
2546 break
2610 break
2547 else:
2611 else:
2548 raise ValueError('Could not find output channel')
2612 raise ValueError('Could not find output channel')
2549 channels[channel] = "=" + test.name[5:].split(".")[0]
2613 channels[channel] = "=" + test.name[5:].split(".")[0]
2550
2614
2551 r = None
2615 r = None
2552 try:
2616 try:
2553 test(result)
2617 test(result)
2554 except KeyboardInterrupt:
2618 except KeyboardInterrupt:
2555 pass
2619 pass
2556 except: # re-raises
2620 except: # re-raises
2557 r = ('!', test, 'run-test raised an error, see traceback')
2621 r = ('!', test, 'run-test raised an error, see traceback')
2558 raise
2622 raise
2559 finally:
2623 finally:
2560 try:
2624 try:
2561 channels[channel] = ''
2625 channels[channel] = ''
2562 except IndexError:
2626 except IndexError:
2563 pass
2627 pass
2564 done.put(r)
2628 done.put(r)
2565
2629
2566 def stat():
2630 def stat():
2567 count = 0
2631 count = 0
2568 while channels:
2632 while channels:
2569 d = '\n%03s ' % count
2633 d = '\n%03s ' % count
2570 for n, v in enumerate(channels):
2634 for n, v in enumerate(channels):
2571 if v:
2635 if v:
2572 d += v[0]
2636 d += v[0]
2573 channels[n] = v[1:] or '.'
2637 channels[n] = v[1:] or '.'
2574 else:
2638 else:
2575 d += ' '
2639 d += ' '
2576 d += ' '
2640 d += ' '
2577 with iolock:
2641 with iolock:
2578 sys.stdout.write(d + ' ')
2642 sys.stdout.write(d + ' ')
2579 sys.stdout.flush()
2643 sys.stdout.flush()
2580 for x in range(10):
2644 for x in range(10):
2581 if channels:
2645 if channels:
2582 time.sleep(0.1)
2646 time.sleep(0.1)
2583 count += 1
2647 count += 1
2584
2648
2585 stoppedearly = False
2649 stoppedearly = False
2586
2650
2587 if self._showchannels:
2651 if self._showchannels:
2588 statthread = threading.Thread(target=stat, name="stat")
2652 statthread = threading.Thread(target=stat, name="stat")
2589 statthread.start()
2653 statthread.start()
2590
2654
2591 try:
2655 try:
2592 while tests or running:
2656 while tests or running:
2593 if not done.empty() or running == self._jobs or not tests:
2657 if not done.empty() or running == self._jobs or not tests:
2594 try:
2658 try:
2595 done.get(True, 1)
2659 done.get(True, 1)
2596 running -= 1
2660 running -= 1
2597 if result and result.shouldStop:
2661 if result and result.shouldStop:
2598 stoppedearly = True
2662 stoppedearly = True
2599 break
2663 break
2600 except queue.Empty:
2664 except queue.Empty:
2601 continue
2665 continue
2602 if tests and not running == self._jobs:
2666 if tests and not running == self._jobs:
2603 test = tests.pop(0)
2667 test = tests.pop(0)
2604 if self._loop:
2668 if self._loop:
2605 if getattr(test, 'should_reload', False):
2669 if getattr(test, 'should_reload', False):
2606 num_tests[0] += 1
2670 num_tests[0] += 1
2607 tests.append(self._loadtest(test, num_tests[0]))
2671 tests.append(self._loadtest(test, num_tests[0]))
2608 else:
2672 else:
2609 tests.append(test)
2673 tests.append(test)
2610 if self._jobs == 1:
2674 if self._jobs == 1:
2611 job(test, result)
2675 job(test, result)
2612 else:
2676 else:
2613 t = threading.Thread(
2677 t = threading.Thread(
2614 target=job, name=test.name, args=(test, result)
2678 target=job, name=test.name, args=(test, result)
2615 )
2679 )
2616 t.start()
2680 t.start()
2617 running += 1
2681 running += 1
2618
2682
2619 # If we stop early we still need to wait on started tests to
2683 # If we stop early we still need to wait on started tests to
2620 # finish. Otherwise, there is a race between the test completing
2684 # finish. Otherwise, there is a race between the test completing
2621 # and the test's cleanup code running. This could result in the
2685 # and the test's cleanup code running. This could result in the
2622 # test reporting incorrect.
2686 # test reporting incorrect.
2623 if stoppedearly:
2687 if stoppedearly:
2624 while running:
2688 while running:
2625 try:
2689 try:
2626 done.get(True, 1)
2690 done.get(True, 1)
2627 running -= 1
2691 running -= 1
2628 except queue.Empty:
2692 except queue.Empty:
2629 continue
2693 continue
2630 except KeyboardInterrupt:
2694 except KeyboardInterrupt:
2631 for test in runtests:
2695 for test in runtests:
2632 test.abort()
2696 test.abort()
2633
2697
2634 channels = []
2698 channels = []
2635
2699
2636 return result
2700 return result
2637
2701
2638
2702
2639 # Save the most recent 5 wall-clock runtimes of each test to a
2703 # Save the most recent 5 wall-clock runtimes of each test to a
2640 # human-readable text file named .testtimes. Tests are sorted
2704 # human-readable text file named .testtimes. Tests are sorted
2641 # alphabetically, while times for each test are listed from oldest to
2705 # alphabetically, while times for each test are listed from oldest to
2642 # newest.
2706 # newest.
2643
2707
2644
2708
2645 def loadtimes(outputdir):
2709 def loadtimes(outputdir):
2646 times = []
2710 times = []
2647 try:
2711 try:
2648 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2712 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2649 for line in fp:
2713 for line in fp:
2650 m = re.match('(.*?) ([0-9. ]+)', line)
2714 m = re.match('(.*?) ([0-9. ]+)', line)
2651 times.append(
2715 times.append(
2652 (m.group(1), [float(t) for t in m.group(2).split()])
2716 (m.group(1), [float(t) for t in m.group(2).split()])
2653 )
2717 )
2654 except FileNotFoundError:
2718 except FileNotFoundError:
2655 pass
2719 pass
2656 return times
2720 return times
2657
2721
2658
2722
2659 def savetimes(outputdir, result):
2723 def savetimes(outputdir, result):
2660 saved = dict(loadtimes(outputdir))
2724 saved = dict(loadtimes(outputdir))
2661 maxruns = 5
2725 maxruns = 5
2662 skipped = {str(t[0]) for t in result.skipped}
2726 skipped = {str(t[0]) for t in result.skipped}
2663 for tdata in result.times:
2727 for tdata in result.times:
2664 test, real = tdata[0], tdata[3]
2728 test, real = tdata[0], tdata[3]
2665 if test not in skipped:
2729 if test not in skipped:
2666 ts = saved.setdefault(test, [])
2730 ts = saved.setdefault(test, [])
2667 ts.append(real)
2731 ts.append(real)
2668 ts[:] = ts[-maxruns:]
2732 ts[:] = ts[-maxruns:]
2669
2733
2670 fd, tmpname = tempfile.mkstemp(
2734 fd, tmpname = tempfile.mkstemp(
2671 prefix=b'.testtimes', dir=outputdir, text=True
2735 prefix=b'.testtimes', dir=outputdir, text=True
2672 )
2736 )
2673 with os.fdopen(fd, 'w') as fp:
2737 with os.fdopen(fd, 'w') as fp:
2674 for name, ts in sorted(saved.items()):
2738 for name, ts in sorted(saved.items()):
2675 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2739 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2676 timepath = os.path.join(outputdir, b'.testtimes')
2740 timepath = os.path.join(outputdir, b'.testtimes')
2677 try:
2741 try:
2678 os.unlink(timepath)
2742 os.unlink(timepath)
2679 except OSError:
2743 except OSError:
2680 pass
2744 pass
2681 try:
2745 try:
2682 os.rename(tmpname, timepath)
2746 os.rename(tmpname, timepath)
2683 except OSError:
2747 except OSError:
2684 pass
2748 pass
2685
2749
2686
2750
2687 class TextTestRunner(unittest.TextTestRunner):
2751 class TextTestRunner(unittest.TextTestRunner):
2688 """Custom unittest test runner that uses appropriate settings."""
2752 """Custom unittest test runner that uses appropriate settings."""
2689
2753
2690 def __init__(self, runner, *args, **kwargs):
2754 def __init__(self, runner, *args, **kwargs):
2691 super(TextTestRunner, self).__init__(*args, **kwargs)
2755 super(TextTestRunner, self).__init__(*args, **kwargs)
2692
2756
2693 self._runner = runner
2757 self._runner = runner
2694
2758
2695 self._result = getTestResult()(
2759 self._result = getTestResult()(
2696 self._runner.options, self.stream, self.descriptions, self.verbosity
2760 self._runner.options, self.stream, self.descriptions, self.verbosity
2697 )
2761 )
2698
2762
2699 def listtests(self, test):
2763 def listtests(self, test):
2700 test = sorted(test, key=lambda t: t.name)
2764 test = sorted(test, key=lambda t: t.name)
2701
2765
2702 self._result.onStart(test)
2766 self._result.onStart(test)
2703
2767
2704 for t in test:
2768 for t in test:
2705 print(t.name)
2769 print(t.name)
2706 self._result.addSuccess(t)
2770 self._result.addSuccess(t)
2707
2771
2708 if self._runner.options.xunit:
2772 if self._runner.options.xunit:
2709 with open(self._runner.options.xunit, "wb") as xuf:
2773 with open(self._runner.options.xunit, "wb") as xuf:
2710 self._writexunit(self._result, xuf)
2774 self._writexunit(self._result, xuf)
2711
2775
2712 if self._runner.options.json:
2776 if self._runner.options.json:
2713 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2777 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2714 with open(jsonpath, 'w') as fp:
2778 with open(jsonpath, 'w') as fp:
2715 self._writejson(self._result, fp)
2779 self._writejson(self._result, fp)
2716
2780
2717 return self._result
2781 return self._result
2718
2782
2719 def run(self, test):
2783 def run(self, test):
2720 self._result.onStart(test)
2784 self._result.onStart(test)
2721 test(self._result)
2785 test(self._result)
2722
2786
2723 failed = len(self._result.failures)
2787 failed = len(self._result.failures)
2724 skipped = len(self._result.skipped)
2788 skipped = len(self._result.skipped)
2725 ignored = len(self._result.ignored)
2789 ignored = len(self._result.ignored)
2726
2790
2727 with iolock:
2791 with iolock:
2728 self.stream.writeln('')
2792 self.stream.writeln('')
2729
2793
2730 if not self._runner.options.noskips:
2794 if not self._runner.options.noskips:
2731 for test, msg in sorted(
2795 for test, msg in sorted(
2732 self._result.skipped, key=lambda s: s[0].name
2796 self._result.skipped, key=lambda s: s[0].name
2733 ):
2797 ):
2734 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2798 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2735 msg = highlightmsg(formatted, self._result.color)
2799 msg = highlightmsg(formatted, self._result.color)
2736 self.stream.write(msg)
2800 self.stream.write(msg)
2737 for test, msg in sorted(
2801 for test, msg in sorted(
2738 self._result.failures, key=lambda f: f[0].name
2802 self._result.failures, key=lambda f: f[0].name
2739 ):
2803 ):
2740 formatted = 'Failed %s: %s\n' % (test.name, msg)
2804 formatted = 'Failed %s: %s\n' % (test.name, msg)
2741 self.stream.write(highlightmsg(formatted, self._result.color))
2805 self.stream.write(highlightmsg(formatted, self._result.color))
2742 for test, msg in sorted(
2806 for test, msg in sorted(
2743 self._result.errors, key=lambda e: e[0].name
2807 self._result.errors, key=lambda e: e[0].name
2744 ):
2808 ):
2745 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2809 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2746
2810
2747 if self._runner.options.xunit:
2811 if self._runner.options.xunit:
2748 with open(self._runner.options.xunit, "wb") as xuf:
2812 with open(self._runner.options.xunit, "wb") as xuf:
2749 self._writexunit(self._result, xuf)
2813 self._writexunit(self._result, xuf)
2750
2814
2751 if self._runner.options.json:
2815 if self._runner.options.json:
2752 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2816 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2753 with open(jsonpath, 'w') as fp:
2817 with open(jsonpath, 'w') as fp:
2754 self._writejson(self._result, fp)
2818 self._writejson(self._result, fp)
2755
2819
2756 self._runner._checkhglib('Tested')
2820 self._runner._checkhglib('Tested')
2757
2821
2758 savetimes(self._runner._outputdir, self._result)
2822 savetimes(self._runner._outputdir, self._result)
2759
2823
2760 if failed and self._runner.options.known_good_rev:
2824 if failed and self._runner.options.known_good_rev:
2761 self._bisecttests(t for t, m in self._result.failures)
2825 self._bisecttests(t for t, m in self._result.failures)
2762 self.stream.writeln(
2826 self.stream.writeln(
2763 '# Ran %d tests, %d skipped, %d failed.'
2827 '# Ran %d tests, %d skipped, %d failed.'
2764 % (self._result.testsRun, skipped + ignored, failed)
2828 % (self._result.testsRun, skipped + ignored, failed)
2765 )
2829 )
2766 if failed:
2830 if failed:
2767 self.stream.writeln(
2831 self.stream.writeln(
2768 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2832 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2769 )
2833 )
2770 if self._runner.options.time:
2834 if self._runner.options.time:
2771 self.printtimes(self._result.times)
2835 self.printtimes(self._result.times)
2772
2836
2773 if self._runner.options.exceptions:
2837 if self._runner.options.exceptions:
2774 exceptions = aggregateexceptions(
2838 exceptions = aggregateexceptions(
2775 os.path.join(self._runner._outputdir, b'exceptions')
2839 os.path.join(self._runner._outputdir, b'exceptions')
2776 )
2840 )
2777
2841
2778 self.stream.writeln('Exceptions Report:')
2842 self.stream.writeln('Exceptions Report:')
2779 self.stream.writeln(
2843 self.stream.writeln(
2780 '%d total from %d frames'
2844 '%d total from %d frames'
2781 % (exceptions['total'], len(exceptions['exceptioncounts']))
2845 % (exceptions['total'], len(exceptions['exceptioncounts']))
2782 )
2846 )
2783 combined = exceptions['combined']
2847 combined = exceptions['combined']
2784 for key in sorted(combined, key=combined.get, reverse=True):
2848 for key in sorted(combined, key=combined.get, reverse=True):
2785 frame, line, exc = key
2849 frame, line, exc = key
2786 totalcount, testcount, leastcount, leasttest = combined[key]
2850 totalcount, testcount, leastcount, leasttest = combined[key]
2787
2851
2788 self.stream.writeln(
2852 self.stream.writeln(
2789 '%d (%d tests)\t%s: %s (%s - %d total)'
2853 '%d (%d tests)\t%s: %s (%s - %d total)'
2790 % (
2854 % (
2791 totalcount,
2855 totalcount,
2792 testcount,
2856 testcount,
2793 frame,
2857 frame,
2794 exc,
2858 exc,
2795 leasttest,
2859 leasttest,
2796 leastcount,
2860 leastcount,
2797 )
2861 )
2798 )
2862 )
2799
2863
2800 self.stream.flush()
2864 self.stream.flush()
2801
2865
2802 return self._result
2866 return self._result
2803
2867
2804 def _bisecttests(self, tests):
2868 def _bisecttests(self, tests):
2805 bisectcmd = ['hg', 'bisect']
2869 bisectcmd = ['hg', 'bisect']
2806 bisectrepo = self._runner.options.bisect_repo
2870 bisectrepo = self._runner.options.bisect_repo
2807 if bisectrepo:
2871 if bisectrepo:
2808 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2872 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2809
2873
2810 def pread(args):
2874 def pread(args):
2811 env = os.environ.copy()
2875 env = os.environ.copy()
2812 env['HGPLAIN'] = '1'
2876 env['HGPLAIN'] = '1'
2813 p = subprocess.Popen(
2877 p = subprocess.Popen(
2814 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2878 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2815 )
2879 )
2816 data = p.stdout.read()
2880 data = p.stdout.read()
2817 p.wait()
2881 p.wait()
2818 return data
2882 return data
2819
2883
2820 for test in tests:
2884 for test in tests:
2821 pread(bisectcmd + ['--reset']),
2885 pread(bisectcmd + ['--reset']),
2822 pread(bisectcmd + ['--bad', '.'])
2886 pread(bisectcmd + ['--bad', '.'])
2823 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2887 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2824 # TODO: we probably need to forward more options
2888 # TODO: we probably need to forward more options
2825 # that alter hg's behavior inside the tests.
2889 # that alter hg's behavior inside the tests.
2826 opts = ''
2890 opts = ''
2827 withhg = self._runner.options.with_hg
2891 withhg = self._runner.options.with_hg
2828 if withhg:
2892 if withhg:
2829 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2893 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2830 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2894 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2831 data = pread(bisectcmd + ['--command', rtc])
2895 data = pread(bisectcmd + ['--command', rtc])
2832 m = re.search(
2896 m = re.search(
2833 (
2897 (
2834 br'\nThe first (?P<goodbad>bad|good) revision '
2898 br'\nThe first (?P<goodbad>bad|good) revision '
2835 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2899 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2836 br'summary: +(?P<summary>[^\n]+)\n'
2900 br'summary: +(?P<summary>[^\n]+)\n'
2837 ),
2901 ),
2838 data,
2902 data,
2839 (re.MULTILINE | re.DOTALL),
2903 (re.MULTILINE | re.DOTALL),
2840 )
2904 )
2841 if m is None:
2905 if m is None:
2842 self.stream.writeln(
2906 self.stream.writeln(
2843 'Failed to identify failure point for %s' % test
2907 'Failed to identify failure point for %s' % test
2844 )
2908 )
2845 continue
2909 continue
2846 dat = m.groupdict()
2910 dat = m.groupdict()
2847 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2911 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2848 self.stream.writeln(
2912 self.stream.writeln(
2849 '%s %s by %s (%s)'
2913 '%s %s by %s (%s)'
2850 % (
2914 % (
2851 test,
2915 test,
2852 verb,
2916 verb,
2853 dat['node'].decode('ascii'),
2917 dat['node'].decode('ascii'),
2854 dat['summary'].decode('utf8', 'ignore'),
2918 dat['summary'].decode('utf8', 'ignore'),
2855 )
2919 )
2856 )
2920 )
2857
2921
2858 def printtimes(self, times):
2922 def printtimes(self, times):
2859 # iolock held by run
2923 # iolock held by run
2860 self.stream.writeln('# Producing time report')
2924 self.stream.writeln('# Producing time report')
2861 times.sort(key=lambda t: (t[3]))
2925 times.sort(key=lambda t: (t[3]))
2862 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2926 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2863 self.stream.writeln(
2927 self.stream.writeln(
2864 '%-7s %-7s %-7s %-7s %-7s %s'
2928 '%-7s %-7s %-7s %-7s %-7s %s'
2865 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2929 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2866 )
2930 )
2867 for tdata in times:
2931 for tdata in times:
2868 test = tdata[0]
2932 test = tdata[0]
2869 cuser, csys, real, start, end = tdata[1:6]
2933 cuser, csys, real, start, end = tdata[1:6]
2870 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2934 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2871
2935
2872 @staticmethod
2936 @staticmethod
2873 def _writexunit(result, outf):
2937 def _writexunit(result, outf):
2874 # See http://llg.cubic.org/docs/junit/ for a reference.
2938 # See http://llg.cubic.org/docs/junit/ for a reference.
2875 timesd = {t[0]: t[3] for t in result.times}
2939 timesd = {t[0]: t[3] for t in result.times}
2876 doc = minidom.Document()
2940 doc = minidom.Document()
2877 s = doc.createElement('testsuite')
2941 s = doc.createElement('testsuite')
2878 s.setAttribute('errors', "0") # TODO
2942 s.setAttribute('errors', "0") # TODO
2879 s.setAttribute('failures', str(len(result.failures)))
2943 s.setAttribute('failures', str(len(result.failures)))
2880 s.setAttribute('name', 'run-tests')
2944 s.setAttribute('name', 'run-tests')
2881 s.setAttribute(
2945 s.setAttribute(
2882 'skipped', str(len(result.skipped) + len(result.ignored))
2946 'skipped', str(len(result.skipped) + len(result.ignored))
2883 )
2947 )
2884 s.setAttribute('tests', str(result.testsRun))
2948 s.setAttribute('tests', str(result.testsRun))
2885 doc.appendChild(s)
2949 doc.appendChild(s)
2886 for tc in result.successes:
2950 for tc in result.successes:
2887 t = doc.createElement('testcase')
2951 t = doc.createElement('testcase')
2888 t.setAttribute('name', tc.name)
2952 t.setAttribute('name', tc.name)
2889 tctime = timesd.get(tc.name)
2953 tctime = timesd.get(tc.name)
2890 if tctime is not None:
2954 if tctime is not None:
2891 t.setAttribute('time', '%.3f' % tctime)
2955 t.setAttribute('time', '%.3f' % tctime)
2892 s.appendChild(t)
2956 s.appendChild(t)
2893 for tc, err in sorted(result.faildata.items()):
2957 for tc, err in sorted(result.faildata.items()):
2894 t = doc.createElement('testcase')
2958 t = doc.createElement('testcase')
2895 t.setAttribute('name', tc)
2959 t.setAttribute('name', tc)
2896 tctime = timesd.get(tc)
2960 tctime = timesd.get(tc)
2897 if tctime is not None:
2961 if tctime is not None:
2898 t.setAttribute('time', '%.3f' % tctime)
2962 t.setAttribute('time', '%.3f' % tctime)
2899 # createCDATASection expects a unicode or it will
2963 # createCDATASection expects a unicode or it will
2900 # convert using default conversion rules, which will
2964 # convert using default conversion rules, which will
2901 # fail if string isn't ASCII.
2965 # fail if string isn't ASCII.
2902 err = cdatasafe(err).decode('utf-8', 'replace')
2966 err = cdatasafe(err).decode('utf-8', 'replace')
2903 cd = doc.createCDATASection(err)
2967 cd = doc.createCDATASection(err)
2904 # Use 'failure' here instead of 'error' to match errors = 0,
2968 # Use 'failure' here instead of 'error' to match errors = 0,
2905 # failures = len(result.failures) in the testsuite element.
2969 # failures = len(result.failures) in the testsuite element.
2906 failelem = doc.createElement('failure')
2970 failelem = doc.createElement('failure')
2907 failelem.setAttribute('message', 'output changed')
2971 failelem.setAttribute('message', 'output changed')
2908 failelem.setAttribute('type', 'output-mismatch')
2972 failelem.setAttribute('type', 'output-mismatch')
2909 failelem.appendChild(cd)
2973 failelem.appendChild(cd)
2910 t.appendChild(failelem)
2974 t.appendChild(failelem)
2911 s.appendChild(t)
2975 s.appendChild(t)
2912 for tc, message in result.skipped:
2976 for tc, message in result.skipped:
2913 # According to the schema, 'skipped' has no attributes. So store
2977 # According to the schema, 'skipped' has no attributes. So store
2914 # the skip message as a text node instead.
2978 # the skip message as a text node instead.
2915 t = doc.createElement('testcase')
2979 t = doc.createElement('testcase')
2916 t.setAttribute('name', tc.name)
2980 t.setAttribute('name', tc.name)
2917 binmessage = message.encode('utf-8')
2981 binmessage = message.encode('utf-8')
2918 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2982 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2919 cd = doc.createCDATASection(message)
2983 cd = doc.createCDATASection(message)
2920 skipelem = doc.createElement('skipped')
2984 skipelem = doc.createElement('skipped')
2921 skipelem.appendChild(cd)
2985 skipelem.appendChild(cd)
2922 t.appendChild(skipelem)
2986 t.appendChild(skipelem)
2923 s.appendChild(t)
2987 s.appendChild(t)
2924 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2988 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2925
2989
2926 @staticmethod
2990 @staticmethod
2927 def _writejson(result, outf):
2991 def _writejson(result, outf):
2928 timesd = {}
2992 timesd = {}
2929 for tdata in result.times:
2993 for tdata in result.times:
2930 test = tdata[0]
2994 test = tdata[0]
2931 timesd[test] = tdata[1:]
2995 timesd[test] = tdata[1:]
2932
2996
2933 outcome = {}
2997 outcome = {}
2934 groups = [
2998 groups = [
2935 ('success', ((tc, None) for tc in result.successes)),
2999 ('success', ((tc, None) for tc in result.successes)),
2936 ('failure', result.failures),
3000 ('failure', result.failures),
2937 ('skip', result.skipped),
3001 ('skip', result.skipped),
2938 ]
3002 ]
2939 for res, testcases in groups:
3003 for res, testcases in groups:
2940 for tc, __ in testcases:
3004 for tc, __ in testcases:
2941 if tc.name in timesd:
3005 if tc.name in timesd:
2942 diff = result.faildata.get(tc.name, b'')
3006 diff = result.faildata.get(tc.name, b'')
2943 try:
3007 try:
2944 diff = diff.decode('unicode_escape')
3008 diff = diff.decode('unicode_escape')
2945 except UnicodeDecodeError as e:
3009 except UnicodeDecodeError as e:
2946 diff = '%r decoding diff, sorry' % e
3010 diff = '%r decoding diff, sorry' % e
2947 tres = {
3011 tres = {
2948 'result': res,
3012 'result': res,
2949 'time': ('%0.3f' % timesd[tc.name][2]),
3013 'time': ('%0.3f' % timesd[tc.name][2]),
2950 'cuser': ('%0.3f' % timesd[tc.name][0]),
3014 'cuser': ('%0.3f' % timesd[tc.name][0]),
2951 'csys': ('%0.3f' % timesd[tc.name][1]),
3015 'csys': ('%0.3f' % timesd[tc.name][1]),
2952 'start': ('%0.3f' % timesd[tc.name][3]),
3016 'start': ('%0.3f' % timesd[tc.name][3]),
2953 'end': ('%0.3f' % timesd[tc.name][4]),
3017 'end': ('%0.3f' % timesd[tc.name][4]),
2954 'diff': diff,
3018 'diff': diff,
2955 }
3019 }
2956 else:
3020 else:
2957 # blacklisted test
3021 # blacklisted test
2958 tres = {'result': res}
3022 tres = {'result': res}
2959
3023
2960 outcome[tc.name] = tres
3024 outcome[tc.name] = tres
2961 jsonout = json.dumps(
3025 jsonout = json.dumps(
2962 outcome, sort_keys=True, indent=4, separators=(',', ': ')
3026 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2963 )
3027 )
2964 outf.writelines(("testreport =", jsonout))
3028 outf.writelines(("testreport =", jsonout))
2965
3029
2966
3030
2967 def sorttests(testdescs, previoustimes, shuffle=False):
3031 def sorttests(testdescs, previoustimes, shuffle=False):
2968 """Do an in-place sort of tests."""
3032 """Do an in-place sort of tests."""
2969 if shuffle:
3033 if shuffle:
2970 random.shuffle(testdescs)
3034 random.shuffle(testdescs)
2971 return
3035 return
2972
3036
2973 if previoustimes:
3037 if previoustimes:
2974
3038
2975 def sortkey(f):
3039 def sortkey(f):
2976 f = f['path']
3040 f = f['path']
2977 if f in previoustimes:
3041 if f in previoustimes:
2978 # Use most recent time as estimate
3042 # Use most recent time as estimate
2979 return -(previoustimes[f][-1])
3043 return -(previoustimes[f][-1])
2980 else:
3044 else:
2981 # Default to a rather arbitrary value of 1 second for new tests
3045 # Default to a rather arbitrary value of 1 second for new tests
2982 return -1.0
3046 return -1.0
2983
3047
2984 else:
3048 else:
2985 # keywords for slow tests
3049 # keywords for slow tests
2986 slow = {
3050 slow = {
2987 b'svn': 10,
3051 b'svn': 10,
2988 b'cvs': 10,
3052 b'cvs': 10,
2989 b'hghave': 10,
3053 b'hghave': 10,
2990 b'largefiles-update': 10,
3054 b'largefiles-update': 10,
2991 b'run-tests': 10,
3055 b'run-tests': 10,
2992 b'corruption': 10,
3056 b'corruption': 10,
2993 b'race': 10,
3057 b'race': 10,
2994 b'i18n': 10,
3058 b'i18n': 10,
2995 b'check': 100,
3059 b'check': 100,
2996 b'gendoc': 100,
3060 b'gendoc': 100,
2997 b'contrib-perf': 200,
3061 b'contrib-perf': 200,
2998 b'merge-combination': 100,
3062 b'merge-combination': 100,
2999 }
3063 }
3000 perf = {}
3064 perf = {}
3001
3065
3002 def sortkey(f):
3066 def sortkey(f):
3003 # run largest tests first, as they tend to take the longest
3067 # run largest tests first, as they tend to take the longest
3004 f = f['path']
3068 f = f['path']
3005 try:
3069 try:
3006 return perf[f]
3070 return perf[f]
3007 except KeyError:
3071 except KeyError:
3008 try:
3072 try:
3009 val = -os.stat(f).st_size
3073 val = -os.stat(f).st_size
3010 except FileNotFoundError:
3074 except FileNotFoundError:
3011 perf[f] = -1e9 # file does not exist, tell early
3075 perf[f] = -1e9 # file does not exist, tell early
3012 return -1e9
3076 return -1e9
3013 for kw, mul in slow.items():
3077 for kw, mul in slow.items():
3014 if kw in f:
3078 if kw in f:
3015 val *= mul
3079 val *= mul
3016 if f.endswith(b'.py'):
3080 if f.endswith(b'.py'):
3017 val /= 10.0
3081 val /= 10.0
3018 perf[f] = val / 1000.0
3082 perf[f] = val / 1000.0
3019 return perf[f]
3083 return perf[f]
3020
3084
3021 testdescs.sort(key=sortkey)
3085 testdescs.sort(key=sortkey)
3022
3086
3023
3087
3024 class TestRunner:
3088 class TestRunner:
3025 """Holds context for executing tests.
3089 """Holds context for executing tests.
3026
3090
3027 Tests rely on a lot of state. This object holds it for them.
3091 Tests rely on a lot of state. This object holds it for them.
3028 """
3092 """
3029
3093
3030 # Programs required to run tests.
3094 # Programs required to run tests.
3031 REQUIREDTOOLS = [
3095 REQUIREDTOOLS = [
3032 b'diff',
3096 b'diff',
3033 b'grep',
3097 b'grep',
3034 b'unzip',
3098 b'unzip',
3035 b'gunzip',
3099 b'gunzip',
3036 b'bunzip2',
3100 b'bunzip2',
3037 b'sed',
3101 b'sed',
3038 ]
3102 ]
3039
3103
3040 # Maps file extensions to test class.
3104 # Maps file extensions to test class.
3041 TESTTYPES = [
3105 TESTTYPES = [
3042 (b'.py', PythonTest),
3106 (b'.py', PythonTest),
3043 (b'.t', TTest),
3107 (b'.t', TTest),
3044 ]
3108 ]
3045
3109
3046 def __init__(self):
3110 def __init__(self):
3047 self.options = None
3111 self.options = None
3048 self._hgroot = None
3112 self._hgroot = None
3049 self._testdir = None
3113 self._testdir = None
3050 self._outputdir = None
3114 self._outputdir = None
3051 self._hgtmp = None
3115 self._hgtmp = None
3052 self._installdir = None
3116 self._installdir = None
3053 self._bindir = None
3117 self._bindir = None
3054 # a place for run-tests.py to generate executable it needs
3118 # a place for run-tests.py to generate executable it needs
3055 self._custom_bin_dir = None
3119 self._custom_bin_dir = None
3056 self._pythondir = None
3120 self._pythondir = None
3057 # True if we had to infer the pythondir from --with-hg
3121 # True if we had to infer the pythondir from --with-hg
3058 self._pythondir_inferred = False
3122 self._pythondir_inferred = False
3059 self._coveragefile = None
3123 self._coveragefile = None
3060 self._createdfiles = []
3124 self._createdfiles = []
3061 self._hgcommand = None
3125 self._hgcommand = None
3062 self._hgpath = None
3126 self._hgpath = None
3063 self._portoffset = 0
3127 self._portoffset = 0
3064 self._ports = {}
3128 self._ports = {}
3065
3129
3066 def run(self, args, parser=None):
3130 def run(self, args, parser=None):
3067 """Run the test suite."""
3131 """Run the test suite."""
3068 oldmask = os.umask(0o22)
3132 oldmask = os.umask(0o22)
3069 try:
3133 try:
3070 parser = parser or getparser()
3134 parser = parser or getparser()
3071 options = parseargs(args, parser)
3135 options = parseargs(args, parser)
3072 tests = [_sys2bytes(a) for a in options.tests]
3136 tests = [_sys2bytes(a) for a in options.tests]
3073 if options.test_list is not None:
3137 if options.test_list is not None:
3074 for listfile in options.test_list:
3138 for listfile in options.test_list:
3075 with open(listfile, 'rb') as f:
3139 with open(listfile, 'rb') as f:
3076 tests.extend(t for t in f.read().splitlines() if t)
3140 tests.extend(t for t in f.read().splitlines() if t)
3077 self.options = options
3141 self.options = options
3078
3142
3079 self._checktools()
3143 self._checktools()
3080 testdescs = self.findtests(tests)
3144 testdescs = self.findtests(tests)
3081 if options.profile_runner:
3145 if options.profile_runner:
3082 import statprof
3146 import statprof
3083
3147
3084 statprof.start()
3148 statprof.start()
3085 result = self._run(testdescs)
3149 result = self._run(testdescs)
3086 if options.profile_runner:
3150 if options.profile_runner:
3087 statprof.stop()
3151 statprof.stop()
3088 statprof.display()
3152 statprof.display()
3089 return result
3153 return result
3090
3154
3091 finally:
3155 finally:
3092 os.umask(oldmask)
3156 os.umask(oldmask)
3093
3157
3094 def _run(self, testdescs):
3158 def _run(self, testdescs):
3095 testdir = getcwdb()
3159 testdir = getcwdb()
3096 # assume all tests in same folder for now
3160 # assume all tests in same folder for now
3097 if testdescs:
3161 if testdescs:
3098 pathname = os.path.dirname(testdescs[0]['path'])
3162 pathname = os.path.dirname(testdescs[0]['path'])
3099 if pathname:
3163 if pathname:
3100 testdir = os.path.join(testdir, pathname)
3164 testdir = os.path.join(testdir, pathname)
3101 self._testdir = osenvironb[b'TESTDIR'] = testdir
3165 self._testdir = osenvironb[b'TESTDIR'] = testdir
3102 osenvironb[b'TESTDIR_FORWARD_SLASH'] = osenvironb[b'TESTDIR'].replace(
3166 osenvironb[b'TESTDIR_FORWARD_SLASH'] = osenvironb[b'TESTDIR'].replace(
3103 os.sep.encode('ascii'), b'/'
3167 os.sep.encode('ascii'), b'/'
3104 )
3168 )
3105
3169
3106 if self.options.outputdir:
3170 if self.options.outputdir:
3107 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3171 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3108 else:
3172 else:
3109 self._outputdir = getcwdb()
3173 self._outputdir = getcwdb()
3110 if testdescs and pathname:
3174 if testdescs and pathname:
3111 self._outputdir = os.path.join(self._outputdir, pathname)
3175 self._outputdir = os.path.join(self._outputdir, pathname)
3112 previoustimes = {}
3176 previoustimes = {}
3113 if self.options.order_by_runtime:
3177 if self.options.order_by_runtime:
3114 previoustimes = dict(loadtimes(self._outputdir))
3178 previoustimes = dict(loadtimes(self._outputdir))
3115 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3179 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3116
3180
3117 if 'PYTHONHASHSEED' not in os.environ:
3181 if 'PYTHONHASHSEED' not in os.environ:
3118 # use a random python hash seed all the time
3182 # use a random python hash seed all the time
3119 # we do the randomness ourself to know what seed is used
3183 # we do the randomness ourself to know what seed is used
3120 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3184 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3121
3185
3122 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3186 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3123 # by default, causing thrashing on high-cpu-count systems.
3187 # by default, causing thrashing on high-cpu-count systems.
3124 # Setting its limit to 3 during tests should still let us uncover
3188 # Setting its limit to 3 during tests should still let us uncover
3125 # multi-threading bugs while keeping the thrashing reasonable.
3189 # multi-threading bugs while keeping the thrashing reasonable.
3126 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3190 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3127
3191
3128 if self.options.tmpdir:
3192 if self.options.tmpdir:
3129 self.options.keep_tmpdir = True
3193 self.options.keep_tmpdir = True
3130 tmpdir = _sys2bytes(self.options.tmpdir)
3194 tmpdir = _sys2bytes(self.options.tmpdir)
3131 if os.path.exists(tmpdir):
3195 if os.path.exists(tmpdir):
3132 # Meaning of tmpdir has changed since 1.3: we used to create
3196 # Meaning of tmpdir has changed since 1.3: we used to create
3133 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3197 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3134 # tmpdir already exists.
3198 # tmpdir already exists.
3135 print("error: temp dir %r already exists" % tmpdir)
3199 print("error: temp dir %r already exists" % tmpdir)
3136 return 1
3200 return 1
3137
3201
3138 os.makedirs(tmpdir)
3202 os.makedirs(tmpdir)
3139 else:
3203 else:
3140 d = None
3204 d = None
3141 if WINDOWS:
3205 if WINDOWS:
3142 # without this, we get the default temp dir location, but
3206 # without this, we get the default temp dir location, but
3143 # in all lowercase, which causes troubles with paths (issue3490)
3207 # in all lowercase, which causes troubles with paths (issue3490)
3144 d = osenvironb.get(b'TMP', None)
3208 d = osenvironb.get(b'TMP', None)
3145 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3209 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3146
3210
3147 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3211 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3148
3212
3149 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3213 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3150 os.makedirs(self._custom_bin_dir)
3214 os.makedirs(self._custom_bin_dir)
3151
3215
3152 # detect and enforce an alternative way to specify rust extension usage
3216 # detect and enforce an alternative way to specify rust extension usage
3153 if (
3217 if (
3154 not (self.options.pure or self.options.rust or self.options.no_rust)
3218 not (self.options.pure or self.options.rust or self.options.no_rust)
3155 and os.environ.get("HGWITHRUSTEXT") == "cpython"
3219 and os.environ.get("HGWITHRUSTEXT") == "cpython"
3156 ):
3220 ):
3157 self.options.rust = True
3221 self.options.rust = True
3158
3222
3159 if self.options.with_hg:
3223 if self.options.with_hg:
3160 self._installdir = None
3224 self._installdir = None
3161 whg = self.options.with_hg
3225 whg = self.options.with_hg
3162 self._bindir = os.path.dirname(os.path.realpath(whg))
3226 self._bindir = os.path.dirname(os.path.realpath(whg))
3163 assert isinstance(self._bindir, bytes)
3227 assert isinstance(self._bindir, bytes)
3164 self._hgcommand = os.path.basename(whg)
3228 self._hgcommand = os.path.basename(whg)
3165
3229
3166 normbin = os.path.normpath(os.path.abspath(whg))
3230 normbin = os.path.normpath(os.path.abspath(whg))
3167 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3231 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3168
3232
3169 # Other Python scripts in the test harness need to
3233 # Other Python scripts in the test harness need to
3170 # `import mercurial`. If `hg` is a Python script, we assume
3234 # `import mercurial`. If `hg` is a Python script, we assume
3171 # the Mercurial modules are relative to its path and tell the tests
3235 # the Mercurial modules are relative to its path and tell the tests
3172 # to load Python modules from its directory.
3236 # to load Python modules from its directory.
3173 with open(whg, 'rb') as fh:
3237 with open(whg, 'rb') as fh:
3174 initial = fh.read(1024)
3238 initial = fh.read(1024)
3175
3239
3176 if re.match(b'#!.*python', initial):
3240 if re.match(b'#!.*python', initial):
3177 self._pythondir = self._bindir
3241 self._pythondir = self._bindir
3178 # If it looks like our in-repo Rust binary, use the source root.
3242 # If it looks like our in-repo Rust binary, use the source root.
3179 # This is a bit hacky. But rhg is still not supported outside the
3243 # This is a bit hacky. But rhg is still not supported outside the
3180 # source directory. So until it is, do the simple thing.
3244 # source directory. So until it is, do the simple thing.
3181 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3245 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3182 self._pythondir = os.path.dirname(self._testdir)
3246 self._pythondir = os.path.dirname(self._testdir)
3183 # Fall back to the legacy behavior.
3247 # Fall back to the legacy behavior.
3184 else:
3248 else:
3185 self._pythondir = self._bindir
3249 self._pythondir = self._bindir
3186 self._pythondir_inferred = True
3250 self._pythondir_inferred = True
3187
3251
3188 else:
3252 else:
3189 self._installdir = os.path.join(self._hgtmp, b"install")
3253 self._installdir = os.path.join(self._hgtmp, b"install")
3190 self._bindir = os.path.join(self._installdir, b"bin")
3254 self._bindir = os.path.join(self._installdir, b"bin")
3191 self._hgcommand = b'hg'
3255 self._hgcommand = b'hg'
3192 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3256 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3193
3257
3194 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3258 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3195 # a python script and feed it to python.exe. Legacy stdio is force
3259 # a python script and feed it to python.exe. Legacy stdio is force
3196 # enabled by hg.exe, and this is a more realistic way to launch hg
3260 # enabled by hg.exe, and this is a more realistic way to launch hg
3197 # anyway.
3261 # anyway.
3198 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3262 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3199 self._hgcommand += b'.exe'
3263 self._hgcommand += b'.exe'
3200
3264
3201 real_hg = os.path.join(self._bindir, self._hgcommand)
3265 real_hg = os.path.join(self._bindir, self._hgcommand)
3202 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3266 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3203 # set CHGHG, then replace "hg" command by "chg"
3267 # set CHGHG, then replace "hg" command by "chg"
3204 chgbindir = self._bindir
3268 chgbindir = self._bindir
3205 if self.options.chg or self.options.with_chg:
3269 if self.options.chg or self.options.with_chg:
3206 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3270 osenvironb[b'CHG_INSTALLED_AS_HG'] = b'1'
3207 osenvironb[b'CHGHG'] = real_hg
3271 osenvironb[b'CHGHG'] = real_hg
3208 else:
3272 else:
3209 # drop flag for hghave
3273 # drop flag for hghave
3210 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3274 osenvironb.pop(b'CHG_INSTALLED_AS_HG', None)
3211 if self.options.chg:
3275 if self.options.chg:
3212 self._hgcommand = b'chg'
3276 self._hgcommand = b'chg'
3213 elif self.options.with_chg:
3277 elif self.options.with_chg:
3214 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3278 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3215 self._hgcommand = os.path.basename(self.options.with_chg)
3279 self._hgcommand = os.path.basename(self.options.with_chg)
3216
3280
3217 # configure fallback and replace "hg" command by "rhg"
3281 # configure fallback and replace "hg" command by "rhg"
3218 rhgbindir = self._bindir
3282 rhgbindir = self._bindir
3219 if self.options.rhg or self.options.with_rhg:
3283 if self.options.rhg or self.options.with_rhg:
3220 # Affects hghave.py
3284 # Affects hghave.py
3221 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3285 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3222 # Affects configuration. Alternatives would be setting configuration through
3286 # Affects configuration. Alternatives would be setting configuration through
3223 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3287 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3224 # `--config` but that disrupts tests that print command lines and check expected
3288 # `--config` but that disrupts tests that print command lines and check expected
3225 # output.
3289 # output.
3226 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3290 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3227 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3291 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3228 else:
3292 else:
3229 # drop flag for hghave
3293 # drop flag for hghave
3230 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3294 osenvironb.pop(b'RHG_INSTALLED_AS_HG', None)
3231 if self.options.rhg:
3295 if self.options.rhg:
3232 self._hgcommand = b'rhg'
3296 self._hgcommand = b'rhg'
3233 elif self.options.with_rhg:
3297 elif self.options.with_rhg:
3234 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3298 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3235 self._hgcommand = os.path.basename(self.options.with_rhg)
3299 self._hgcommand = os.path.basename(self.options.with_rhg)
3236
3300
3237 if self.options.pyoxidized:
3301 if self.options.pyoxidized:
3238 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3302 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
3239 reporootdir = os.path.dirname(testdir)
3303 reporootdir = os.path.dirname(testdir)
3240 # XXX we should ideally install stuff instead of using the local build
3304 # XXX we should ideally install stuff instead of using the local build
3241
3305
3242 exe = b'hg'
3306 exe = b'hg'
3243 triple = b''
3307 triple = b''
3244
3308
3245 if WINDOWS:
3309 if WINDOWS:
3246 triple = b'x86_64-pc-windows-msvc'
3310 triple = b'x86_64-pc-windows-msvc'
3247 exe = b'hg.exe'
3311 exe = b'hg.exe'
3248 elif MACOS:
3312 elif MACOS:
3249 # TODO: support Apple silicon too
3313 # TODO: support Apple silicon too
3250 triple = b'x86_64-apple-darwin'
3314 triple = b'x86_64-apple-darwin'
3251
3315
3252 bin_path = b'build/pyoxidizer/%s/release/app/%s' % (triple, exe)
3316 bin_path = b'build/pyoxidizer/%s/release/app/%s' % (triple, exe)
3253 full_path = os.path.join(reporootdir, bin_path)
3317 full_path = os.path.join(reporootdir, bin_path)
3254 self._hgcommand = full_path
3318 self._hgcommand = full_path
3255 # Affects hghave.py
3319 # Affects hghave.py
3256 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3320 osenvironb[b'PYOXIDIZED_INSTALLED_AS_HG'] = b'1'
3257 else:
3321 else:
3258 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3322 osenvironb.pop(b'PYOXIDIZED_INSTALLED_AS_HG', None)
3259
3323
3260 osenvironb[b"BINDIR"] = self._bindir
3324 osenvironb[b"BINDIR"] = self._bindir
3261 osenvironb[b"PYTHON"] = PYTHON
3325 osenvironb[b"PYTHON"] = PYTHON
3262
3326
3263 fileb = _sys2bytes(__file__)
3327 fileb = _sys2bytes(__file__)
3264 runtestdir = os.path.abspath(os.path.dirname(fileb))
3328 runtestdir = os.path.abspath(os.path.dirname(fileb))
3265 osenvironb[b'RUNTESTDIR'] = runtestdir
3329 osenvironb[b'RUNTESTDIR'] = runtestdir
3266 osenvironb[b'RUNTESTDIR_FORWARD_SLASH'] = runtestdir.replace(
3330 osenvironb[b'RUNTESTDIR_FORWARD_SLASH'] = runtestdir.replace(
3267 os.sep.encode('ascii'), b'/'
3331 os.sep.encode('ascii'), b'/'
3268 )
3332 )
3269 sepb = _sys2bytes(os.pathsep)
3333 sepb = _sys2bytes(os.pathsep)
3270 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3334 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3271 if os.path.islink(__file__):
3335 if os.path.islink(__file__):
3272 # test helper will likely be at the end of the symlink
3336 # test helper will likely be at the end of the symlink
3273 realfile = os.path.realpath(fileb)
3337 realfile = os.path.realpath(fileb)
3274 realdir = os.path.abspath(os.path.dirname(realfile))
3338 realdir = os.path.abspath(os.path.dirname(realfile))
3275 path.insert(2, realdir)
3339 path.insert(2, realdir)
3276 if chgbindir != self._bindir:
3340 if chgbindir != self._bindir:
3277 path.insert(1, chgbindir)
3341 path.insert(1, chgbindir)
3278 if rhgbindir != self._bindir:
3342 if rhgbindir != self._bindir:
3279 path.insert(1, rhgbindir)
3343 path.insert(1, rhgbindir)
3280 if self._testdir != runtestdir:
3344 if self._testdir != runtestdir:
3281 path = [self._testdir] + path
3345 path = [self._testdir] + path
3282 path = [self._custom_bin_dir] + path
3346 path = [self._custom_bin_dir] + path
3283 osenvironb[b"PATH"] = sepb.join(path)
3347 osenvironb[b"PATH"] = sepb.join(path)
3284
3348
3285 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3349 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3286 # can run .../tests/run-tests.py test-foo where test-foo
3350 # can run .../tests/run-tests.py test-foo where test-foo
3287 # adds an extension to HGRC. Also include run-test.py directory to
3351 # adds an extension to HGRC. Also include run-test.py directory to
3288 # import modules like heredoctest.
3352 # import modules like heredoctest.
3289 pypath = [self._pythondir, self._testdir, runtestdir]
3353 pypath = [self._pythondir, self._testdir, runtestdir]
3290
3354
3291 # Setting PYTHONPATH with an activated venv causes the modules installed
3355 # Setting PYTHONPATH with an activated venv causes the modules installed
3292 # in it to be ignored. Therefore, include the related paths in sys.path
3356 # in it to be ignored. Therefore, include the related paths in sys.path
3293 # in PYTHONPATH.
3357 # in PYTHONPATH.
3294 virtual_env = osenvironb.get(b"VIRTUAL_ENV")
3358 virtual_env = osenvironb.get(b"VIRTUAL_ENV")
3295 if virtual_env:
3359 if virtual_env:
3296 virtual_env = os.path.join(virtual_env, b'')
3360 virtual_env = os.path.join(virtual_env, b'')
3297 for p in sys.path:
3361 for p in sys.path:
3298 p = _sys2bytes(p)
3362 p = _sys2bytes(p)
3299 if p.startswith(virtual_env):
3363 if p.startswith(virtual_env):
3300 pypath.append(p)
3364 pypath.append(p)
3301
3365
3302 # We have to augment PYTHONPATH, rather than simply replacing
3366 # We have to augment PYTHONPATH, rather than simply replacing
3303 # it, in case external libraries are only available via current
3367 # it, in case external libraries are only available via current
3304 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3368 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3305 # are in /opt/subversion.)
3369 # are in /opt/subversion.)
3306 oldpypath = osenvironb.get(IMPL_PATH)
3370 oldpypath = osenvironb.get(IMPL_PATH)
3307 if oldpypath:
3371 if oldpypath:
3308 pypath.append(oldpypath)
3372 pypath.append(oldpypath)
3309 osenvironb[IMPL_PATH] = sepb.join(pypath)
3373 osenvironb[IMPL_PATH] = sepb.join(pypath)
3310
3374
3311 if self.options.pure:
3375 if self.options.pure:
3312 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3376 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3313 os.environ["HGMODULEPOLICY"] = "py"
3377 os.environ["HGMODULEPOLICY"] = "py"
3314 if self.options.rust:
3378 if self.options.rust:
3315 os.environ["HGMODULEPOLICY"] = "rust+c"
3379 os.environ["HGMODULEPOLICY"] = "rust+c"
3316 if self.options.no_rust:
3380 if self.options.no_rust:
3317 current_policy = os.environ.get("HGMODULEPOLICY", "")
3381 current_policy = os.environ.get("HGMODULEPOLICY", "")
3318 if current_policy.startswith("rust+"):
3382 if current_policy.startswith("rust+"):
3319 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3383 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3320 os.environ.pop("HGWITHRUSTEXT", None)
3384 os.environ.pop("HGWITHRUSTEXT", None)
3321
3385
3322 if self.options.allow_slow_tests:
3386 if self.options.allow_slow_tests:
3323 os.environ["HGTEST_SLOW"] = "slow"
3387 os.environ["HGTEST_SLOW"] = "slow"
3324 elif 'HGTEST_SLOW' in os.environ:
3388 elif 'HGTEST_SLOW' in os.environ:
3325 del os.environ['HGTEST_SLOW']
3389 del os.environ['HGTEST_SLOW']
3326
3390
3327 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3391 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3328
3392
3329 if self.options.exceptions:
3393 if self.options.exceptions:
3330 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3394 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3331 try:
3395 try:
3332 os.makedirs(exceptionsdir)
3396 os.makedirs(exceptionsdir)
3333 except FileExistsError:
3397 except FileExistsError:
3334 pass
3398 pass
3335
3399
3336 # Remove all existing exception reports.
3400 # Remove all existing exception reports.
3337 for f in os.listdir(exceptionsdir):
3401 for f in os.listdir(exceptionsdir):
3338 os.unlink(os.path.join(exceptionsdir, f))
3402 os.unlink(os.path.join(exceptionsdir, f))
3339
3403
3340 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3404 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3341 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3405 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3342 self.options.extra_config_opt.append(
3406 self.options.extra_config_opt.append(
3343 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3407 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3344 )
3408 )
3345
3409
3346 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3410 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3347 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3411 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3348 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3412 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3349 vlog("# Using PATH", os.environ["PATH"])
3413 vlog("# Using PATH", os.environ["PATH"])
3350 vlog(
3414 vlog(
3351 "# Using",
3415 "# Using",
3352 _bytes2sys(IMPL_PATH),
3416 _bytes2sys(IMPL_PATH),
3353 _bytes2sys(osenvironb[IMPL_PATH]),
3417 _bytes2sys(osenvironb[IMPL_PATH]),
3354 )
3418 )
3355 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3419 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3356
3420
3357 try:
3421 try:
3358 return self._runtests(testdescs) or 0
3422 return self._runtests(testdescs) or 0
3359 finally:
3423 finally:
3360 time.sleep(0.1)
3424 time.sleep(0.1)
3361 self._cleanup()
3425 self._cleanup()
3362
3426
3363 def findtests(self, args):
3427 def findtests(self, args):
3364 """Finds possible test files from arguments.
3428 """Finds possible test files from arguments.
3365
3429
3366 If you wish to inject custom tests into the test harness, this would
3430 If you wish to inject custom tests into the test harness, this would
3367 be a good function to monkeypatch or override in a derived class.
3431 be a good function to monkeypatch or override in a derived class.
3368 """
3432 """
3369 if not args:
3433 if not args:
3370 if self.options.changed:
3434 if self.options.changed:
3371 proc = Popen4(
3435 proc = Popen4(
3372 b'hg st --rev "%s" -man0 .'
3436 b'hg st --rev "%s" -man0 .'
3373 % _sys2bytes(self.options.changed),
3437 % _sys2bytes(self.options.changed),
3374 None,
3438 None,
3375 0,
3439 0,
3376 )
3440 )
3377 stdout, stderr = proc.communicate()
3441 stdout, stderr = proc.communicate()
3378 args = stdout.strip(b'\0').split(b'\0')
3442 args = stdout.strip(b'\0').split(b'\0')
3379 else:
3443 else:
3380 args = os.listdir(b'.')
3444 args = os.listdir(b'.')
3381
3445
3382 expanded_args = []
3446 expanded_args = []
3383 for arg in args:
3447 for arg in args:
3384 if os.path.isdir(arg):
3448 if os.path.isdir(arg):
3385 if not arg.endswith(b'/'):
3449 if not arg.endswith(b'/'):
3386 arg += b'/'
3450 arg += b'/'
3387 expanded_args.extend([arg + a for a in os.listdir(arg)])
3451 expanded_args.extend([arg + a for a in os.listdir(arg)])
3388 else:
3452 else:
3389 expanded_args.append(arg)
3453 expanded_args.append(arg)
3390 args = expanded_args
3454 args = expanded_args
3391
3455
3392 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3456 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3393 tests = []
3457 tests = []
3394 for t in args:
3458 for t in args:
3395 case = []
3459 case = []
3396
3460
3397 if not (
3461 if not (
3398 os.path.basename(t).startswith(b'test-')
3462 os.path.basename(t).startswith(b'test-')
3399 and (t.endswith(b'.py') or t.endswith(b'.t'))
3463 and (t.endswith(b'.py') or t.endswith(b'.t'))
3400 ):
3464 ):
3401 m = testcasepattern.match(os.path.basename(t))
3465 m = testcasepattern.match(os.path.basename(t))
3402 if m is not None:
3466 if m is not None:
3403 t_basename, casestr = m.groups()
3467 t_basename, casestr = m.groups()
3404 t = os.path.join(os.path.dirname(t), t_basename)
3468 t = os.path.join(os.path.dirname(t), t_basename)
3405 if casestr:
3469 if casestr:
3406 case = casestr.split(b'#')
3470 case = casestr.split(b'#')
3407 else:
3471 else:
3408 continue
3472 continue
3409
3473
3410 if t.endswith(b'.t'):
3474 if t.endswith(b'.t'):
3411 # .t file may contain multiple test cases
3475 # .t file may contain multiple test cases
3412 casedimensions = parsettestcases(t)
3476 casedimensions = parsettestcases(t)
3413 if casedimensions:
3477 if casedimensions:
3414 cases = []
3478 cases = []
3415
3479
3416 def addcases(case, casedimensions):
3480 def addcases(case, casedimensions):
3417 if not casedimensions:
3481 if not casedimensions:
3418 cases.append(case)
3482 cases.append(case)
3419 else:
3483 else:
3420 for c in casedimensions[0]:
3484 for c in casedimensions[0]:
3421 addcases(case + [c], casedimensions[1:])
3485 addcases(case + [c], casedimensions[1:])
3422
3486
3423 addcases([], casedimensions)
3487 addcases([], casedimensions)
3424 if case and case in cases:
3488 if case and case in cases:
3425 cases = [case]
3489 cases = [case]
3426 elif case:
3490 elif case:
3427 # Ignore invalid cases
3491 # Ignore invalid cases
3428 cases = []
3492 cases = []
3429 else:
3493 else:
3430 pass
3494 pass
3431 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3495 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3432 else:
3496 else:
3433 tests.append({'path': t})
3497 tests.append({'path': t})
3434 else:
3498 else:
3435 tests.append({'path': t})
3499 tests.append({'path': t})
3436
3500
3437 if self.options.retest:
3501 if self.options.retest:
3438 retest_args = []
3502 retest_args = []
3439 for test in tests:
3503 for test in tests:
3440 errpath = self._geterrpath(test)
3504 errpath = self._geterrpath(test)
3441 if os.path.exists(errpath):
3505 if os.path.exists(errpath):
3442 retest_args.append(test)
3506 retest_args.append(test)
3443 tests = retest_args
3507 tests = retest_args
3444 return tests
3508 return tests
3445
3509
3446 def _runtests(self, testdescs):
3510 def _runtests(self, testdescs):
3447 def _reloadtest(test, i):
3511 def _reloadtest(test, i):
3448 # convert a test back to its description dict
3512 # convert a test back to its description dict
3449 desc = {'path': test.path}
3513 desc = {'path': test.path}
3450 case = getattr(test, '_case', [])
3514 case = getattr(test, '_case', [])
3451 if case:
3515 if case:
3452 desc['case'] = case
3516 desc['case'] = case
3453 return self._gettest(desc, i)
3517 return self._gettest(desc, i)
3454
3518
3455 try:
3519 try:
3456 if self.options.restart:
3520 if self.options.restart:
3457 orig = list(testdescs)
3521 orig = list(testdescs)
3458 while testdescs:
3522 while testdescs:
3459 desc = testdescs[0]
3523 desc = testdescs[0]
3460 errpath = self._geterrpath(desc)
3524 errpath = self._geterrpath(desc)
3461 if os.path.exists(errpath):
3525 if os.path.exists(errpath):
3462 break
3526 break
3463 testdescs.pop(0)
3527 testdescs.pop(0)
3464 if not testdescs:
3528 if not testdescs:
3465 print("running all tests")
3529 print("running all tests")
3466 testdescs = orig
3530 testdescs = orig
3467
3531
3468 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3532 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3469 num_tests = len(tests) * self.options.runs_per_test
3533 num_tests = len(tests) * self.options.runs_per_test
3470
3534
3471 jobs = min(num_tests, self.options.jobs)
3535 jobs = min(num_tests, self.options.jobs)
3472
3536
3473 failed = False
3537 failed = False
3474 kws = self.options.keywords
3538 kws = self.options.keywords
3475 if kws is not None:
3539 if kws is not None:
3476 kws = kws.encode('utf-8')
3540 kws = kws.encode('utf-8')
3477
3541
3478 suite = TestSuite(
3542 suite = TestSuite(
3479 self._testdir,
3543 self._testdir,
3480 jobs=jobs,
3544 jobs=jobs,
3481 whitelist=self.options.whitelisted,
3545 whitelist=self.options.whitelisted,
3482 blacklist=self.options.blacklist,
3546 blacklist=self.options.blacklist,
3483 keywords=kws,
3547 keywords=kws,
3484 loop=self.options.loop,
3548 loop=self.options.loop,
3485 runs_per_test=self.options.runs_per_test,
3549 runs_per_test=self.options.runs_per_test,
3486 showchannels=self.options.showchannels,
3550 showchannels=self.options.showchannels,
3487 tests=tests,
3551 tests=tests,
3488 loadtest=_reloadtest,
3552 loadtest=_reloadtest,
3489 )
3553 )
3490 verbosity = 1
3554 verbosity = 1
3491 if self.options.list_tests:
3555 if self.options.list_tests:
3492 verbosity = 0
3556 verbosity = 0
3493 elif self.options.verbose:
3557 elif self.options.verbose:
3494 verbosity = 2
3558 verbosity = 2
3495 runner = TextTestRunner(self, verbosity=verbosity)
3559 runner = TextTestRunner(self, verbosity=verbosity)
3496
3560
3497 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3561 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3498 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3562 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3499
3563
3500 if self.options.list_tests:
3564 if self.options.list_tests:
3501 result = runner.listtests(suite)
3565 result = runner.listtests(suite)
3502 else:
3566 else:
3503 install_start_time = time.monotonic()
3567 install_start_time = time.monotonic()
3504 self._usecorrectpython()
3568 self._usecorrectpython()
3505 if self._installdir:
3569 if self._installdir:
3506 self._installhg()
3570 self._installhg()
3507 self._checkhglib("Testing")
3571 self._checkhglib("Testing")
3508 if self.options.chg:
3572 if self.options.chg:
3509 assert self._installdir
3573 assert self._installdir
3510 self._installchg()
3574 self._installchg()
3511 if self.options.rhg:
3575 if self.options.rhg:
3512 assert self._installdir
3576 assert self._installdir
3513 self._installrhg()
3577 self._installrhg()
3514 elif self.options.pyoxidized:
3578 elif self.options.pyoxidized:
3515 self._build_pyoxidized()
3579 self._build_pyoxidized()
3516 self._use_correct_mercurial()
3580 self._use_correct_mercurial()
3517 install_end_time = time.monotonic()
3581 install_end_time = time.monotonic()
3518 if self._installdir:
3582 if self._installdir:
3519 msg = 'installed Mercurial in %.2f seconds'
3583 msg = 'installed Mercurial in %.2f seconds'
3520 msg %= install_end_time - install_start_time
3584 msg %= install_end_time - install_start_time
3521 log(msg)
3585 log(msg)
3522
3586
3523 log(
3587 log(
3524 'running %d tests using %d parallel processes'
3588 'running %d tests using %d parallel processes'
3525 % (num_tests, jobs)
3589 % (num_tests, jobs)
3526 )
3590 )
3527
3591
3528 result = runner.run(suite)
3592 result = runner.run(suite)
3529
3593
3530 if result.failures or result.errors:
3594 if result.failures or result.errors:
3531 failed = True
3595 failed = True
3532
3596
3533 result.onEnd()
3597 result.onEnd()
3534
3598
3535 if self.options.anycoverage:
3599 if self.options.anycoverage:
3536 self._outputcoverage()
3600 self._outputcoverage()
3537 except KeyboardInterrupt:
3601 except KeyboardInterrupt:
3538 failed = True
3602 failed = True
3539 print("\ninterrupted!")
3603 print("\ninterrupted!")
3540
3604
3541 if failed:
3605 if failed:
3542 return 1
3606 return 1
3543
3607
3544 def _geterrpath(self, test):
3608 def _geterrpath(self, test):
3545 # test['path'] is a relative path
3609 # test['path'] is a relative path
3546 if 'case' in test:
3610 if 'case' in test:
3547 # for multiple dimensions test cases
3611 # for multiple dimensions test cases
3548 casestr = b'#'.join(test['case'])
3612 casestr = b'#'.join(test['case'])
3549 errpath = b'%s#%s.err' % (test['path'], casestr)
3613 errpath = b'%s#%s.err' % (test['path'], casestr)
3550 else:
3614 else:
3551 errpath = b'%s.err' % test['path']
3615 errpath = b'%s.err' % test['path']
3552 if self.options.outputdir:
3616 if self.options.outputdir:
3553 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3617 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3554 errpath = os.path.join(self._outputdir, errpath)
3618 errpath = os.path.join(self._outputdir, errpath)
3555 return errpath
3619 return errpath
3556
3620
3557 def _getport(self, count):
3621 def _getport(self, count):
3558 port = self._ports.get(count) # do we have a cached entry?
3622 port = self._ports.get(count) # do we have a cached entry?
3559 if port is None:
3623 if port is None:
3560 portneeded = 3
3624 portneeded = 3
3561 # above 100 tries we just give up and let test reports failure
3625 # above 100 tries we just give up and let test reports failure
3562 for tries in range(100):
3626 for tries in range(100):
3563 allfree = True
3627 allfree = True
3564 port = self.options.port + self._portoffset
3628 port = self.options.port + self._portoffset
3565 for idx in range(portneeded):
3629 for idx in range(portneeded):
3566 if not checkportisavailable(port + idx):
3630 if not checkportisavailable(port + idx):
3567 allfree = False
3631 allfree = False
3568 break
3632 break
3569 self._portoffset += portneeded
3633 self._portoffset += portneeded
3570 if allfree:
3634 if allfree:
3571 break
3635 break
3572 self._ports[count] = port
3636 self._ports[count] = port
3573 return port
3637 return port
3574
3638
3575 def _gettest(self, testdesc, count):
3639 def _gettest(self, testdesc, count):
3576 """Obtain a Test by looking at its filename.
3640 """Obtain a Test by looking at its filename.
3577
3641
3578 Returns a Test instance. The Test may not be runnable if it doesn't
3642 Returns a Test instance. The Test may not be runnable if it doesn't
3579 map to a known type.
3643 map to a known type.
3580 """
3644 """
3581 path = testdesc['path']
3645 path = testdesc['path']
3582 lctest = path.lower()
3646 lctest = path.lower()
3583 testcls = Test
3647 testcls = Test
3584
3648
3585 for ext, cls in self.TESTTYPES:
3649 for ext, cls in self.TESTTYPES:
3586 if lctest.endswith(ext):
3650 if lctest.endswith(ext):
3587 testcls = cls
3651 testcls = cls
3588 break
3652 break
3589
3653
3590 refpath = os.path.join(getcwdb(), path)
3654 refpath = os.path.join(getcwdb(), path)
3591 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3655 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3592
3656
3593 # extra keyword parameters. 'case' is used by .t tests
3657 # extra keyword parameters. 'case' is used by .t tests
3594 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3658 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3595
3659
3596 t = testcls(
3660 t = testcls(
3597 refpath,
3661 refpath,
3598 self._outputdir,
3662 self._outputdir,
3599 tmpdir,
3663 tmpdir,
3600 keeptmpdir=self.options.keep_tmpdir,
3664 keeptmpdir=self.options.keep_tmpdir,
3601 debug=self.options.debug,
3665 debug=self.options.debug,
3602 first=self.options.first,
3666 first=self.options.first,
3603 timeout=self.options.timeout,
3667 timeout=self.options.timeout,
3604 startport=self._getport(count),
3668 startport=self._getport(count),
3605 extraconfigopts=self.options.extra_config_opt,
3669 extraconfigopts=self.options.extra_config_opt,
3606 shell=self.options.shell,
3670 shell=self.options.shell,
3607 hgcommand=self._hgcommand,
3671 hgcommand=self._hgcommand,
3608 usechg=bool(self.options.with_chg or self.options.chg),
3672 usechg=bool(self.options.with_chg or self.options.chg),
3609 chgdebug=self.options.chg_debug,
3673 chgdebug=self.options.chg_debug,
3610 useipv6=useipv6,
3674 useipv6=useipv6,
3611 **kwds
3675 **kwds
3612 )
3676 )
3613 t.should_reload = True
3677 t.should_reload = True
3614 return t
3678 return t
3615
3679
3616 def _cleanup(self):
3680 def _cleanup(self):
3617 """Clean up state from this test invocation."""
3681 """Clean up state from this test invocation."""
3618 if self.options.keep_tmpdir:
3682 if self.options.keep_tmpdir:
3619 return
3683 return
3620
3684
3621 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3685 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3622 shutil.rmtree(self._hgtmp, True)
3686 shutil.rmtree(self._hgtmp, True)
3623 for f in self._createdfiles:
3687 for f in self._createdfiles:
3624 try:
3688 try:
3625 os.remove(f)
3689 os.remove(f)
3626 except OSError:
3690 except OSError:
3627 pass
3691 pass
3628
3692
3629 def _usecorrectpython(self):
3693 def _usecorrectpython(self):
3630 """Configure the environment to use the appropriate Python in tests."""
3694 """Configure the environment to use the appropriate Python in tests."""
3631 # Tests must use the same interpreter as us or bad things will happen.
3695 # Tests must use the same interpreter as us or bad things will happen.
3632 if WINDOWS:
3696 if WINDOWS:
3633 pyexe_names = [b'python', b'python3', b'python.exe']
3697 pyexe_names = [b'python', b'python3', b'python.exe']
3634 else:
3698 else:
3635 pyexe_names = [b'python', b'python3']
3699 pyexe_names = [b'python', b'python3']
3636
3700
3637 # os.symlink() is a thing with py3 on Windows, but it requires
3701 # os.symlink() is a thing with py3 on Windows, but it requires
3638 # Administrator rights.
3702 # Administrator rights.
3639 if not WINDOWS and getattr(os, 'symlink', None):
3703 if not WINDOWS and getattr(os, 'symlink', None):
3640 msg = "# Making python executable in test path a symlink to '%s'"
3704 msg = "# Making python executable in test path a symlink to '%s'"
3641 msg %= sysexecutable
3705 msg %= sysexecutable
3642 vlog(msg)
3706 vlog(msg)
3643 for pyexename in pyexe_names:
3707 for pyexename in pyexe_names:
3644 mypython = os.path.join(self._custom_bin_dir, pyexename)
3708 mypython = os.path.join(self._custom_bin_dir, pyexename)
3645 try:
3709 try:
3646 if os.readlink(mypython) == sysexecutable:
3710 if os.readlink(mypython) == sysexecutable:
3647 continue
3711 continue
3648 os.unlink(mypython)
3712 os.unlink(mypython)
3649 except FileNotFoundError:
3713 except FileNotFoundError:
3650 pass
3714 pass
3651 if self._findprogram(pyexename) != sysexecutable:
3715 if self._findprogram(pyexename) != sysexecutable:
3652 try:
3716 try:
3653 os.symlink(sysexecutable, mypython)
3717 os.symlink(sysexecutable, mypython)
3654 self._createdfiles.append(mypython)
3718 self._createdfiles.append(mypython)
3655 except FileExistsError:
3719 except FileExistsError:
3656 # child processes may race, which is harmless
3720 # child processes may race, which is harmless
3657 pass
3721 pass
3658 elif WINDOWS and not os.getenv('MSYSTEM'):
3722 elif WINDOWS and not os.getenv('MSYSTEM'):
3659 raise AssertionError('cannot run test on Windows without MSYSTEM')
3723 raise AssertionError('cannot run test on Windows without MSYSTEM')
3660 else:
3724 else:
3661 # Generate explicit file instead of symlink
3725 # Generate explicit file instead of symlink
3662 #
3726 #
3663 # This is especially important as Windows doesn't have
3727 # This is especially important as Windows doesn't have
3664 # `python3.exe`, and MSYS cannot understand the reparse point with
3728 # `python3.exe`, and MSYS cannot understand the reparse point with
3665 # that name provided by Microsoft. Create a simple script on PATH
3729 # that name provided by Microsoft. Create a simple script on PATH
3666 # with that name that delegates to the py3 launcher so the shebang
3730 # with that name that delegates to the py3 launcher so the shebang
3667 # lines work.
3731 # lines work.
3668 esc_executable = _sys2bytes(shellquote(sysexecutable))
3732 esc_executable = _sys2bytes(shellquote(sysexecutable))
3669 for pyexename in pyexe_names:
3733 for pyexename in pyexe_names:
3670 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3734 stub_exec_path = os.path.join(self._custom_bin_dir, pyexename)
3671 with open(stub_exec_path, 'wb') as f:
3735 with open(stub_exec_path, 'wb') as f:
3672 f.write(b'#!/bin/sh\n')
3736 f.write(b'#!/bin/sh\n')
3673 f.write(b'%s "$@"\n' % esc_executable)
3737 f.write(b'%s "$@"\n' % esc_executable)
3674
3738
3675 if WINDOWS:
3739 if WINDOWS:
3676 # adjust the path to make sur the main python finds it own dll
3740 # adjust the path to make sur the main python finds it own dll
3677 path = os.environ['PATH'].split(os.pathsep)
3741 path = os.environ['PATH'].split(os.pathsep)
3678 main_exec_dir = os.path.dirname(sysexecutable)
3742 main_exec_dir = os.path.dirname(sysexecutable)
3679 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3743 extra_paths = [_bytes2sys(self._custom_bin_dir), main_exec_dir]
3680
3744
3681 # Binaries installed by pip into the user area like pylint.exe may
3745 # Binaries installed by pip into the user area like pylint.exe may
3682 # not be in PATH by default.
3746 # not be in PATH by default.
3683 appdata = os.environ.get('APPDATA')
3747 appdata = os.environ.get('APPDATA')
3684 vi = sys.version_info
3748 vi = sys.version_info
3685 if appdata is not None:
3749 if appdata is not None:
3686 python_dir = 'Python%d%d' % (vi[0], vi[1])
3750 python_dir = 'Python%d%d' % (vi[0], vi[1])
3687 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3751 scripts_path = [appdata, 'Python', python_dir, 'Scripts']
3688 scripts_dir = os.path.join(*scripts_path)
3752 scripts_dir = os.path.join(*scripts_path)
3689 extra_paths.append(scripts_dir)
3753 extra_paths.append(scripts_dir)
3690
3754
3691 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3755 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3692
3756
3693 def _use_correct_mercurial(self):
3757 def _use_correct_mercurial(self):
3694 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3758 target_exec = os.path.join(self._custom_bin_dir, b'hg')
3695 if self._hgcommand != b'hg':
3759 if self._hgcommand != b'hg':
3696 # shutil.which only accept bytes from 3.8
3760 # shutil.which only accept bytes from 3.8
3697 real_exec = which(self._hgcommand)
3761 real_exec = which(self._hgcommand)
3698 if real_exec is None:
3762 if real_exec is None:
3699 raise ValueError('could not find exec path for "%s"', real_exec)
3763 raise ValueError('could not find exec path for "%s"', real_exec)
3700 if real_exec == target_exec:
3764 if real_exec == target_exec:
3701 # do not overwrite something with itself
3765 # do not overwrite something with itself
3702 return
3766 return
3703 if WINDOWS:
3767 if WINDOWS:
3704 with open(target_exec, 'wb') as f:
3768 with open(target_exec, 'wb') as f:
3705 f.write(b'#!/bin/sh\n')
3769 f.write(b'#!/bin/sh\n')
3706 escaped_exec = shellquote(_bytes2sys(real_exec))
3770 escaped_exec = shellquote(_bytes2sys(real_exec))
3707 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3771 f.write(b'%s "$@"\n' % _sys2bytes(escaped_exec))
3708 else:
3772 else:
3709 os.symlink(real_exec, target_exec)
3773 os.symlink(real_exec, target_exec)
3710 self._createdfiles.append(target_exec)
3774 self._createdfiles.append(target_exec)
3711
3775
3712 def _installhg(self):
3776 def _installhg(self):
3713 """Install hg into the test environment.
3777 """Install hg into the test environment.
3714
3778
3715 This will also configure hg with the appropriate testing settings.
3779 This will also configure hg with the appropriate testing settings.
3716 """
3780 """
3717 vlog("# Performing temporary installation of HG")
3781 vlog("# Performing temporary installation of HG")
3718 installerrs = os.path.join(self._hgtmp, b"install.err")
3782 installerrs = os.path.join(self._hgtmp, b"install.err")
3719 compiler = ''
3783 compiler = ''
3720 if self.options.compiler:
3784 if self.options.compiler:
3721 compiler = '--compiler ' + self.options.compiler
3785 compiler = '--compiler ' + self.options.compiler
3722 setup_opts = b""
3786 setup_opts = b""
3723 if self.options.pure:
3787 if self.options.pure:
3724 setup_opts = b"--pure"
3788 setup_opts = b"--pure"
3725 elif self.options.rust:
3789 elif self.options.rust:
3726 setup_opts = b"--rust"
3790 setup_opts = b"--rust"
3727 elif self.options.no_rust:
3791 elif self.options.no_rust:
3728 setup_opts = b"--no-rust"
3792 setup_opts = b"--no-rust"
3729
3793
3730 # Run installer in hg root
3794 # Run installer in hg root
3731 compiler = _sys2bytes(compiler)
3795 compiler = _sys2bytes(compiler)
3732 script = _sys2bytes(os.path.realpath(sys.argv[0]))
3796 script = _sys2bytes(os.path.realpath(sys.argv[0]))
3733 exe = _sys2bytes(sysexecutable)
3797 exe = _sys2bytes(sysexecutable)
3734 hgroot = os.path.dirname(os.path.dirname(script))
3798 hgroot = os.path.dirname(os.path.dirname(script))
3735 self._hgroot = hgroot
3799 self._hgroot = hgroot
3736 os.chdir(hgroot)
3800 os.chdir(hgroot)
3737 nohome = b'--home=""'
3801 nohome = b'--home=""'
3738 if WINDOWS:
3802 if WINDOWS:
3739 # The --home="" trick works only on OS where os.sep == '/'
3803 # The --home="" trick works only on OS where os.sep == '/'
3740 # because of a distutils convert_path() fast-path. Avoid it at
3804 # because of a distutils convert_path() fast-path. Avoid it at
3741 # least on Windows for now, deal with .pydistutils.cfg bugs
3805 # least on Windows for now, deal with .pydistutils.cfg bugs
3742 # when they happen.
3806 # when they happen.
3743 nohome = b''
3807 nohome = b''
3744 cmd = (
3808 cmd = (
3745 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3809 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3746 b' build %(compiler)s --build-base="%(base)s"'
3810 b' build %(compiler)s --build-base="%(base)s"'
3747 b' install --force --prefix="%(prefix)s"'
3811 b' install --force --prefix="%(prefix)s"'
3748 b' --install-lib="%(libdir)s"'
3812 b' --install-lib="%(libdir)s"'
3749 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3813 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3750 % {
3814 % {
3751 b'exe': exe,
3815 b'exe': exe,
3752 b'setup_opts': setup_opts,
3816 b'setup_opts': setup_opts,
3753 b'compiler': compiler,
3817 b'compiler': compiler,
3754 b'base': os.path.join(self._hgtmp, b"build"),
3818 b'base': os.path.join(self._hgtmp, b"build"),
3755 b'prefix': self._installdir,
3819 b'prefix': self._installdir,
3756 b'libdir': self._pythondir,
3820 b'libdir': self._pythondir,
3757 b'bindir': self._bindir,
3821 b'bindir': self._bindir,
3758 b'nohome': nohome,
3822 b'nohome': nohome,
3759 b'logfile': installerrs,
3823 b'logfile': installerrs,
3760 }
3824 }
3761 )
3825 )
3762
3826
3763 # setuptools requires install directories to exist.
3827 # setuptools requires install directories to exist.
3764 def makedirs(p):
3828 def makedirs(p):
3765 try:
3829 try:
3766 os.makedirs(p)
3830 os.makedirs(p)
3767 except FileExistsError:
3831 except FileExistsError:
3768 pass
3832 pass
3769
3833
3770 makedirs(self._pythondir)
3834 makedirs(self._pythondir)
3771 makedirs(self._bindir)
3835 makedirs(self._bindir)
3772
3836
3773 vlog("# Running", cmd.decode("utf-8"))
3837 vlog("# Running", cmd.decode("utf-8"))
3774 if subprocess.call(_bytes2sys(cmd), shell=True, env=original_env) == 0:
3838 if subprocess.call(_bytes2sys(cmd), shell=True, env=original_env) == 0:
3775 if not self.options.verbose:
3839 if not self.options.verbose:
3776 try:
3840 try:
3777 os.remove(installerrs)
3841 os.remove(installerrs)
3778 except FileNotFoundError:
3842 except FileNotFoundError:
3779 pass
3843 pass
3780 else:
3844 else:
3781 with open(installerrs, 'rb') as f:
3845 with open(installerrs, 'rb') as f:
3782 for line in f:
3846 for line in f:
3783 sys.stdout.buffer.write(line)
3847 sys.stdout.buffer.write(line)
3784 sys.exit(1)
3848 sys.exit(1)
3785 os.chdir(self._testdir)
3849 os.chdir(self._testdir)
3786
3850
3787 hgbat = os.path.join(self._bindir, b'hg.bat')
3851 hgbat = os.path.join(self._bindir, b'hg.bat')
3788 if os.path.isfile(hgbat):
3852 if os.path.isfile(hgbat):
3789 # hg.bat expects to be put in bin/scripts while run-tests.py
3853 # hg.bat expects to be put in bin/scripts while run-tests.py
3790 # installation layout put it in bin/ directly. Fix it
3854 # installation layout put it in bin/ directly. Fix it
3791 with open(hgbat, 'rb') as f:
3855 with open(hgbat, 'rb') as f:
3792 data = f.read()
3856 data = f.read()
3793 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3857 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3794 data = data.replace(
3858 data = data.replace(
3795 br'"%~dp0..\python" "%~dp0hg" %*',
3859 br'"%~dp0..\python" "%~dp0hg" %*',
3796 b'"%~dp0python" "%~dp0hg" %*',
3860 b'"%~dp0python" "%~dp0hg" %*',
3797 )
3861 )
3798 with open(hgbat, 'wb') as f:
3862 with open(hgbat, 'wb') as f:
3799 f.write(data)
3863 f.write(data)
3800 else:
3864 else:
3801 print('WARNING: cannot fix hg.bat reference to python.exe')
3865 print('WARNING: cannot fix hg.bat reference to python.exe')
3802
3866
3803 if self.options.anycoverage:
3867 if self.options.anycoverage:
3804 custom = os.path.join(
3868 custom = os.path.join(
3805 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3869 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3806 )
3870 )
3807 target = os.path.join(self._pythondir, b'sitecustomize.py')
3871 target = os.path.join(self._pythondir, b'sitecustomize.py')
3808 vlog('# Installing coverage trigger to %s' % target)
3872 vlog('# Installing coverage trigger to %s' % target)
3809 shutil.copyfile(custom, target)
3873 shutil.copyfile(custom, target)
3810 rc = os.path.join(self._testdir, b'.coveragerc')
3874 rc = os.path.join(self._testdir, b'.coveragerc')
3811 vlog('# Installing coverage rc to %s' % rc)
3875 vlog('# Installing coverage rc to %s' % rc)
3812 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3876 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3813 covdir = os.path.join(self._installdir, b'..', b'coverage')
3877 covdir = os.path.join(self._installdir, b'..', b'coverage')
3814 try:
3878 try:
3815 os.mkdir(covdir)
3879 os.mkdir(covdir)
3816 except FileExistsError:
3880 except FileExistsError:
3817 pass
3881 pass
3818
3882
3819 osenvironb[b'COVERAGE_DIR'] = covdir
3883 osenvironb[b'COVERAGE_DIR'] = covdir
3820
3884
3821 def _checkhglib(self, verb):
3885 def _checkhglib(self, verb):
3822 """Ensure that the 'mercurial' package imported by python is
3886 """Ensure that the 'mercurial' package imported by python is
3823 the one we expect it to be. If not, print a warning to stderr."""
3887 the one we expect it to be. If not, print a warning to stderr."""
3824 if self._pythondir_inferred:
3888 if self._pythondir_inferred:
3825 # The pythondir has been inferred from --with-hg flag.
3889 # The pythondir has been inferred from --with-hg flag.
3826 # We cannot expect anything sensible here.
3890 # We cannot expect anything sensible here.
3827 return
3891 return
3828 expecthg = os.path.join(self._pythondir, b'mercurial')
3892 expecthg = os.path.join(self._pythondir, b'mercurial')
3829 actualhg = self._gethgpath()
3893 actualhg = self._gethgpath()
3830 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3894 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3831 sys.stderr.write(
3895 sys.stderr.write(
3832 'warning: %s with unexpected mercurial lib: %s\n'
3896 'warning: %s with unexpected mercurial lib: %s\n'
3833 ' (expected %s)\n' % (verb, actualhg, expecthg)
3897 ' (expected %s)\n' % (verb, actualhg, expecthg)
3834 )
3898 )
3835
3899
3836 def _gethgpath(self):
3900 def _gethgpath(self):
3837 """Return the path to the mercurial package that is actually found by
3901 """Return the path to the mercurial package that is actually found by
3838 the current Python interpreter."""
3902 the current Python interpreter."""
3839 if self._hgpath is not None:
3903 if self._hgpath is not None:
3840 return self._hgpath
3904 return self._hgpath
3841
3905
3842 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3906 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3843 cmd = _bytes2sys(cmd % PYTHON)
3907 cmd = _bytes2sys(cmd % PYTHON)
3844
3908
3845 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3909 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3846 out, err = p.communicate()
3910 out, err = p.communicate()
3847
3911
3848 self._hgpath = out.strip()
3912 self._hgpath = out.strip()
3849
3913
3850 return self._hgpath
3914 return self._hgpath
3851
3915
3852 def _installchg(self):
3916 def _installchg(self):
3853 """Install chg into the test environment"""
3917 """Install chg into the test environment"""
3854 vlog('# Performing temporary installation of CHG')
3918 vlog('# Performing temporary installation of CHG')
3855 assert os.path.dirname(self._bindir) == self._installdir
3919 assert os.path.dirname(self._bindir) == self._installdir
3856 assert self._hgroot, 'must be called after _installhg()'
3920 assert self._hgroot, 'must be called after _installhg()'
3857 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3921 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3858 b'make': b'make', # TODO: switch by option or environment?
3922 b'make': b'make', # TODO: switch by option or environment?
3859 b'prefix': self._installdir,
3923 b'prefix': self._installdir,
3860 }
3924 }
3861 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3925 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3862 vlog("# Running", cmd)
3926 vlog("# Running", cmd)
3863 proc = subprocess.Popen(
3927 proc = subprocess.Popen(
3864 cmd,
3928 cmd,
3865 shell=True,
3929 shell=True,
3866 cwd=cwd,
3930 cwd=cwd,
3867 stdin=subprocess.PIPE,
3931 stdin=subprocess.PIPE,
3868 stdout=subprocess.PIPE,
3932 stdout=subprocess.PIPE,
3869 stderr=subprocess.STDOUT,
3933 stderr=subprocess.STDOUT,
3870 )
3934 )
3871 out, _err = proc.communicate()
3935 out, _err = proc.communicate()
3872 if proc.returncode != 0:
3936 if proc.returncode != 0:
3873 sys.stdout.buffer.write(out)
3937 sys.stdout.buffer.write(out)
3874 sys.exit(1)
3938 sys.exit(1)
3875
3939
3876 def _installrhg(self):
3940 def _installrhg(self):
3877 """Install rhg into the test environment"""
3941 """Install rhg into the test environment"""
3878 vlog('# Performing temporary installation of rhg')
3942 vlog('# Performing temporary installation of rhg')
3879 assert os.path.dirname(self._bindir) == self._installdir
3943 assert os.path.dirname(self._bindir) == self._installdir
3880 assert self._hgroot, 'must be called after _installhg()'
3944 assert self._hgroot, 'must be called after _installhg()'
3881 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3945 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3882 b'make': b'make', # TODO: switch by option or environment?
3946 b'make': b'make', # TODO: switch by option or environment?
3883 b'prefix': self._installdir,
3947 b'prefix': self._installdir,
3884 }
3948 }
3885 cwd = self._hgroot
3949 cwd = self._hgroot
3886 vlog("# Running", cmd)
3950 vlog("# Running", cmd)
3887 proc = subprocess.Popen(
3951 proc = subprocess.Popen(
3888 cmd,
3952 cmd,
3889 shell=True,
3953 shell=True,
3890 cwd=cwd,
3954 cwd=cwd,
3891 stdin=subprocess.PIPE,
3955 stdin=subprocess.PIPE,
3892 stdout=subprocess.PIPE,
3956 stdout=subprocess.PIPE,
3893 stderr=subprocess.STDOUT,
3957 stderr=subprocess.STDOUT,
3894 )
3958 )
3895 out, _err = proc.communicate()
3959 out, _err = proc.communicate()
3896 if proc.returncode != 0:
3960 if proc.returncode != 0:
3897 sys.stdout.buffer.write(out)
3961 sys.stdout.buffer.write(out)
3898 sys.exit(1)
3962 sys.exit(1)
3899
3963
3900 def _build_pyoxidized(self):
3964 def _build_pyoxidized(self):
3901 """build a pyoxidized version of mercurial into the test environment
3965 """build a pyoxidized version of mercurial into the test environment
3902
3966
3903 Ideally this function would be `install_pyoxidier` and would both build
3967 Ideally this function would be `install_pyoxidier` and would both build
3904 and install pyoxidier. However we are starting small to get pyoxidizer
3968 and install pyoxidier. However we are starting small to get pyoxidizer
3905 build binary to testing quickly.
3969 build binary to testing quickly.
3906 """
3970 """
3907 vlog('# build a pyoxidized version of Mercurial')
3971 vlog('# build a pyoxidized version of Mercurial')
3908 assert os.path.dirname(self._bindir) == self._installdir
3972 assert os.path.dirname(self._bindir) == self._installdir
3909 assert self._hgroot, 'must be called after _installhg()'
3973 assert self._hgroot, 'must be called after _installhg()'
3910 target = b''
3974 target = b''
3911 if WINDOWS:
3975 if WINDOWS:
3912 target = b'windows'
3976 target = b'windows'
3913 elif MACOS:
3977 elif MACOS:
3914 target = b'macos'
3978 target = b'macos'
3915
3979
3916 cmd = b'"%(make)s" pyoxidizer-%(platform)s-tests' % {
3980 cmd = b'"%(make)s" pyoxidizer-%(platform)s-tests' % {
3917 b'make': b'make',
3981 b'make': b'make',
3918 b'platform': target,
3982 b'platform': target,
3919 }
3983 }
3920 cwd = self._hgroot
3984 cwd = self._hgroot
3921 vlog("# Running", cmd)
3985 vlog("# Running", cmd)
3922 proc = subprocess.Popen(
3986 proc = subprocess.Popen(
3923 _bytes2sys(cmd),
3987 _bytes2sys(cmd),
3924 shell=True,
3988 shell=True,
3925 cwd=_bytes2sys(cwd),
3989 cwd=_bytes2sys(cwd),
3926 stdin=subprocess.PIPE,
3990 stdin=subprocess.PIPE,
3927 stdout=subprocess.PIPE,
3991 stdout=subprocess.PIPE,
3928 stderr=subprocess.STDOUT,
3992 stderr=subprocess.STDOUT,
3929 )
3993 )
3930 out, _err = proc.communicate()
3994 out, _err = proc.communicate()
3931 if proc.returncode != 0:
3995 if proc.returncode != 0:
3932 sys.stdout.buffer.write(out)
3996 sys.stdout.buffer.write(out)
3933 sys.exit(1)
3997 sys.exit(1)
3934
3998
3935 cmd = _bytes2sys(b"%s debuginstall -Tjson" % self._hgcommand)
3999 cmd = _bytes2sys(b"%s debuginstall -Tjson" % self._hgcommand)
3936 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
4000 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3937 out, err = p.communicate()
4001 out, err = p.communicate()
3938
4002
3939 props = json.loads(out)[0]
4003 props = json.loads(out)[0]
3940
4004
3941 # Affects hghave.py
4005 # Affects hghave.py
3942 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
4006 osenvironb.pop(b'PYOXIDIZED_IN_MEMORY_RSRC', None)
3943 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
4007 osenvironb.pop(b'PYOXIDIZED_FILESYSTEM_RSRC', None)
3944 if props["hgmodules"] == props["pythonexe"]:
4008 if props["hgmodules"] == props["pythonexe"]:
3945 osenvironb[b'PYOXIDIZED_IN_MEMORY_RSRC'] = b'1'
4009 osenvironb[b'PYOXIDIZED_IN_MEMORY_RSRC'] = b'1'
3946 else:
4010 else:
3947 osenvironb[b'PYOXIDIZED_FILESYSTEM_RSRC'] = b'1'
4011 osenvironb[b'PYOXIDIZED_FILESYSTEM_RSRC'] = b'1'
3948
4012
3949 def _outputcoverage(self):
4013 def _outputcoverage(self):
3950 """Produce code coverage output."""
4014 """Produce code coverage output."""
3951 import coverage
4015 import coverage
3952
4016
3953 coverage = coverage.coverage
4017 coverage = coverage.coverage
3954
4018
3955 vlog('# Producing coverage report')
4019 vlog('# Producing coverage report')
3956 # chdir is the easiest way to get short, relative paths in the
4020 # chdir is the easiest way to get short, relative paths in the
3957 # output.
4021 # output.
3958 os.chdir(self._hgroot)
4022 os.chdir(self._hgroot)
3959 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
4023 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3960 cov = coverage(data_file=os.path.join(covdir, 'cov'))
4024 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3961
4025
3962 # Map install directory paths back to source directory.
4026 # Map install directory paths back to source directory.
3963 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
4027 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3964
4028
3965 cov.combine()
4029 cov.combine()
3966
4030
3967 omit = [
4031 omit = [
3968 _bytes2sys(os.path.join(x, b'*'))
4032 _bytes2sys(os.path.join(x, b'*'))
3969 for x in [self._bindir, self._testdir]
4033 for x in [self._bindir, self._testdir]
3970 ]
4034 ]
3971 cov.report(ignore_errors=True, omit=omit)
4035 cov.report(ignore_errors=True, omit=omit)
3972
4036
3973 if self.options.htmlcov:
4037 if self.options.htmlcov:
3974 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
4038 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3975 cov.html_report(directory=htmldir, omit=omit)
4039 cov.html_report(directory=htmldir, omit=omit)
3976 if self.options.annotate:
4040 if self.options.annotate:
3977 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
4041 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3978 if not os.path.isdir(adir):
4042 if not os.path.isdir(adir):
3979 os.mkdir(adir)
4043 os.mkdir(adir)
3980 cov.annotate(directory=adir, omit=omit)
4044 cov.annotate(directory=adir, omit=omit)
3981
4045
3982 def _findprogram(self, program):
4046 def _findprogram(self, program):
3983 """Search PATH for a executable program"""
4047 """Search PATH for a executable program"""
3984 dpb = _sys2bytes(os.defpath)
4048 dpb = _sys2bytes(os.defpath)
3985 sepb = _sys2bytes(os.pathsep)
4049 sepb = _sys2bytes(os.pathsep)
3986 for p in osenvironb.get(b'PATH', dpb).split(sepb):
4050 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3987 name = os.path.join(p, program)
4051 name = os.path.join(p, program)
3988 if WINDOWS or os.access(name, os.X_OK):
4052 if WINDOWS or os.access(name, os.X_OK):
3989 return _bytes2sys(name)
4053 return _bytes2sys(name)
3990 return None
4054 return None
3991
4055
3992 def _checktools(self):
4056 def _checktools(self):
3993 """Ensure tools required to run tests are present."""
4057 """Ensure tools required to run tests are present."""
3994 for p in self.REQUIREDTOOLS:
4058 for p in self.REQUIREDTOOLS:
3995 if WINDOWS and not p.endswith(b'.exe'):
4059 if WINDOWS and not p.endswith(b'.exe'):
3996 p += b'.exe'
4060 p += b'.exe'
3997 found = self._findprogram(p)
4061 found = self._findprogram(p)
3998 p = p.decode("utf-8")
4062 p = p.decode("utf-8")
3999 if found:
4063 if found:
4000 vlog("# Found prerequisite", p, "at", found)
4064 vlog("# Found prerequisite", p, "at", found)
4001 else:
4065 else:
4002 print("WARNING: Did not find prerequisite tool: %s " % p)
4066 print("WARNING: Did not find prerequisite tool: %s " % p)
4003
4067
4004
4068
4005 def aggregateexceptions(path):
4069 def aggregateexceptions(path):
4006 exceptioncounts = collections.Counter()
4070 exceptioncounts = collections.Counter()
4007 testsbyfailure = collections.defaultdict(set)
4071 testsbyfailure = collections.defaultdict(set)
4008 failuresbytest = collections.defaultdict(set)
4072 failuresbytest = collections.defaultdict(set)
4009
4073
4010 for f in os.listdir(path):
4074 for f in os.listdir(path):
4011 with open(os.path.join(path, f), 'rb') as fh:
4075 with open(os.path.join(path, f), 'rb') as fh:
4012 data = fh.read().split(b'\0')
4076 data = fh.read().split(b'\0')
4013 if len(data) != 5:
4077 if len(data) != 5:
4014 continue
4078 continue
4015
4079
4016 exc, mainframe, hgframe, hgline, testname = data
4080 exc, mainframe, hgframe, hgline, testname = data
4017 exc = exc.decode('utf-8')
4081 exc = exc.decode('utf-8')
4018 mainframe = mainframe.decode('utf-8')
4082 mainframe = mainframe.decode('utf-8')
4019 hgframe = hgframe.decode('utf-8')
4083 hgframe = hgframe.decode('utf-8')
4020 hgline = hgline.decode('utf-8')
4084 hgline = hgline.decode('utf-8')
4021 testname = testname.decode('utf-8')
4085 testname = testname.decode('utf-8')
4022
4086
4023 key = (hgframe, hgline, exc)
4087 key = (hgframe, hgline, exc)
4024 exceptioncounts[key] += 1
4088 exceptioncounts[key] += 1
4025 testsbyfailure[key].add(testname)
4089 testsbyfailure[key].add(testname)
4026 failuresbytest[testname].add(key)
4090 failuresbytest[testname].add(key)
4027
4091
4028 # Find test having fewest failures for each failure.
4092 # Find test having fewest failures for each failure.
4029 leastfailing = {}
4093 leastfailing = {}
4030 for key, tests in testsbyfailure.items():
4094 for key, tests in testsbyfailure.items():
4031 fewesttest = None
4095 fewesttest = None
4032 fewestcount = 99999999
4096 fewestcount = 99999999
4033 for test in sorted(tests):
4097 for test in sorted(tests):
4034 if len(failuresbytest[test]) < fewestcount:
4098 if len(failuresbytest[test]) < fewestcount:
4035 fewesttest = test
4099 fewesttest = test
4036 fewestcount = len(failuresbytest[test])
4100 fewestcount = len(failuresbytest[test])
4037
4101
4038 leastfailing[key] = (fewestcount, fewesttest)
4102 leastfailing[key] = (fewestcount, fewesttest)
4039
4103
4040 # Create a combined counter so we can sort by total occurrences and
4104 # Create a combined counter so we can sort by total occurrences and
4041 # impacted tests.
4105 # impacted tests.
4042 combined = {}
4106 combined = {}
4043 for key in exceptioncounts:
4107 for key in exceptioncounts:
4044 combined[key] = (
4108 combined[key] = (
4045 exceptioncounts[key],
4109 exceptioncounts[key],
4046 len(testsbyfailure[key]),
4110 len(testsbyfailure[key]),
4047 leastfailing[key][0],
4111 leastfailing[key][0],
4048 leastfailing[key][1],
4112 leastfailing[key][1],
4049 )
4113 )
4050
4114
4051 return {
4115 return {
4052 'exceptioncounts': exceptioncounts,
4116 'exceptioncounts': exceptioncounts,
4053 'total': sum(exceptioncounts.values()),
4117 'total': sum(exceptioncounts.values()),
4054 'combined': combined,
4118 'combined': combined,
4055 'leastfailing': leastfailing,
4119 'leastfailing': leastfailing,
4056 'byfailure': testsbyfailure,
4120 'byfailure': testsbyfailure,
4057 'bytest': failuresbytest,
4121 'bytest': failuresbytest,
4058 }
4122 }
4059
4123
4060
4124
4061 if __name__ == '__main__':
4125 if __name__ == '__main__':
4062 if WINDOWS and not os.getenv('MSYSTEM'):
4126 if WINDOWS and not os.getenv('MSYSTEM'):
4063 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4127 print('cannot run test on Windows without MSYSTEM', file=sys.stderr)
4064 print(
4128 print(
4065 '(if you need to do so contact the mercurial devs: '
4129 '(if you need to do so contact the mercurial devs: '
4066 'mercurial@mercurial-scm.org)',
4130 'mercurial@mercurial-scm.org)',
4067 file=sys.stderr,
4131 file=sys.stderr,
4068 )
4132 )
4069 sys.exit(255)
4133 sys.exit(255)
4070
4134
4071 runner = TestRunner()
4135 runner = TestRunner()
4072
4136
4073 try:
4137 try:
4074 import msvcrt
4138 import msvcrt
4075
4139
4076 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4140 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
4077 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4141 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4078 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4142 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
4079 except ImportError:
4143 except ImportError:
4080 pass
4144 pass
4081
4145
4082 sys.exit(runner.run(sys.argv[1:]))
4146 sys.exit(runner.run(sys.argv[1:]))
@@ -1,2089 +1,2089
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=$HGTEST_REAL_HG -j1 "$@"
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@"
19 > }
19 > }
20
20
21 error paths
21 error paths
22
22
23 #if symlink
23 #if symlink
24 $ ln -s $TESTDIR/run-tests.py hg
24 $ ln -s $TESTDIR/run-tests.py 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, not: run-tests.py
26 warning: --with-hg should specify an hg script, not: run-tests.py
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;28m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (pygments211 !)
179 \x1b[38;5;28m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (pygments211 !)
180 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (no-pygments211 !)
180 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc) (no-pygments211 !)
181 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
181 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
182 \x1b[38;5;250m \x1b[39m $ echo "bar-baz"; echo "bar-bad"; echo foo (esc) (pygments211 !)
182 \x1b[38;5;250m \x1b[39m $ echo "bar-baz"; echo "bar-bad"; echo foo (esc) (pygments211 !)
183 $ echo "bar-baz"; echo "bar-bad"; echo foo (no-pygments211 !)
183 $ echo "bar-baz"; echo "bar-bad"; echo foo (no-pygments211 !)
184 \x1b[38;5;28m+ bar*baz (glob)\x1b[39m (esc) (pygments211 !)
184 \x1b[38;5;28m+ bar*baz (glob)\x1b[39m (esc) (pygments211 !)
185 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc) (no-pygments211 !)
185 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc) (no-pygments211 !)
186 \x1b[38;5;250m \x1b[39m bar*bad (glob) (esc) (pygments211 !)
186 \x1b[38;5;250m \x1b[39m bar*bad (glob) (esc) (pygments211 !)
187 bar*bad (glob) (no-pygments211 !)
187 bar*bad (glob) (no-pygments211 !)
188 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
188 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
189 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
189 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
190 \x1b[38;5;28m+ foo\x1b[39m (esc) (pygments211 !)
190 \x1b[38;5;28m+ foo\x1b[39m (esc) (pygments211 !)
191 \x1b[38;5;34m+ foo\x1b[39m (esc) (no-pygments211 !)
191 \x1b[38;5;34m+ foo\x1b[39m (esc) (no-pygments211 !)
192
192
193 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
193 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
194 !
194 \x1b[38;5;88m!\x1b[39m (esc)
195 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
195 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
196 # Ran 1 tests, 0 skipped, 1 failed.
196 # Ran 1 tests, 0 skipped, 1 failed.
197 python hash seed: * (glob)
197 python hash seed: * (glob)
198 [1]
198 [1]
199
199
200 $ rt test-failure.t 2> tmp.log
200 $ rt test-failure.t 2> tmp.log
201 running 1 tests using 1 parallel processes
201 running 1 tests using 1 parallel processes
202 [1]
202 [1]
203 $ cat tmp.log
203 $ cat tmp.log
204
204
205 --- $TESTTMP/test-failure.t
205 --- $TESTTMP/test-failure.t
206 +++ $TESTTMP/test-failure.t.err
206 +++ $TESTTMP/test-failure.t.err
207 @@ -1,4 +1,4 @@
207 @@ -1,4 +1,4 @@
208 $ echo "bar-baz"; echo "bar-bad"; echo foo
208 $ echo "bar-baz"; echo "bar-bad"; echo foo
209 + bar*baz (glob)
209 + bar*baz (glob)
210 bar*bad (glob)
210 bar*bad (glob)
211 - bar*baz (glob)
211 - bar*baz (glob)
212 - | fo (re)
212 - | fo (re)
213 + foo
213 + foo
214
214
215 ERROR: test-failure.t output changed
215 ERROR: test-failure.t output changed
216 !
216 !
217 Failed test-failure.t: output changed
217 Failed test-failure.t: output changed
218 # Ran 1 tests, 0 skipped, 1 failed.
218 # Ran 1 tests, 0 skipped, 1 failed.
219 python hash seed: * (glob)
219 python hash seed: * (glob)
220 #endif
220 #endif
221
221
222 $ cat > test-failure.t << EOF
222 $ cat > test-failure.t << EOF
223 > $ true
223 > $ true
224 > should go away (true !)
224 > should go away (true !)
225 > $ true
225 > $ true
226 > should stay (false !)
226 > should stay (false !)
227 >
227 >
228 > Should remove first line, not second or third
228 > Should remove first line, not second or third
229 > $ echo 'testing'
229 > $ echo 'testing'
230 > baz*foo (glob) (true !)
230 > baz*foo (glob) (true !)
231 > foobar*foo (glob) (false !)
231 > foobar*foo (glob) (false !)
232 > te*ting (glob) (true !)
232 > te*ting (glob) (true !)
233 >
233 >
234 > Should keep first two lines, remove third and last
234 > Should keep first two lines, remove third and last
235 > $ echo 'testing'
235 > $ echo 'testing'
236 > test.ng (re) (true !)
236 > test.ng (re) (true !)
237 > foo.ar (re) (false !)
237 > foo.ar (re) (false !)
238 > b.r (re) (true !)
238 > b.r (re) (true !)
239 > missing (?)
239 > missing (?)
240 > awol (true !)
240 > awol (true !)
241 >
241 >
242 > The "missing" line should stay, even though awol is dropped
242 > The "missing" line should stay, even though awol is dropped
243 > $ echo 'testing'
243 > $ echo 'testing'
244 > test.ng (re) (true !)
244 > test.ng (re) (true !)
245 > foo.ar (?)
245 > foo.ar (?)
246 > awol
246 > awol
247 > missing (?)
247 > missing (?)
248 > EOF
248 > EOF
249 $ rt test-failure.t
249 $ rt test-failure.t
250 running 1 tests using 1 parallel processes
250 running 1 tests using 1 parallel processes
251
251
252 --- $TESTTMP/test-failure.t
252 --- $TESTTMP/test-failure.t
253 +++ $TESTTMP/test-failure.t.err
253 +++ $TESTTMP/test-failure.t.err
254 @@ -1,11 +1,9 @@
254 @@ -1,11 +1,9 @@
255 $ true
255 $ true
256 - should go away (true !)
256 - should go away (true !)
257 $ true
257 $ true
258 should stay (false !)
258 should stay (false !)
259
259
260 Should remove first line, not second or third
260 Should remove first line, not second or third
261 $ echo 'testing'
261 $ echo 'testing'
262 - baz*foo (glob) (true !)
262 - baz*foo (glob) (true !)
263 foobar*foo (glob) (false !)
263 foobar*foo (glob) (false !)
264 te*ting (glob) (true !)
264 te*ting (glob) (true !)
265
265
266 foo.ar (re) (false !)
266 foo.ar (re) (false !)
267 missing (?)
267 missing (?)
268 @@ -13,13 +11,10 @@
268 @@ -13,13 +11,10 @@
269 $ echo 'testing'
269 $ echo 'testing'
270 test.ng (re) (true !)
270 test.ng (re) (true !)
271 foo.ar (re) (false !)
271 foo.ar (re) (false !)
272 - b.r (re) (true !)
272 - b.r (re) (true !)
273 missing (?)
273 missing (?)
274 - awol (true !)
274 - awol (true !)
275
275
276 The "missing" line should stay, even though awol is dropped
276 The "missing" line should stay, even though awol is dropped
277 $ echo 'testing'
277 $ echo 'testing'
278 test.ng (re) (true !)
278 test.ng (re) (true !)
279 foo.ar (?)
279 foo.ar (?)
280 - awol
280 - awol
281 missing (?)
281 missing (?)
282
282
283 ERROR: test-failure.t output changed
283 ERROR: test-failure.t output changed
284 !
284 !
285 Failed test-failure.t: output changed
285 Failed test-failure.t: output changed
286 # Ran 1 tests, 0 skipped, 1 failed.
286 # Ran 1 tests, 0 skipped, 1 failed.
287 python hash seed: * (glob)
287 python hash seed: * (glob)
288 [1]
288 [1]
289
289
290 basic failing test
290 basic failing test
291 $ cat > test-failure.t << EOF
291 $ cat > test-failure.t << EOF
292 > $ echo babar
292 > $ echo babar
293 > rataxes
293 > rataxes
294 > This is a noop statement so that
294 > This is a noop statement so that
295 > this test is still more bytes than success.
295 > this test is still more bytes than success.
296 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
297 > pad pad pad pad............................................................
297 > pad pad pad pad............................................................
298 > pad pad pad pad............................................................
298 > pad pad pad pad............................................................
299 > pad pad pad pad............................................................
299 > pad pad pad pad............................................................
300 > pad pad pad pad............................................................
300 > pad pad pad pad............................................................
301 > pad pad pad pad............................................................
301 > pad pad pad pad............................................................
302 > EOF
302 > EOF
303
303
304 >>> fh = open('test-failure-unicode.t', 'wb')
304 >>> fh = open('test-failure-unicode.t', 'wb')
305 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
305 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
306 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
306 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
307
307
308 $ rt
308 $ rt
309 running 3 tests using 1 parallel processes
309 running 3 tests using 1 parallel processes
310
310
311 --- $TESTTMP/test-failure.t
311 --- $TESTTMP/test-failure.t
312 +++ $TESTTMP/test-failure.t.err
312 +++ $TESTTMP/test-failure.t.err
313 @@ -1,5 +1,5 @@
313 @@ -1,5 +1,5 @@
314 $ echo babar
314 $ echo babar
315 - rataxes
315 - rataxes
316 + babar
316 + babar
317 This is a noop statement so that
317 This is a noop statement so that
318 this test is still more bytes than success.
318 this test is still more bytes than success.
319 pad pad pad pad............................................................
319 pad pad pad pad............................................................
320
320
321 ERROR: test-failure.t output changed
321 ERROR: test-failure.t output changed
322 !.
322 !.
323 --- $TESTTMP/test-failure-unicode.t
323 --- $TESTTMP/test-failure-unicode.t
324 +++ $TESTTMP/test-failure-unicode.t.err
324 +++ $TESTTMP/test-failure-unicode.t.err
325 @@ -1,2 +1,2 @@
325 @@ -1,2 +1,2 @@
326 $ echo babar\xce\xb1 (esc)
326 $ echo babar\xce\xb1 (esc)
327 - l\xce\xb5\xce\xb5t (esc)
327 - l\xce\xb5\xce\xb5t (esc)
328 + babar\xce\xb1 (esc)
328 + babar\xce\xb1 (esc)
329
329
330 ERROR: test-failure-unicode.t output changed
330 ERROR: test-failure-unicode.t output changed
331 !
331 !
332 Failed test-failure-unicode.t: output changed
332 Failed test-failure-unicode.t: output changed
333 Failed test-failure.t: output changed
333 Failed test-failure.t: output changed
334 # Ran 3 tests, 0 skipped, 2 failed.
334 # Ran 3 tests, 0 skipped, 2 failed.
335 python hash seed: * (glob)
335 python hash seed: * (glob)
336 [1]
336 [1]
337
337
338 test --outputdir
338 test --outputdir
339 $ mkdir output
339 $ mkdir output
340 $ rt --outputdir output
340 $ rt --outputdir output
341 running 3 tests using 1 parallel processes
341 running 3 tests using 1 parallel processes
342
342
343 --- $TESTTMP/test-failure.t
343 --- $TESTTMP/test-failure.t
344 +++ $TESTTMP/output/test-failure.t.err
344 +++ $TESTTMP/output/test-failure.t.err
345 @@ -1,5 +1,5 @@
345 @@ -1,5 +1,5 @@
346 $ echo babar
346 $ echo babar
347 - rataxes
347 - rataxes
348 + babar
348 + babar
349 This is a noop statement so that
349 This is a noop statement so that
350 this test is still more bytes than success.
350 this test is still more bytes than success.
351 pad pad pad pad............................................................
351 pad pad pad pad............................................................
352
352
353 ERROR: test-failure.t output changed
353 ERROR: test-failure.t output changed
354 !.
354 !.
355 --- $TESTTMP/test-failure-unicode.t
355 --- $TESTTMP/test-failure-unicode.t
356 +++ $TESTTMP/output/test-failure-unicode.t.err
356 +++ $TESTTMP/output/test-failure-unicode.t.err
357 @@ -1,2 +1,2 @@
357 @@ -1,2 +1,2 @@
358 $ echo babar\xce\xb1 (esc)
358 $ echo babar\xce\xb1 (esc)
359 - l\xce\xb5\xce\xb5t (esc)
359 - l\xce\xb5\xce\xb5t (esc)
360 + babar\xce\xb1 (esc)
360 + babar\xce\xb1 (esc)
361
361
362 ERROR: test-failure-unicode.t output changed
362 ERROR: test-failure-unicode.t output changed
363 !
363 !
364 Failed test-failure-unicode.t: output changed
364 Failed test-failure-unicode.t: output changed
365 Failed test-failure.t: output changed
365 Failed test-failure.t: output changed
366 # Ran 3 tests, 0 skipped, 2 failed.
366 # Ran 3 tests, 0 skipped, 2 failed.
367 python hash seed: * (glob)
367 python hash seed: * (glob)
368 [1]
368 [1]
369 $ ls -a output
369 $ ls -a output
370 .
370 .
371 ..
371 ..
372 .testtimes
372 .testtimes
373 test-failure-unicode.t.err
373 test-failure-unicode.t.err
374 test-failure.t.err
374 test-failure.t.err
375
375
376 test --xunit support
376 test --xunit support
377 $ rt --xunit=xunit.xml
377 $ rt --xunit=xunit.xml
378 running 3 tests using 1 parallel processes
378 running 3 tests using 1 parallel processes
379
379
380 --- $TESTTMP/test-failure.t
380 --- $TESTTMP/test-failure.t
381 +++ $TESTTMP/test-failure.t.err
381 +++ $TESTTMP/test-failure.t.err
382 @@ -1,5 +1,5 @@
382 @@ -1,5 +1,5 @@
383 $ echo babar
383 $ echo babar
384 - rataxes
384 - rataxes
385 + babar
385 + babar
386 This is a noop statement so that
386 This is a noop statement so that
387 this test is still more bytes than success.
387 this test is still more bytes than success.
388 pad pad pad pad............................................................
388 pad pad pad pad............................................................
389
389
390 ERROR: test-failure.t output changed
390 ERROR: test-failure.t output changed
391 !.
391 !.
392 --- $TESTTMP/test-failure-unicode.t
392 --- $TESTTMP/test-failure-unicode.t
393 +++ $TESTTMP/test-failure-unicode.t.err
393 +++ $TESTTMP/test-failure-unicode.t.err
394 @@ -1,2 +1,2 @@
394 @@ -1,2 +1,2 @@
395 $ echo babar\xce\xb1 (esc)
395 $ echo babar\xce\xb1 (esc)
396 - l\xce\xb5\xce\xb5t (esc)
396 - l\xce\xb5\xce\xb5t (esc)
397 + babar\xce\xb1 (esc)
397 + babar\xce\xb1 (esc)
398
398
399 ERROR: test-failure-unicode.t output changed
399 ERROR: test-failure-unicode.t output changed
400 !
400 !
401 Failed test-failure-unicode.t: output changed
401 Failed test-failure-unicode.t: output changed
402 Failed test-failure.t: output changed
402 Failed test-failure.t: output changed
403 # Ran 3 tests, 0 skipped, 2 failed.
403 # Ran 3 tests, 0 skipped, 2 failed.
404 python hash seed: * (glob)
404 python hash seed: * (glob)
405 [1]
405 [1]
406 $ cat xunit.xml
406 $ cat xunit.xml
407 <?xml version="1.0" encoding="utf-8"?>
407 <?xml version="1.0" encoding="utf-8"?>
408 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
408 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
409 <testcase name="test-success.t" time="*"/> (glob)
409 <testcase name="test-success.t" time="*"/> (glob)
410 <testcase name="test-failure-unicode.t" time="*"> (glob)
410 <testcase name="test-failure-unicode.t" time="*"> (glob)
411 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
411 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
412 <failure message="output changed" type="output-mismatch"> (no-py38 !)
412 <failure message="output changed" type="output-mismatch"> (no-py38 !)
413 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
413 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
414 +++ $TESTTMP/test-failure-unicode.t.err
414 +++ $TESTTMP/test-failure-unicode.t.err
415 @@ -1,2 +1,2 @@
415 @@ -1,2 +1,2 @@
416 $ echo babar\xce\xb1 (esc)
416 $ echo babar\xce\xb1 (esc)
417 - l\xce\xb5\xce\xb5t (esc)
417 - l\xce\xb5\xce\xb5t (esc)
418 + babar\xce\xb1 (esc)
418 + babar\xce\xb1 (esc)
419 ]]></failure> (py38 !)
419 ]]></failure> (py38 !)
420 ]]> </failure> (no-py38 !)
420 ]]> </failure> (no-py38 !)
421 </testcase>
421 </testcase>
422 <testcase name="test-failure.t" time="*"> (glob)
422 <testcase name="test-failure.t" time="*"> (glob)
423 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
423 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
424 <failure message="output changed" type="output-mismatch"> (no-py38 !)
424 <failure message="output changed" type="output-mismatch"> (no-py38 !)
425 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
425 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
426 +++ $TESTTMP/test-failure.t.err
426 +++ $TESTTMP/test-failure.t.err
427 @@ -1,5 +1,5 @@
427 @@ -1,5 +1,5 @@
428 $ echo babar
428 $ echo babar
429 - rataxes
429 - rataxes
430 + babar
430 + babar
431 This is a noop statement so that
431 This is a noop statement so that
432 this test is still more bytes than success.
432 this test is still more bytes than success.
433 pad pad pad pad............................................................
433 pad pad pad pad............................................................
434 ]]></failure> (py38 !)
434 ]]></failure> (py38 !)
435 ]]> </failure> (no-py38 !)
435 ]]> </failure> (no-py38 !)
436 </testcase>
436 </testcase>
437 </testsuite>
437 </testsuite>
438
438
439 $ cat .testtimes
439 $ cat .testtimes
440 test-empty.t * (glob)
440 test-empty.t * (glob)
441 test-failure-globs.t * (glob)
441 test-failure-globs.t * (glob)
442 test-failure-unicode.t * (glob)
442 test-failure-unicode.t * (glob)
443 test-failure.t * (glob)
443 test-failure.t * (glob)
444 test-success.t * (glob)
444 test-success.t * (glob)
445
445
446 $ rt --list-tests
446 $ rt --list-tests
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
450
451 $ rt --list-tests --json
451 $ rt --list-tests --json
452 test-failure-unicode.t
452 test-failure-unicode.t
453 test-failure.t
453 test-failure.t
454 test-success.t
454 test-success.t
455 $ cat report.json
455 $ cat report.json
456 testreport ={
456 testreport ={
457 "test-failure-unicode.t": {
457 "test-failure-unicode.t": {
458 "result": "success"
458 "result": "success"
459 },
459 },
460 "test-failure.t": {
460 "test-failure.t": {
461 "result": "success"
461 "result": "success"
462 },
462 },
463 "test-success.t": {
463 "test-success.t": {
464 "result": "success"
464 "result": "success"
465 }
465 }
466 } (no-eol)
466 } (no-eol)
467
467
468 $ rt --list-tests --xunit=xunit.xml
468 $ rt --list-tests --xunit=xunit.xml
469 test-failure-unicode.t
469 test-failure-unicode.t
470 test-failure.t
470 test-failure.t
471 test-success.t
471 test-success.t
472 $ cat xunit.xml
472 $ cat xunit.xml
473 <?xml version="1.0" encoding="utf-8"?>
473 <?xml version="1.0" encoding="utf-8"?>
474 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
474 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
475 <testcase name="test-failure-unicode.t"/>
475 <testcase name="test-failure-unicode.t"/>
476 <testcase name="test-failure.t"/>
476 <testcase name="test-failure.t"/>
477 <testcase name="test-success.t"/>
477 <testcase name="test-success.t"/>
478 </testsuite>
478 </testsuite>
479
479
480 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
480 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
481 test-failure-unicode.t
481 test-failure-unicode.t
482 test-failure.t
482 test-failure.t
483 $ cat output/report.json
483 $ cat output/report.json
484 testreport ={
484 testreport ={
485 "test-failure-unicode.t": {
485 "test-failure-unicode.t": {
486 "result": "success"
486 "result": "success"
487 },
487 },
488 "test-failure.t": {
488 "test-failure.t": {
489 "result": "success"
489 "result": "success"
490 }
490 }
491 } (no-eol)
491 } (no-eol)
492 $ cat xunit.xml
492 $ cat xunit.xml
493 <?xml version="1.0" encoding="utf-8"?>
493 <?xml version="1.0" encoding="utf-8"?>
494 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
494 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
495 <testcase name="test-failure-unicode.t"/>
495 <testcase name="test-failure-unicode.t"/>
496 <testcase name="test-failure.t"/>
496 <testcase name="test-failure.t"/>
497 </testsuite>
497 </testsuite>
498
498
499 $ rm test-failure-unicode.t
499 $ rm test-failure-unicode.t
500
500
501 test for --retest
501 test for --retest
502 ====================
502 ====================
503
503
504 $ rt --retest
504 $ rt --retest
505 running 1 tests using 1 parallel processes
505 running 1 tests using 1 parallel processes
506
506
507 --- $TESTTMP/test-failure.t
507 --- $TESTTMP/test-failure.t
508 +++ $TESTTMP/test-failure.t.err
508 +++ $TESTTMP/test-failure.t.err
509 @@ -1,5 +1,5 @@
509 @@ -1,5 +1,5 @@
510 $ echo babar
510 $ echo babar
511 - rataxes
511 - rataxes
512 + babar
512 + babar
513 This is a noop statement so that
513 This is a noop statement so that
514 this test is still more bytes than success.
514 this test is still more bytes than success.
515 pad pad pad pad............................................................
515 pad pad pad pad............................................................
516
516
517 ERROR: test-failure.t output changed
517 ERROR: test-failure.t output changed
518 !
518 !
519 Failed test-failure.t: output changed
519 Failed test-failure.t: output changed
520 # Ran 1 tests, 0 skipped, 1 failed.
520 # Ran 1 tests, 0 skipped, 1 failed.
521 python hash seed: * (glob)
521 python hash seed: * (glob)
522 [1]
522 [1]
523
523
524 --retest works with --outputdir
524 --retest works with --outputdir
525 $ rm -r output
525 $ rm -r output
526 $ mkdir output
526 $ mkdir output
527 $ mv test-failure.t.err output
527 $ mv test-failure.t.err output
528 $ rt --retest --outputdir output
528 $ rt --retest --outputdir output
529 running 1 tests using 1 parallel processes
529 running 1 tests using 1 parallel processes
530
530
531 --- $TESTTMP/test-failure.t
531 --- $TESTTMP/test-failure.t
532 +++ $TESTTMP/output/test-failure.t.err
532 +++ $TESTTMP/output/test-failure.t.err
533 @@ -1,5 +1,5 @@
533 @@ -1,5 +1,5 @@
534 $ echo babar
534 $ echo babar
535 - rataxes
535 - rataxes
536 + babar
536 + babar
537 This is a noop statement so that
537 This is a noop statement so that
538 this test is still more bytes than success.
538 this test is still more bytes than success.
539 pad pad pad pad............................................................
539 pad pad pad pad............................................................
540
540
541 ERROR: test-failure.t output changed
541 ERROR: test-failure.t output changed
542 !
542 !
543 Failed test-failure.t: output changed
543 Failed test-failure.t: output changed
544 # Ran 1 tests, 0 skipped, 1 failed.
544 # Ran 1 tests, 0 skipped, 1 failed.
545 python hash seed: * (glob)
545 python hash seed: * (glob)
546 [1]
546 [1]
547
547
548 Selecting Tests To Run
548 Selecting Tests To Run
549 ======================
549 ======================
550
550
551 successful
551 successful
552
552
553 $ rt test-success.t
553 $ rt test-success.t
554 running 1 tests using 1 parallel processes
554 running 1 tests using 1 parallel processes
555 .
555 .
556 # Ran 1 tests, 0 skipped, 0 failed.
556 # Ran 1 tests, 0 skipped, 0 failed.
557
557
558 success w/ keyword
558 success w/ keyword
559 $ rt -k xyzzy
559 $ rt -k xyzzy
560 running 2 tests using 1 parallel processes
560 running 2 tests using 1 parallel processes
561 .
561 .
562 # Ran 2 tests, 1 skipped, 0 failed.
562 # Ran 2 tests, 1 skipped, 0 failed.
563
563
564 failed
564 failed
565
565
566 $ rt test-failure.t
566 $ rt test-failure.t
567 running 1 tests using 1 parallel processes
567 running 1 tests using 1 parallel processes
568
568
569 --- $TESTTMP/test-failure.t
569 --- $TESTTMP/test-failure.t
570 +++ $TESTTMP/test-failure.t.err
570 +++ $TESTTMP/test-failure.t.err
571 @@ -1,5 +1,5 @@
571 @@ -1,5 +1,5 @@
572 $ echo babar
572 $ echo babar
573 - rataxes
573 - rataxes
574 + babar
574 + babar
575 This is a noop statement so that
575 This is a noop statement so that
576 this test is still more bytes than success.
576 this test is still more bytes than success.
577 pad pad pad pad............................................................
577 pad pad pad pad............................................................
578
578
579 ERROR: test-failure.t output changed
579 ERROR: test-failure.t output changed
580 !
580 !
581 Failed test-failure.t: output changed
581 Failed test-failure.t: output changed
582 # Ran 1 tests, 0 skipped, 1 failed.
582 # Ran 1 tests, 0 skipped, 1 failed.
583 python hash seed: * (glob)
583 python hash seed: * (glob)
584 [1]
584 [1]
585
585
586 failure w/ keyword
586 failure w/ keyword
587 $ rt -k rataxes
587 $ rt -k rataxes
588 running 2 tests using 1 parallel processes
588 running 2 tests using 1 parallel processes
589
589
590 --- $TESTTMP/test-failure.t
590 --- $TESTTMP/test-failure.t
591 +++ $TESTTMP/test-failure.t.err
591 +++ $TESTTMP/test-failure.t.err
592 @@ -1,5 +1,5 @@
592 @@ -1,5 +1,5 @@
593 $ echo babar
593 $ echo babar
594 - rataxes
594 - rataxes
595 + babar
595 + babar
596 This is a noop statement so that
596 This is a noop statement so that
597 this test is still more bytes than success.
597 this test is still more bytes than success.
598 pad pad pad pad............................................................
598 pad pad pad pad............................................................
599
599
600 ERROR: test-failure.t output changed
600 ERROR: test-failure.t output changed
601 !
601 !
602 Failed test-failure.t: output changed
602 Failed test-failure.t: output changed
603 # Ran 2 tests, 1 skipped, 1 failed.
603 # Ran 2 tests, 1 skipped, 1 failed.
604 python hash seed: * (glob)
604 python hash seed: * (glob)
605 [1]
605 [1]
606
606
607 Verify that when a process fails to start we show a useful message
607 Verify that when a process fails to start we show a useful message
608 ==================================================================
608 ==================================================================
609
609
610 $ cat > test-serve-fail.t <<EOF
610 $ cat > test-serve-fail.t <<EOF
611 > $ echo 'abort: child process failed to start blah'
611 > $ echo 'abort: child process failed to start blah'
612 > EOF
612 > EOF
613 $ rt test-serve-fail.t
613 $ rt test-serve-fail.t
614 running 1 tests using 1 parallel processes
614 running 1 tests using 1 parallel processes
615
615
616 --- $TESTTMP/test-serve-fail.t
616 --- $TESTTMP/test-serve-fail.t
617 +++ $TESTTMP/test-serve-fail.t.err
617 +++ $TESTTMP/test-serve-fail.t.err
618 @@ -1* +1,2 @@ (glob)
618 @@ -1* +1,2 @@ (glob)
619 $ echo 'abort: child process failed to start blah'
619 $ echo 'abort: child process failed to start blah'
620 + abort: child process failed to start blah
620 + abort: child process failed to start blah
621
621
622 ERROR: test-serve-fail.t output changed
622 ERROR: test-serve-fail.t output changed
623 !
623 !
624 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
624 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
625 # Ran 1 tests, 0 skipped, 1 failed.
625 # Ran 1 tests, 0 skipped, 1 failed.
626 python hash seed: * (glob)
626 python hash seed: * (glob)
627 [1]
627 [1]
628 $ rm test-serve-fail.t
628 $ rm test-serve-fail.t
629
629
630 Verify that we can try other ports
630 Verify that we can try other ports
631 ===================================
631 ===================================
632
632
633 Extensions aren't inherited by the invoked run-tests.py. An extension
633 Extensions aren't inherited by the invoked run-tests.py. An extension
634 introducing a repository requirement could cause this to fail. So we force
634 introducing a repository requirement could cause this to fail. So we force
635 HGRCPATH to get a clean environment.
635 HGRCPATH to get a clean environment.
636
636
637 $ HGRCPATH= hg init inuse
637 $ HGRCPATH= hg init inuse
638 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
638 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
639 $ cat blocks.pid >> $DAEMON_PIDS
639 $ cat blocks.pid >> $DAEMON_PIDS
640 $ cat > test-serve-inuse.t <<EOF
640 $ cat > test-serve-inuse.t <<EOF
641 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
641 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
642 > $ cat hg.pid >> \$DAEMON_PIDS
642 > $ cat hg.pid >> \$DAEMON_PIDS
643 > EOF
643 > EOF
644 $ rt test-serve-inuse.t
644 $ rt test-serve-inuse.t
645 running 1 tests using 1 parallel processes
645 running 1 tests using 1 parallel processes
646 .
646 .
647 # Ran 1 tests, 0 skipped, 0 failed.
647 # Ran 1 tests, 0 skipped, 0 failed.
648 $ rm test-serve-inuse.t
648 $ rm test-serve-inuse.t
649 $ killdaemons.py $DAEMON_PIDS
649 $ killdaemons.py $DAEMON_PIDS
650
650
651 Running In Debug Mode
651 Running In Debug Mode
652 ======================
652 ======================
653
653
654 $ rt --debug 2>&1 | grep -v pwd
654 $ rt --debug 2>&1 | grep -v pwd
655 running 2 tests using 1 parallel processes
655 running 2 tests using 1 parallel processes
656 + echo *SALT* 0 0 (glob)
656 + echo *SALT* 0 0 (glob)
657 *SALT* 0 0 (glob)
657 *SALT* 0 0 (glob)
658 + echo babar
658 + echo babar
659 babar
659 babar
660 + echo *SALT* 10 0 (glob)
660 + echo *SALT* 10 0 (glob)
661 *SALT* 10 0 (glob)
661 *SALT* 10 0 (glob)
662 *+ echo *SALT* 0 0 (glob)
662 *+ echo *SALT* 0 0 (glob)
663 *SALT* 0 0 (glob)
663 *SALT* 0 0 (glob)
664 + echo babar
664 + echo babar
665 babar
665 babar
666 + echo *SALT* 2 0 (glob)
666 + echo *SALT* 2 0 (glob)
667 *SALT* 2 0 (glob)
667 *SALT* 2 0 (glob)
668 + echo xyzzy
668 + echo xyzzy
669 xyzzy
669 xyzzy
670 + echo *SALT* 9 0 (glob)
670 + echo *SALT* 9 0 (glob)
671 *SALT* 9 0 (glob)
671 *SALT* 9 0 (glob)
672 + printf *abc\ndef\nxyz\n* (glob)
672 + printf *abc\ndef\nxyz\n* (glob)
673 abc
673 abc
674 def
674 def
675 xyz
675 xyz
676 + echo *SALT* 15 0 (glob)
676 + echo *SALT* 15 0 (glob)
677 *SALT* 15 0 (glob)
677 *SALT* 15 0 (glob)
678 + printf *zyx\nwvu\ntsr\n* (glob)
678 + printf *zyx\nwvu\ntsr\n* (glob)
679 zyx
679 zyx
680 wvu
680 wvu
681 tsr
681 tsr
682 + echo *SALT* 22 0 (glob)
682 + echo *SALT* 22 0 (glob)
683 *SALT* 22 0 (glob)
683 *SALT* 22 0 (glob)
684 .
684 .
685 # Ran 2 tests, 0 skipped, 0 failed.
685 # Ran 2 tests, 0 skipped, 0 failed.
686
686
687 Parallel runs
687 Parallel runs
688 ==============
688 ==============
689
689
690 (duplicate the failing test to get predictable output)
690 (duplicate the failing test to get predictable output)
691 $ cp test-failure.t test-failure-copy.t
691 $ cp test-failure.t test-failure-copy.t
692
692
693 $ rt --jobs 2 test-failure*.t -n
693 $ rt --jobs 2 test-failure*.t -n
694 running 2 tests using 2 parallel processes
694 running 2 tests using 2 parallel processes
695 !!
695 !!
696 Failed test-failure*.t: output changed (glob)
696 Failed test-failure*.t: output changed (glob)
697 Failed test-failure*.t: output changed (glob)
697 Failed test-failure*.t: output changed (glob)
698 # Ran 2 tests, 0 skipped, 2 failed.
698 # Ran 2 tests, 0 skipped, 2 failed.
699 python hash seed: * (glob)
699 python hash seed: * (glob)
700 [1]
700 [1]
701
701
702 failures in parallel with --first should only print one failure
702 failures in parallel with --first should only print one failure
703 $ rt --jobs 2 --first test-failure*.t
703 $ rt --jobs 2 --first test-failure*.t
704 running 2 tests using 2 parallel processes
704 running 2 tests using 2 parallel processes
705
705
706 --- $TESTTMP/test-failure*.t (glob)
706 --- $TESTTMP/test-failure*.t (glob)
707 +++ $TESTTMP/test-failure*.t.err (glob)
707 +++ $TESTTMP/test-failure*.t.err (glob)
708 @@ -1,5 +1,5 @@
708 @@ -1,5 +1,5 @@
709 $ echo babar
709 $ echo babar
710 - rataxes
710 - rataxes
711 + babar
711 + babar
712 This is a noop statement so that
712 This is a noop statement so that
713 this test is still more bytes than success.
713 this test is still more bytes than success.
714 pad pad pad pad............................................................
714 pad pad pad pad............................................................
715
715
716 Failed test-failure*.t: output changed (glob)
716 Failed test-failure*.t: output changed (glob)
717 Failed test-failure*.t: output changed (glob)
717 Failed test-failure*.t: output changed (glob)
718 # Ran 2 tests, 0 skipped, 2 failed.
718 # Ran 2 tests, 0 skipped, 2 failed.
719 python hash seed: * (glob)
719 python hash seed: * (glob)
720 [1]
720 [1]
721
721
722
722
723 (delete the duplicated test file)
723 (delete the duplicated test file)
724 $ rm test-failure-copy.t
724 $ rm test-failure-copy.t
725
725
726 multiple runs per test should be parallelized
726 multiple runs per test should be parallelized
727
727
728 $ rt --jobs 2 --runs-per-test 2 test-success.t
728 $ rt --jobs 2 --runs-per-test 2 test-success.t
729 running 2 tests using 2 parallel processes
729 running 2 tests using 2 parallel processes
730 ..
730 ..
731 # Ran 2 tests, 0 skipped, 0 failed.
731 # Ran 2 tests, 0 skipped, 0 failed.
732
732
733 Interactive run
733 Interactive run
734 ===============
734 ===============
735
735
736 (backup the failing test)
736 (backup the failing test)
737 $ cp test-failure.t backup
737 $ cp test-failure.t backup
738
738
739 Refuse the fix
739 Refuse the fix
740
740
741 $ echo 'n' | rt -i
741 $ echo 'n' | rt -i
742 running 2 tests using 1 parallel processes
742 running 2 tests using 1 parallel processes
743
743
744 --- $TESTTMP/test-failure.t
744 --- $TESTTMP/test-failure.t
745 +++ $TESTTMP/test-failure.t.err
745 +++ $TESTTMP/test-failure.t.err
746 @@ -1,5 +1,5 @@
746 @@ -1,5 +1,5 @@
747 $ echo babar
747 $ echo babar
748 - rataxes
748 - rataxes
749 + babar
749 + babar
750 This is a noop statement so that
750 This is a noop statement so that
751 this test is still more bytes than success.
751 this test is still more bytes than success.
752 pad pad pad pad............................................................
752 pad pad pad pad............................................................
753 Accept this change? [y/N]
753 Accept this change? [y/N]
754 ERROR: test-failure.t output changed
754 ERROR: test-failure.t output changed
755 !.
755 !.
756 Failed test-failure.t: output changed
756 Failed test-failure.t: output changed
757 # Ran 2 tests, 0 skipped, 1 failed.
757 # Ran 2 tests, 0 skipped, 1 failed.
758 python hash seed: * (glob)
758 python hash seed: * (glob)
759 [1]
759 [1]
760
760
761 $ cat test-failure.t
761 $ cat test-failure.t
762 $ echo babar
762 $ echo babar
763 rataxes
763 rataxes
764 This is a noop statement so that
764 This is a noop statement so that
765 this test is still more bytes than success.
765 this test is still more bytes than success.
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 pad pad pad pad............................................................
769 pad pad pad pad............................................................
770 pad pad pad pad............................................................
770 pad pad pad pad............................................................
771 pad pad pad pad............................................................
771 pad pad pad pad............................................................
772
772
773 Interactive with custom view
773 Interactive with custom view
774
774
775 $ echo 'n' | rt -i --view echo
775 $ echo 'n' | rt -i --view echo
776 running 2 tests using 1 parallel processes
776 running 2 tests using 1 parallel processes
777 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
777 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
778 Accept this change? [y/N]* (glob)
778 Accept this change? [y/N]* (glob)
779 ERROR: test-failure.t output changed
779 ERROR: test-failure.t output changed
780 !.
780 !.
781 Failed test-failure.t: output changed
781 Failed test-failure.t: output changed
782 # Ran 2 tests, 0 skipped, 1 failed.
782 # Ran 2 tests, 0 skipped, 1 failed.
783 python hash seed: * (glob)
783 python hash seed: * (glob)
784 [1]
784 [1]
785
785
786 View the fix
786 View the fix
787
787
788 $ echo 'y' | rt --view echo
788 $ echo 'y' | rt --view echo
789 running 2 tests using 1 parallel processes
789 running 2 tests using 1 parallel processes
790 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
790 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
791
791
792 ERROR: test-failure.t output changed
792 ERROR: test-failure.t output changed
793 !.
793 !.
794 Failed test-failure.t: output changed
794 Failed test-failure.t: output changed
795 # Ran 2 tests, 0 skipped, 1 failed.
795 # Ran 2 tests, 0 skipped, 1 failed.
796 python hash seed: * (glob)
796 python hash seed: * (glob)
797 [1]
797 [1]
798
798
799 Accept the fix
799 Accept the fix
800
800
801 $ cat >> test-failure.t <<EOF
801 $ cat >> test-failure.t <<EOF
802 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
802 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
803 > saved backup bundle to \$TESTTMP/foo.hg
803 > saved backup bundle to \$TESTTMP/foo.hg
804 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
804 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
805 > saved backup bundle to $TESTTMP\\foo.hg
805 > saved backup bundle to $TESTTMP\\foo.hg
806 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
806 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
807 > saved backup bundle to \$TESTTMP/*.hg (glob)
807 > saved backup bundle to \$TESTTMP/*.hg (glob)
808 > EOF
808 > EOF
809 $ echo 'y' | rt -i 2>&1
809 $ echo 'y' | rt -i 2>&1
810 running 2 tests using 1 parallel processes
810 running 2 tests using 1 parallel processes
811
811
812 --- $TESTTMP/test-failure.t
812 --- $TESTTMP/test-failure.t
813 +++ $TESTTMP/test-failure.t.err
813 +++ $TESTTMP/test-failure.t.err
814 @@ -1,5 +1,5 @@
814 @@ -1,5 +1,5 @@
815 $ echo babar
815 $ echo babar
816 - rataxes
816 - rataxes
817 + babar
817 + babar
818 This is a noop statement so that
818 This is a noop statement so that
819 this test is still more bytes than success.
819 this test is still more bytes than success.
820 pad pad pad pad............................................................
820 pad pad pad pad............................................................
821 @@ -11,6 +11,6 @@
821 @@ -11,6 +11,6 @@
822 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
822 $ echo '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\foo.hg
825 - saved backup bundle to $TESTTMP\foo.hg
826 + saved backup bundle to $TESTTMP/foo.hg
826 + saved backup bundle to $TESTTMP/foo.hg
827 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
827 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
828 saved backup bundle to $TESTTMP/*.hg (glob)
828 saved backup bundle to $TESTTMP/*.hg (glob)
829 Accept this change? [y/N] ..
829 Accept this change? [y/N] ..
830 # Ran 2 tests, 0 skipped, 0 failed.
830 # Ran 2 tests, 0 skipped, 0 failed.
831
831
832 $ sed -e 's,(glob)$,&<,g' test-failure.t
832 $ sed -e 's,(glob)$,&<,g' test-failure.t
833 $ echo babar
833 $ echo babar
834 babar
834 babar
835 This is a noop statement so that
835 This is a noop statement so that
836 this test is still more bytes than success.
836 this test is still more bytes than success.
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 pad pad pad pad............................................................
840 pad pad pad pad............................................................
841 pad pad pad pad............................................................
841 pad pad pad pad............................................................
842 pad pad pad pad............................................................
842 pad pad pad pad............................................................
843 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
843 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
844 saved backup bundle to $TESTTMP/foo.hg
844 saved backup bundle to $TESTTMP/foo.hg
845 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
845 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
846 saved backup bundle to $TESTTMP/foo.hg
846 saved backup bundle to $TESTTMP/foo.hg
847 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
847 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
848 saved backup bundle to $TESTTMP/*.hg (glob)<
848 saved backup bundle to $TESTTMP/*.hg (glob)<
849
849
850 $ rm test-failure.t
850 $ rm test-failure.t
851
851
852 Race condition - test file was modified when test is running
852 Race condition - test file was modified when test is running
853
853
854 $ TESTRACEDIR=`pwd`
854 $ TESTRACEDIR=`pwd`
855 $ export TESTRACEDIR
855 $ export TESTRACEDIR
856 $ cat > test-race.t <<EOF
856 $ cat > test-race.t <<EOF
857 > $ echo 1
857 > $ echo 1
858 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
858 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
859 > EOF
859 > EOF
860
860
861 $ rt -i test-race.t
861 $ rt -i test-race.t
862 running 1 tests using 1 parallel processes
862 running 1 tests using 1 parallel processes
863
863
864 --- $TESTTMP/test-race.t
864 --- $TESTTMP/test-race.t
865 +++ $TESTTMP/test-race.t.err
865 +++ $TESTTMP/test-race.t.err
866 @@ -1,2 +1,3 @@
866 @@ -1,2 +1,3 @@
867 $ echo 1
867 $ echo 1
868 + 1
868 + 1
869 $ echo "# a new line" >> $TESTTMP/test-race.t
869 $ echo "# a new line" >> $TESTTMP/test-race.t
870 Reference output has changed (run again to prompt changes)
870 Reference output has changed (run again to prompt changes)
871 ERROR: test-race.t output changed
871 ERROR: test-race.t output changed
872 !
872 !
873 Failed test-race.t: output changed
873 Failed test-race.t: output changed
874 # Ran 1 tests, 0 skipped, 1 failed.
874 # Ran 1 tests, 0 skipped, 1 failed.
875 python hash seed: * (glob)
875 python hash seed: * (glob)
876 [1]
876 [1]
877
877
878 $ rm test-race.t
878 $ rm test-race.t
879
879
880 When "#testcases" is used in .t files
880 When "#testcases" is used in .t files
881
881
882 $ cat >> test-cases.t <<EOF
882 $ cat >> test-cases.t <<EOF
883 > #testcases a b
883 > #testcases a b
884 > #if a
884 > #if a
885 > $ echo 1
885 > $ echo 1
886 > #endif
886 > #endif
887 > #if b
887 > #if b
888 > $ echo 2
888 > $ echo 2
889 > #endif
889 > #endif
890 > EOF
890 > EOF
891
891
892 $ cat <<EOF | rt -i test-cases.t 2>&1
892 $ cat <<EOF | rt -i test-cases.t 2>&1
893 > y
893 > y
894 > y
894 > y
895 > EOF
895 > EOF
896 running 2 tests using 1 parallel processes
896 running 2 tests using 1 parallel processes
897
897
898 --- $TESTTMP/test-cases.t
898 --- $TESTTMP/test-cases.t
899 +++ $TESTTMP/test-cases.t#a.err
899 +++ $TESTTMP/test-cases.t#a.err
900 @@ -1,6 +1,7 @@
900 @@ -1,6 +1,7 @@
901 #testcases a b
901 #testcases a b
902 #if a
902 #if a
903 $ echo 1
903 $ echo 1
904 + 1
904 + 1
905 #endif
905 #endif
906 #if b
906 #if b
907 $ echo 2
907 $ echo 2
908 Accept this change? [y/N] .
908 Accept this change? [y/N] .
909 --- $TESTTMP/test-cases.t
909 --- $TESTTMP/test-cases.t
910 +++ $TESTTMP/test-cases.t#b.err
910 +++ $TESTTMP/test-cases.t#b.err
911 @@ -5,4 +5,5 @@
911 @@ -5,4 +5,5 @@
912 #endif
912 #endif
913 #if b
913 #if b
914 $ echo 2
914 $ echo 2
915 + 2
915 + 2
916 #endif
916 #endif
917 Accept this change? [y/N] .
917 Accept this change? [y/N] .
918 # Ran 2 tests, 0 skipped, 0 failed.
918 # Ran 2 tests, 0 skipped, 0 failed.
919
919
920 $ cat test-cases.t
920 $ cat test-cases.t
921 #testcases a b
921 #testcases a b
922 #if a
922 #if a
923 $ echo 1
923 $ echo 1
924 1
924 1
925 #endif
925 #endif
926 #if b
926 #if b
927 $ echo 2
927 $ echo 2
928 2
928 2
929 #endif
929 #endif
930
930
931 $ cat >> test-cases.t <<'EOF'
931 $ cat >> test-cases.t <<'EOF'
932 > #if a
932 > #if a
933 > $ NAME=A
933 > $ NAME=A
934 > #else
934 > #else
935 > $ NAME=B
935 > $ NAME=B
936 > #endif
936 > #endif
937 > $ echo $NAME
937 > $ echo $NAME
938 > A (a !)
938 > A (a !)
939 > B (b !)
939 > B (b !)
940 > EOF
940 > EOF
941 $ rt test-cases.t
941 $ rt test-cases.t
942 running 2 tests using 1 parallel processes
942 running 2 tests using 1 parallel processes
943 ..
943 ..
944 # Ran 2 tests, 0 skipped, 0 failed.
944 # Ran 2 tests, 0 skipped, 0 failed.
945
945
946 When using multiple dimensions of "#testcases" in .t files
946 When using multiple dimensions of "#testcases" in .t files
947
947
948 $ cat > test-cases.t <<'EOF'
948 $ cat > test-cases.t <<'EOF'
949 > #testcases a b
949 > #testcases a b
950 > #testcases c d
950 > #testcases c d
951 > #if a d
951 > #if a d
952 > $ echo $TESTCASE
952 > $ echo $TESTCASE
953 > a#d
953 > a#d
954 > #endif
954 > #endif
955 > #if b c
955 > #if b c
956 > $ echo yes
956 > $ echo yes
957 > no
957 > no
958 > #endif
958 > #endif
959 > EOF
959 > EOF
960 $ rt test-cases.t
960 $ rt test-cases.t
961 running 4 tests using 1 parallel processes
961 running 4 tests using 1 parallel processes
962 ..
962 ..
963 --- $TESTTMP/test-cases.t
963 --- $TESTTMP/test-cases.t
964 +++ $TESTTMP/test-cases.t#b#c.err
964 +++ $TESTTMP/test-cases.t#b#c.err
965 @@ -6,5 +6,5 @@
965 @@ -6,5 +6,5 @@
966 #endif
966 #endif
967 #if b c
967 #if b c
968 $ echo yes
968 $ echo yes
969 - no
969 - no
970 + yes
970 + yes
971 #endif
971 #endif
972
972
973 ERROR: test-cases.t#b#c output changed
973 ERROR: test-cases.t#b#c output changed
974 !.
974 !.
975 Failed test-cases.t#b#c: output changed
975 Failed test-cases.t#b#c: output changed
976 # Ran 4 tests, 0 skipped, 1 failed.
976 # Ran 4 tests, 0 skipped, 1 failed.
977 python hash seed: * (glob)
977 python hash seed: * (glob)
978 [1]
978 [1]
979
979
980 $ rt --retest
980 $ rt --retest
981 running 1 tests using 1 parallel processes
981 running 1 tests using 1 parallel processes
982
982
983 --- $TESTTMP/test-cases.t
983 --- $TESTTMP/test-cases.t
984 +++ $TESTTMP/test-cases.t#b#c.err
984 +++ $TESTTMP/test-cases.t#b#c.err
985 @@ -6,5 +6,5 @@
985 @@ -6,5 +6,5 @@
986 #endif
986 #endif
987 #if b c
987 #if b c
988 $ echo yes
988 $ echo yes
989 - no
989 - no
990 + yes
990 + yes
991 #endif
991 #endif
992
992
993 ERROR: test-cases.t#b#c output changed
993 ERROR: test-cases.t#b#c output changed
994 !
994 !
995 Failed test-cases.t#b#c: output changed
995 Failed test-cases.t#b#c: output changed
996 # Ran 1 tests, 0 skipped, 1 failed.
996 # Ran 1 tests, 0 skipped, 1 failed.
997 python hash seed: * (glob)
997 python hash seed: * (glob)
998 [1]
998 [1]
999 $ rm test-cases.t#b#c.err
999 $ rm test-cases.t#b#c.err
1000 $ rm test-cases.t
1000 $ rm test-cases.t
1001
1001
1002 (reinstall)
1002 (reinstall)
1003 $ mv backup test-failure.t
1003 $ mv backup test-failure.t
1004
1004
1005 No Diff
1005 No Diff
1006 ===============
1006 ===============
1007
1007
1008 $ rt --nodiff
1008 $ rt --nodiff
1009 running 2 tests using 1 parallel processes
1009 running 2 tests using 1 parallel processes
1010 !.
1010 !.
1011 Failed test-failure.t: output changed
1011 Failed test-failure.t: output changed
1012 # Ran 2 tests, 0 skipped, 1 failed.
1012 # Ran 2 tests, 0 skipped, 1 failed.
1013 python hash seed: * (glob)
1013 python hash seed: * (glob)
1014 [1]
1014 [1]
1015
1015
1016 test --tmpdir support
1016 test --tmpdir support
1017 $ rt --tmpdir=$TESTTMP/keep test-success.t
1017 $ rt --tmpdir=$TESTTMP/keep test-success.t
1018 running 1 tests using 1 parallel processes
1018 running 1 tests using 1 parallel processes
1019
1019
1020 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1020 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1021 Keeping threadtmp dir: $TESTTMP/keep/child1
1021 Keeping threadtmp dir: $TESTTMP/keep/child1
1022 .
1022 .
1023 # Ran 1 tests, 0 skipped, 0 failed.
1023 # Ran 1 tests, 0 skipped, 0 failed.
1024
1024
1025 timeouts
1025 timeouts
1026 ========
1026 ========
1027 $ cat > test-timeout.t <<EOF
1027 $ cat > test-timeout.t <<EOF
1028 > $ sleep 2
1028 > $ sleep 2
1029 > $ echo pass
1029 > $ echo pass
1030 > pass
1030 > pass
1031 > EOF
1031 > EOF
1032 > echo '#require slow' > test-slow-timeout.t
1032 > echo '#require slow' > test-slow-timeout.t
1033 > cat test-timeout.t >> test-slow-timeout.t
1033 > cat test-timeout.t >> test-slow-timeout.t
1034 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1034 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1035 running 2 tests using 1 parallel processes
1035 running 2 tests using 1 parallel processes
1036 st
1036 st
1037 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1037 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1038 Failed test-timeout.t: timed out
1038 Failed test-timeout.t: timed out
1039 # Ran 1 tests, 1 skipped, 1 failed.
1039 # Ran 1 tests, 1 skipped, 1 failed.
1040 python hash seed: * (glob)
1040 python hash seed: * (glob)
1041 [1]
1041 [1]
1042 $ rt --timeout=1 --slowtimeout=3 \
1042 $ rt --timeout=1 --slowtimeout=3 \
1043 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1043 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1044 running 2 tests using 1 parallel processes
1044 running 2 tests using 1 parallel processes
1045 .t
1045 .t
1046 Failed test-timeout.t: timed out
1046 Failed test-timeout.t: timed out
1047 # Ran 2 tests, 0 skipped, 1 failed.
1047 # Ran 2 tests, 0 skipped, 1 failed.
1048 python hash seed: * (glob)
1048 python hash seed: * (glob)
1049 [1]
1049 [1]
1050 $ rm test-timeout.t test-slow-timeout.t
1050 $ rm test-timeout.t test-slow-timeout.t
1051
1051
1052 test for --time
1052 test for --time
1053 ==================
1053 ==================
1054
1054
1055 $ rt test-success.t --time
1055 $ rt test-success.t --time
1056 running 1 tests using 1 parallel processes
1056 running 1 tests using 1 parallel processes
1057 .
1057 .
1058 # Ran 1 tests, 0 skipped, 0 failed.
1058 # Ran 1 tests, 0 skipped, 0 failed.
1059 # Producing time report
1059 # Producing time report
1060 start end cuser csys real Test
1060 start end cuser csys real Test
1061 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1061 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1062
1062
1063 test for --time with --job enabled
1063 test for --time with --job enabled
1064 ====================================
1064 ====================================
1065
1065
1066 $ rt test-success.t --time --jobs 2
1066 $ rt test-success.t --time --jobs 2
1067 running 1 tests using 1 parallel processes
1067 running 1 tests using 1 parallel processes
1068 .
1068 .
1069 # Ran 1 tests, 0 skipped, 0 failed.
1069 # Ran 1 tests, 0 skipped, 0 failed.
1070 # Producing time report
1070 # Producing time report
1071 start end cuser csys real Test
1071 start end cuser csys real Test
1072 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1072 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1073
1073
1074 Skips
1074 Skips
1075 ================
1075 ================
1076 $ cat > test-skip.t <<EOF
1076 $ cat > test-skip.t <<EOF
1077 > $ echo xyzzy
1077 > $ echo xyzzy
1078 > #if true
1078 > #if true
1079 > #require false
1079 > #require false
1080 > #end
1080 > #end
1081 > EOF
1081 > EOF
1082 $ cat > test-noskip.t <<EOF
1082 $ cat > test-noskip.t <<EOF
1083 > #if false
1083 > #if false
1084 > #require false
1084 > #require false
1085 > #endif
1085 > #endif
1086 > EOF
1086 > EOF
1087 $ rt --nodiff
1087 $ rt --nodiff
1088 running 4 tests using 1 parallel processes
1088 running 4 tests using 1 parallel processes
1089 !.s.
1089 !.s.
1090 Skipped test-skip.t: missing feature: nail clipper
1090 Skipped test-skip.t: missing feature: nail clipper
1091 Failed test-failure.t: output changed
1091 Failed test-failure.t: output changed
1092 # Ran 3 tests, 1 skipped, 1 failed.
1092 # Ran 3 tests, 1 skipped, 1 failed.
1093 python hash seed: * (glob)
1093 python hash seed: * (glob)
1094 [1]
1094 [1]
1095
1095
1096 $ rm test-noskip.t
1096 $ rm test-noskip.t
1097 $ rt --keyword xyzzy
1097 $ rt --keyword xyzzy
1098 running 3 tests using 1 parallel processes
1098 running 3 tests using 1 parallel processes
1099 .s
1099 .s
1100 Skipped test-skip.t: missing feature: nail clipper
1100 Skipped test-skip.t: missing feature: nail clipper
1101 # Ran 2 tests, 2 skipped, 0 failed.
1101 # Ran 2 tests, 2 skipped, 0 failed.
1102
1102
1103 Skips with xml
1103 Skips with xml
1104 $ rt --keyword xyzzy \
1104 $ rt --keyword xyzzy \
1105 > --xunit=xunit.xml
1105 > --xunit=xunit.xml
1106 running 3 tests using 1 parallel processes
1106 running 3 tests using 1 parallel processes
1107 .s
1107 .s
1108 Skipped test-skip.t: missing feature: nail clipper
1108 Skipped test-skip.t: missing feature: nail clipper
1109 # Ran 2 tests, 2 skipped, 0 failed.
1109 # Ran 2 tests, 2 skipped, 0 failed.
1110 $ cat xunit.xml
1110 $ cat xunit.xml
1111 <?xml version="1.0" encoding="utf-8"?>
1111 <?xml version="1.0" encoding="utf-8"?>
1112 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1112 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1113 <testcase name="test-success.t" time="*"/> (glob)
1113 <testcase name="test-success.t" time="*"/> (glob)
1114 <testcase name="test-skip.t">
1114 <testcase name="test-skip.t">
1115 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1115 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1116 <skipped> (no-py38 !)
1116 <skipped> (no-py38 !)
1117 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1117 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1118 </testcase>
1118 </testcase>
1119 </testsuite>
1119 </testsuite>
1120
1120
1121 Missing skips or blacklisted skips don't count as executed:
1121 Missing skips or blacklisted skips don't count as executed:
1122 $ mkdir tests
1122 $ mkdir tests
1123 $ echo tests/test-failure.t > blacklist
1123 $ echo tests/test-failure.t > blacklist
1124 $ cp test-failure.t tests
1124 $ cp test-failure.t tests
1125 $ rt --blacklist=blacklist --json\
1125 $ rt --blacklist=blacklist --json\
1126 > tests/test-failure.t tests/test-bogus.t
1126 > tests/test-failure.t tests/test-bogus.t
1127 running 2 tests using 1 parallel processes
1127 running 2 tests using 1 parallel processes
1128 ss
1128 ss
1129 Skipped test-bogus.t: Doesn't exist
1129 Skipped test-bogus.t: Doesn't exist
1130 Skipped test-failure.t: blacklisted
1130 Skipped test-failure.t: blacklisted
1131 # Ran 0 tests, 2 skipped, 0 failed.
1131 # Ran 0 tests, 2 skipped, 0 failed.
1132 $ cat tests/report.json
1132 $ cat tests/report.json
1133 testreport ={
1133 testreport ={
1134 "test-bogus.t": {
1134 "test-bogus.t": {
1135 "result": "skip"
1135 "result": "skip"
1136 },
1136 },
1137 "test-failure.t": {
1137 "test-failure.t": {
1138 "result": "skip"
1138 "result": "skip"
1139 }
1139 }
1140 } (no-eol)
1140 } (no-eol)
1141 $ rm -r tests
1141 $ rm -r tests
1142 $ echo test-failure.t > blacklist
1142 $ echo test-failure.t > blacklist
1143
1143
1144 Whitelist trumps blacklist
1144 Whitelist trumps blacklist
1145 $ echo test-failure.t > whitelist
1145 $ echo test-failure.t > whitelist
1146 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1146 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1147 > test-failure.t test-bogus.t
1147 > test-failure.t test-bogus.t
1148 running 2 tests using 1 parallel processes
1148 running 2 tests using 1 parallel processes
1149 s
1149 s
1150 --- $TESTTMP/test-failure.t
1150 --- $TESTTMP/test-failure.t
1151 +++ $TESTTMP/test-failure.t.err
1151 +++ $TESTTMP/test-failure.t.err
1152 @@ -1,5 +1,5 @@
1152 @@ -1,5 +1,5 @@
1153 $ echo babar
1153 $ echo babar
1154 - rataxes
1154 - rataxes
1155 + babar
1155 + babar
1156 This is a noop statement so that
1156 This is a noop statement so that
1157 this test is still more bytes than success.
1157 this test is still more bytes than success.
1158 pad pad pad pad............................................................
1158 pad pad pad pad............................................................
1159
1159
1160 ERROR: test-failure.t output changed
1160 ERROR: test-failure.t output changed
1161 !
1161 !
1162 Skipped test-bogus.t: Doesn't exist
1162 Skipped test-bogus.t: Doesn't exist
1163 Failed test-failure.t: output changed
1163 Failed test-failure.t: output changed
1164 # Ran 1 tests, 1 skipped, 1 failed.
1164 # Ran 1 tests, 1 skipped, 1 failed.
1165 python hash seed: * (glob)
1165 python hash seed: * (glob)
1166 [1]
1166 [1]
1167
1167
1168 Ensure that --test-list causes only the tests listed in that file to
1168 Ensure that --test-list causes only the tests listed in that file to
1169 be executed.
1169 be executed.
1170 $ echo test-success.t >> onlytest
1170 $ echo test-success.t >> onlytest
1171 $ rt --test-list=onlytest
1171 $ rt --test-list=onlytest
1172 running 1 tests using 1 parallel processes
1172 running 1 tests using 1 parallel processes
1173 .
1173 .
1174 # Ran 1 tests, 0 skipped, 0 failed.
1174 # Ran 1 tests, 0 skipped, 0 failed.
1175 $ echo test-bogus.t >> anothertest
1175 $ echo test-bogus.t >> anothertest
1176 $ rt --test-list=onlytest --test-list=anothertest
1176 $ rt --test-list=onlytest --test-list=anothertest
1177 running 2 tests using 1 parallel processes
1177 running 2 tests using 1 parallel processes
1178 s.
1178 s.
1179 Skipped test-bogus.t: Doesn't exist
1179 Skipped test-bogus.t: Doesn't exist
1180 # Ran 1 tests, 1 skipped, 0 failed.
1180 # Ran 1 tests, 1 skipped, 0 failed.
1181 $ rm onlytest anothertest
1181 $ rm onlytest anothertest
1182
1182
1183 test for --json
1183 test for --json
1184 ==================
1184 ==================
1185
1185
1186 $ rt --json
1186 $ rt --json
1187 running 3 tests using 1 parallel processes
1187 running 3 tests using 1 parallel processes
1188
1188
1189 --- $TESTTMP/test-failure.t
1189 --- $TESTTMP/test-failure.t
1190 +++ $TESTTMP/test-failure.t.err
1190 +++ $TESTTMP/test-failure.t.err
1191 @@ -1,5 +1,5 @@
1191 @@ -1,5 +1,5 @@
1192 $ echo babar
1192 $ echo babar
1193 - rataxes
1193 - rataxes
1194 + babar
1194 + babar
1195 This is a noop statement so that
1195 This is a noop statement so that
1196 this test is still more bytes than success.
1196 this test is still more bytes than success.
1197 pad pad pad pad............................................................
1197 pad pad pad pad............................................................
1198
1198
1199 ERROR: test-failure.t output changed
1199 ERROR: test-failure.t output changed
1200 !.s
1200 !.s
1201 Skipped test-skip.t: missing feature: nail clipper
1201 Skipped test-skip.t: missing feature: nail clipper
1202 Failed test-failure.t: output changed
1202 Failed test-failure.t: output changed
1203 # Ran 2 tests, 1 skipped, 1 failed.
1203 # Ran 2 tests, 1 skipped, 1 failed.
1204 python hash seed: * (glob)
1204 python hash seed: * (glob)
1205 [1]
1205 [1]
1206
1206
1207 $ cat report.json
1207 $ cat report.json
1208 testreport ={
1208 testreport ={
1209 "test-failure.t": [\{] (re)
1209 "test-failure.t": [\{] (re)
1210 "csys": "\s*\d+\.\d{3,4}", ? (re)
1210 "csys": "\s*\d+\.\d{3,4}", ? (re)
1211 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1211 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1212 "diff": "---.+\+\+\+.+", ? (re)
1212 "diff": "---.+\+\+\+.+", ? (re)
1213 "end": "\s*\d+\.\d{3,4}", ? (re)
1213 "end": "\s*\d+\.\d{3,4}", ? (re)
1214 "result": "failure", ? (re)
1214 "result": "failure", ? (re)
1215 "start": "\s*\d+\.\d{3,4}", ? (re)
1215 "start": "\s*\d+\.\d{3,4}", ? (re)
1216 "time": "\s*\d+\.\d{3,4}" (re)
1216 "time": "\s*\d+\.\d{3,4}" (re)
1217 }, ? (re)
1217 }, ? (re)
1218 "test-skip.t": {
1218 "test-skip.t": {
1219 "csys": "\s*\d+\.\d{3,4}", ? (re)
1219 "csys": "\s*\d+\.\d{3,4}", ? (re)
1220 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1220 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1221 "diff": "", ? (re)
1221 "diff": "", ? (re)
1222 "end": "\s*\d+\.\d{3,4}", ? (re)
1222 "end": "\s*\d+\.\d{3,4}", ? (re)
1223 "result": "skip", ? (re)
1223 "result": "skip", ? (re)
1224 "start": "\s*\d+\.\d{3,4}", ? (re)
1224 "start": "\s*\d+\.\d{3,4}", ? (re)
1225 "time": "\s*\d+\.\d{3,4}" (re)
1225 "time": "\s*\d+\.\d{3,4}" (re)
1226 }, ? (re)
1226 }, ? (re)
1227 "test-success.t": [\{] (re)
1227 "test-success.t": [\{] (re)
1228 "csys": "\s*\d+\.\d{3,4}", ? (re)
1228 "csys": "\s*\d+\.\d{3,4}", ? (re)
1229 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1229 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1230 "diff": "", ? (re)
1230 "diff": "", ? (re)
1231 "end": "\s*\d+\.\d{3,4}", ? (re)
1231 "end": "\s*\d+\.\d{3,4}", ? (re)
1232 "result": "success", ? (re)
1232 "result": "success", ? (re)
1233 "start": "\s*\d+\.\d{3,4}", ? (re)
1233 "start": "\s*\d+\.\d{3,4}", ? (re)
1234 "time": "\s*\d+\.\d{3,4}" (re)
1234 "time": "\s*\d+\.\d{3,4}" (re)
1235 }
1235 }
1236 } (no-eol)
1236 } (no-eol)
1237 --json with --outputdir
1237 --json with --outputdir
1238
1238
1239 $ rm report.json
1239 $ rm report.json
1240 $ rm -r output
1240 $ rm -r output
1241 $ mkdir output
1241 $ mkdir output
1242 $ rt --json --outputdir output
1242 $ rt --json --outputdir output
1243 running 3 tests using 1 parallel processes
1243 running 3 tests using 1 parallel processes
1244
1244
1245 --- $TESTTMP/test-failure.t
1245 --- $TESTTMP/test-failure.t
1246 +++ $TESTTMP/output/test-failure.t.err
1246 +++ $TESTTMP/output/test-failure.t.err
1247 @@ -1,5 +1,5 @@
1247 @@ -1,5 +1,5 @@
1248 $ echo babar
1248 $ echo babar
1249 - rataxes
1249 - rataxes
1250 + babar
1250 + babar
1251 This is a noop statement so that
1251 This is a noop statement so that
1252 this test is still more bytes than success.
1252 this test is still more bytes than success.
1253 pad pad pad pad............................................................
1253 pad pad pad pad............................................................
1254
1254
1255 ERROR: test-failure.t output changed
1255 ERROR: test-failure.t output changed
1256 !.s
1256 !.s
1257 Skipped test-skip.t: missing feature: nail clipper
1257 Skipped test-skip.t: missing feature: nail clipper
1258 Failed test-failure.t: output changed
1258 Failed test-failure.t: output changed
1259 # Ran 2 tests, 1 skipped, 1 failed.
1259 # Ran 2 tests, 1 skipped, 1 failed.
1260 python hash seed: * (glob)
1260 python hash seed: * (glob)
1261 [1]
1261 [1]
1262 $ f report.json
1262 $ f report.json
1263 report.json: file not found
1263 report.json: file not found
1264 $ cat output/report.json
1264 $ cat output/report.json
1265 testreport ={
1265 testreport ={
1266 "test-failure.t": [\{] (re)
1266 "test-failure.t": [\{] (re)
1267 "csys": "\s*\d+\.\d{3,4}", ? (re)
1267 "csys": "\s*\d+\.\d{3,4}", ? (re)
1268 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1268 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1269 "diff": "---.+\+\+\+.+", ? (re)
1269 "diff": "---.+\+\+\+.+", ? (re)
1270 "end": "\s*\d+\.\d{3,4}", ? (re)
1270 "end": "\s*\d+\.\d{3,4}", ? (re)
1271 "result": "failure", ? (re)
1271 "result": "failure", ? (re)
1272 "start": "\s*\d+\.\d{3,4}", ? (re)
1272 "start": "\s*\d+\.\d{3,4}", ? (re)
1273 "time": "\s*\d+\.\d{3,4}" (re)
1273 "time": "\s*\d+\.\d{3,4}" (re)
1274 }, ? (re)
1274 }, ? (re)
1275 "test-skip.t": {
1275 "test-skip.t": {
1276 "csys": "\s*\d+\.\d{3,4}", ? (re)
1276 "csys": "\s*\d+\.\d{3,4}", ? (re)
1277 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1277 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1278 "diff": "", ? (re)
1278 "diff": "", ? (re)
1279 "end": "\s*\d+\.\d{3,4}", ? (re)
1279 "end": "\s*\d+\.\d{3,4}", ? (re)
1280 "result": "skip", ? (re)
1280 "result": "skip", ? (re)
1281 "start": "\s*\d+\.\d{3,4}", ? (re)
1281 "start": "\s*\d+\.\d{3,4}", ? (re)
1282 "time": "\s*\d+\.\d{3,4}" (re)
1282 "time": "\s*\d+\.\d{3,4}" (re)
1283 }, ? (re)
1283 }, ? (re)
1284 "test-success.t": [\{] (re)
1284 "test-success.t": [\{] (re)
1285 "csys": "\s*\d+\.\d{3,4}", ? (re)
1285 "csys": "\s*\d+\.\d{3,4}", ? (re)
1286 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1286 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1287 "diff": "", ? (re)
1287 "diff": "", ? (re)
1288 "end": "\s*\d+\.\d{3,4}", ? (re)
1288 "end": "\s*\d+\.\d{3,4}", ? (re)
1289 "result": "success", ? (re)
1289 "result": "success", ? (re)
1290 "start": "\s*\d+\.\d{3,4}", ? (re)
1290 "start": "\s*\d+\.\d{3,4}", ? (re)
1291 "time": "\s*\d+\.\d{3,4}" (re)
1291 "time": "\s*\d+\.\d{3,4}" (re)
1292 }
1292 }
1293 } (no-eol)
1293 } (no-eol)
1294 $ ls -a output
1294 $ ls -a output
1295 .
1295 .
1296 ..
1296 ..
1297 .testtimes
1297 .testtimes
1298 report.json
1298 report.json
1299 test-failure.t.err
1299 test-failure.t.err
1300
1300
1301 Test that failed test accepted through interactive are properly reported:
1301 Test that failed test accepted through interactive are properly reported:
1302
1302
1303 $ cp test-failure.t backup
1303 $ cp test-failure.t backup
1304 $ echo y | rt --json -i
1304 $ echo y | rt --json -i
1305 running 3 tests using 1 parallel processes
1305 running 3 tests using 1 parallel processes
1306
1306
1307 --- $TESTTMP/test-failure.t
1307 --- $TESTTMP/test-failure.t
1308 +++ $TESTTMP/test-failure.t.err
1308 +++ $TESTTMP/test-failure.t.err
1309 @@ -1,5 +1,5 @@
1309 @@ -1,5 +1,5 @@
1310 $ echo babar
1310 $ echo babar
1311 - rataxes
1311 - rataxes
1312 + babar
1312 + babar
1313 This is a noop statement so that
1313 This is a noop statement so that
1314 this test is still more bytes than success.
1314 this test is still more bytes than success.
1315 pad pad pad pad............................................................
1315 pad pad pad pad............................................................
1316 Accept this change? [y/N] ..s
1316 Accept this change? [y/N] ..s
1317 Skipped test-skip.t: missing feature: nail clipper
1317 Skipped test-skip.t: missing feature: nail clipper
1318 # Ran 2 tests, 1 skipped, 0 failed.
1318 # Ran 2 tests, 1 skipped, 0 failed.
1319
1319
1320 $ cat report.json
1320 $ cat report.json
1321 testreport ={
1321 testreport ={
1322 "test-failure.t": [\{] (re)
1322 "test-failure.t": [\{] (re)
1323 "csys": "\s*\d+\.\d{3,4}", ? (re)
1323 "csys": "\s*\d+\.\d{3,4}", ? (re)
1324 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1324 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1325 "diff": "", ? (re)
1325 "diff": "", ? (re)
1326 "end": "\s*\d+\.\d{3,4}", ? (re)
1326 "end": "\s*\d+\.\d{3,4}", ? (re)
1327 "result": "success", ? (re)
1327 "result": "success", ? (re)
1328 "start": "\s*\d+\.\d{3,4}", ? (re)
1328 "start": "\s*\d+\.\d{3,4}", ? (re)
1329 "time": "\s*\d+\.\d{3,4}" (re)
1329 "time": "\s*\d+\.\d{3,4}" (re)
1330 }, ? (re)
1330 }, ? (re)
1331 "test-skip.t": {
1331 "test-skip.t": {
1332 "csys": "\s*\d+\.\d{3,4}", ? (re)
1332 "csys": "\s*\d+\.\d{3,4}", ? (re)
1333 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1333 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1334 "diff": "", ? (re)
1334 "diff": "", ? (re)
1335 "end": "\s*\d+\.\d{3,4}", ? (re)
1335 "end": "\s*\d+\.\d{3,4}", ? (re)
1336 "result": "skip", ? (re)
1336 "result": "skip", ? (re)
1337 "start": "\s*\d+\.\d{3,4}", ? (re)
1337 "start": "\s*\d+\.\d{3,4}", ? (re)
1338 "time": "\s*\d+\.\d{3,4}" (re)
1338 "time": "\s*\d+\.\d{3,4}" (re)
1339 }, ? (re)
1339 }, ? (re)
1340 "test-success.t": [\{] (re)
1340 "test-success.t": [\{] (re)
1341 "csys": "\s*\d+\.\d{3,4}", ? (re)
1341 "csys": "\s*\d+\.\d{3,4}", ? (re)
1342 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1342 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1343 "diff": "", ? (re)
1343 "diff": "", ? (re)
1344 "end": "\s*\d+\.\d{3,4}", ? (re)
1344 "end": "\s*\d+\.\d{3,4}", ? (re)
1345 "result": "success", ? (re)
1345 "result": "success", ? (re)
1346 "start": "\s*\d+\.\d{3,4}", ? (re)
1346 "start": "\s*\d+\.\d{3,4}", ? (re)
1347 "time": "\s*\d+\.\d{3,4}" (re)
1347 "time": "\s*\d+\.\d{3,4}" (re)
1348 }
1348 }
1349 } (no-eol)
1349 } (no-eol)
1350 $ mv backup test-failure.t
1350 $ mv backup test-failure.t
1351
1351
1352 backslash on end of line with glob matching is handled properly
1352 backslash on end of line with glob matching is handled properly
1353
1353
1354 $ cat > test-glob-backslash.t << EOF
1354 $ cat > test-glob-backslash.t << EOF
1355 > $ echo 'foo bar \\'
1355 > $ echo 'foo bar \\'
1356 > foo * \ (glob)
1356 > foo * \ (glob)
1357 > EOF
1357 > EOF
1358
1358
1359 $ rt test-glob-backslash.t
1359 $ rt test-glob-backslash.t
1360 running 1 tests using 1 parallel processes
1360 running 1 tests using 1 parallel processes
1361 .
1361 .
1362 # Ran 1 tests, 0 skipped, 0 failed.
1362 # Ran 1 tests, 0 skipped, 0 failed.
1363
1363
1364 $ rm -f test-glob-backslash.t
1364 $ rm -f test-glob-backslash.t
1365
1365
1366 Test globbing of local IP addresses
1366 Test globbing of local IP addresses
1367 $ echo 172.16.18.1
1367 $ echo 172.16.18.1
1368 $LOCALIP (glob)
1368 $LOCALIP (glob)
1369 $ echo dead:beef::1
1369 $ echo dead:beef::1
1370 $LOCALIP (glob)
1370 $LOCALIP (glob)
1371
1371
1372 Add support for external test formatter
1372 Add support for external test formatter
1373 =======================================
1373 =======================================
1374
1374
1375 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@" test-success.t test-failure.t
1375 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@" test-success.t test-failure.t
1376 running 2 tests using 1 parallel processes
1376 running 2 tests using 1 parallel processes
1377
1377
1378 # Ran 2 tests, 0 skipped, 0 failed.
1378 # Ran 2 tests, 0 skipped, 0 failed.
1379 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1379 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1380 FAILURE! test-failure.t output changed
1380 FAILURE! test-failure.t output changed
1381 SUCCESS! test-success.t
1381 SUCCESS! test-success.t
1382 ON_END!
1382 ON_END!
1383
1383
1384 Test reusability for third party tools
1384 Test reusability for third party tools
1385 ======================================
1385 ======================================
1386
1386
1387 $ THISTESTDIR="$TESTDIR"
1387 $ THISTESTDIR="$TESTDIR"
1388 $ export THISTESTDIR
1388 $ export THISTESTDIR
1389 $ THISTESTTMP="$TESTTMP"
1389 $ THISTESTTMP="$TESTTMP"
1390 $ export THISTESTTMP
1390 $ export THISTESTTMP
1391
1391
1392 #if windows
1392 #if windows
1393
1393
1394 $ NEWTESTDIR="$THISTESTTMP"\\anothertests
1394 $ NEWTESTDIR="$THISTESTTMP"\\anothertests
1395
1395
1396 #else
1396 #else
1397
1397
1398 $ NEWTESTDIR="$THISTESTTMP"/anothertests
1398 $ NEWTESTDIR="$THISTESTTMP"/anothertests
1399
1399
1400 #endif
1400 #endif
1401
1401
1402 $ export NEWTESTDIR
1402 $ export NEWTESTDIR
1403
1403
1404 $ echo creating some new test in: $NEWTESTDIR
1404 $ echo creating some new test in: $NEWTESTDIR
1405 creating some new test in: $TESTTMP\anothertests (windows !)
1405 creating some new test in: $TESTTMP\anothertests (windows !)
1406 creating some new test in: $TESTTMP/anothertests (no-windows !)
1406 creating some new test in: $TESTTMP/anothertests (no-windows !)
1407 $ mkdir "$NEWTESTDIR"
1407 $ mkdir "$NEWTESTDIR"
1408 $ cd "$NEWTESTDIR"
1408 $ cd "$NEWTESTDIR"
1409
1409
1410 test that `run-tests.py` can execute hghave, even if it runs not in
1410 test that `run-tests.py` can execute hghave, even if it runs not in
1411 Mercurial source tree.
1411 Mercurial source tree.
1412
1412
1413 $ cat > test-hghave.t <<EOF
1413 $ cat > test-hghave.t <<EOF
1414 > #require true
1414 > #require true
1415 > $ echo foo
1415 > $ echo foo
1416 > foo
1416 > foo
1417 > EOF
1417 > EOF
1418 $ rt test-hghave.t
1418 $ rt test-hghave.t
1419 running 1 tests using 1 parallel processes
1419 running 1 tests using 1 parallel processes
1420 .
1420 .
1421 # Ran 1 tests, 0 skipped, 0 failed.
1421 # Ran 1 tests, 0 skipped, 0 failed.
1422
1422
1423 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1423 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1424 running is placed.
1424 running is placed.
1425
1425
1426
1426
1427 $ cat > test-runtestdir.t <<EOF
1427 $ cat > test-runtestdir.t <<EOF
1428 > # \$THISTESTDIR, in which test-run-tests.t (this test file) is placed
1428 > # \$THISTESTDIR, in which test-run-tests.t (this test file) is placed
1429 > # \$THISTESTTMP, in which test-run-tests.t (this test file) is placed
1429 > # \$THISTESTTMP, in which test-run-tests.t (this test file) is placed
1430 > # \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1430 > # \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1431 > # \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1431 > # \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1432 >
1432 >
1433 > $ test "\$TESTDIR" = "\$NEWTESTDIR"
1433 > $ test "\$TESTDIR" = "\$NEWTESTDIR"
1434 > If this prints a path, that means RUNTESTDIR didn't equal
1434 > If this prints a path, that means RUNTESTDIR didn't equal
1435 > THISTESTDIR as it should have.
1435 > THISTESTDIR as it should have.
1436 > $ test "\$RUNTESTDIR" = "\$THISTESTDIR" || echo "\$RUNTESTDIR"
1436 > $ test "\$RUNTESTDIR" = "\$THISTESTDIR" || echo "\$RUNTESTDIR"
1437 > This should print the start of check-code. If this passes but the
1437 > This should print the start of check-code. If this passes but the
1438 > previous check failed, that means we found a copy of check-code at whatever
1438 > previous check failed, that means we found a copy of check-code at whatever
1439 > RUNTESTSDIR ended up containing, even though it doesn't match THISTESTDIR.
1439 > RUNTESTSDIR ended up containing, even though it doesn't match THISTESTDIR.
1440 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1440 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1441 > #!USRBINENVPY
1441 > #!USRBINENVPY
1442 > #
1442 > #
1443 > # check-code - a style and portability checker for Mercurial
1443 > # check-code - a style and portability checker for Mercurial
1444 > EOF
1444 > EOF
1445 $ rt test-runtestdir.t
1445 $ rt test-runtestdir.t
1446 running 1 tests using 1 parallel processes
1446 running 1 tests using 1 parallel processes
1447 .
1447 .
1448 # Ran 1 tests, 0 skipped, 0 failed.
1448 # Ran 1 tests, 0 skipped, 0 failed.
1449
1449
1450 #if execbit
1450 #if execbit
1451
1451
1452 test that TESTDIR is referred in PATH
1452 test that TESTDIR is referred in PATH
1453
1453
1454 $ cat > custom-command.sh <<EOF
1454 $ cat > custom-command.sh <<EOF
1455 > #!/bin/sh
1455 > #!/bin/sh
1456 > echo "hello world"
1456 > echo "hello world"
1457 > EOF
1457 > EOF
1458 $ chmod +x custom-command.sh
1458 $ chmod +x custom-command.sh
1459 $ cat > test-testdir-path.t <<EOF
1459 $ cat > test-testdir-path.t <<EOF
1460 > $ custom-command.sh
1460 > $ custom-command.sh
1461 > hello world
1461 > hello world
1462 > EOF
1462 > EOF
1463 $ rt test-testdir-path.t
1463 $ rt test-testdir-path.t
1464 running 1 tests using 1 parallel processes
1464 running 1 tests using 1 parallel processes
1465 .
1465 .
1466 # Ran 1 tests, 0 skipped, 0 failed.
1466 # Ran 1 tests, 0 skipped, 0 failed.
1467
1467
1468 #endif
1468 #endif
1469
1469
1470 test support for --allow-slow-tests
1470 test support for --allow-slow-tests
1471 $ cat > test-very-slow-test.t <<EOF
1471 $ cat > test-very-slow-test.t <<EOF
1472 > #require slow
1472 > #require slow
1473 > $ echo pass
1473 > $ echo pass
1474 > pass
1474 > pass
1475 > EOF
1475 > EOF
1476 $ rt test-very-slow-test.t
1476 $ rt test-very-slow-test.t
1477 running 1 tests using 1 parallel processes
1477 running 1 tests using 1 parallel processes
1478 s
1478 s
1479 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1479 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1480 # Ran 0 tests, 1 skipped, 0 failed.
1480 # Ran 0 tests, 1 skipped, 0 failed.
1481 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1481 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1482 running 1 tests using 1 parallel processes
1482 running 1 tests using 1 parallel processes
1483 .
1483 .
1484 # Ran 1 tests, 0 skipped, 0 failed.
1484 # Ran 1 tests, 0 skipped, 0 failed.
1485
1485
1486 support for running a test outside the current directory
1486 support for running a test outside the current directory
1487 $ mkdir nonlocal
1487 $ mkdir nonlocal
1488 $ cat > nonlocal/test-is-not-here.t << EOF
1488 $ cat > nonlocal/test-is-not-here.t << EOF
1489 > $ echo pass
1489 > $ echo pass
1490 > pass
1490 > pass
1491 > EOF
1491 > EOF
1492 $ rt nonlocal/test-is-not-here.t
1492 $ rt nonlocal/test-is-not-here.t
1493 running 1 tests using 1 parallel processes
1493 running 1 tests using 1 parallel processes
1494 .
1494 .
1495 # Ran 1 tests, 0 skipped, 0 failed.
1495 # Ran 1 tests, 0 skipped, 0 failed.
1496
1496
1497 support for automatically discovering test if arg is a folder
1497 support for automatically discovering test if arg is a folder
1498 $ mkdir tmp && cd tmp
1498 $ mkdir tmp && cd tmp
1499
1499
1500 $ cat > test-uno.t << EOF
1500 $ cat > test-uno.t << EOF
1501 > $ echo line
1501 > $ echo line
1502 > line
1502 > line
1503 > EOF
1503 > EOF
1504
1504
1505 $ cp test-uno.t test-dos.t
1505 $ cp test-uno.t test-dos.t
1506 $ cd ..
1506 $ cd ..
1507 $ cp -R tmp tmpp
1507 $ cp -R tmp tmpp
1508 $ cp tmp/test-uno.t test-solo.t
1508 $ cp tmp/test-uno.t test-solo.t
1509
1509
1510 $ rt tmp/ test-solo.t tmpp
1510 $ rt tmp/ test-solo.t tmpp
1511 running 5 tests using 1 parallel processes
1511 running 5 tests using 1 parallel processes
1512 .....
1512 .....
1513 # Ran 5 tests, 0 skipped, 0 failed.
1513 # Ran 5 tests, 0 skipped, 0 failed.
1514 $ rm -rf tmp tmpp
1514 $ rm -rf tmp tmpp
1515
1515
1516 support for running run-tests.py from another directory
1516 support for running run-tests.py from another directory
1517 $ mkdir tmp && cd tmp
1517 $ mkdir tmp && cd tmp
1518
1518
1519 $ cat > useful-file.sh << EOF
1519 $ cat > useful-file.sh << EOF
1520 > important command
1520 > important command
1521 > EOF
1521 > EOF
1522
1522
1523 $ cat > test-folder.t << EOF
1523 $ cat > test-folder.t << EOF
1524 > $ cat \$TESTDIR/useful-file.sh
1524 > $ cat \$TESTDIR/useful-file.sh
1525 > important command
1525 > important command
1526 > EOF
1526 > EOF
1527
1527
1528 $ cat > test-folder-fail.t << EOF
1528 $ cat > test-folder-fail.t << EOF
1529 > $ cat \$TESTDIR/useful-file.sh
1529 > $ cat \$TESTDIR/useful-file.sh
1530 > important commando
1530 > important commando
1531 > EOF
1531 > EOF
1532
1532
1533 $ cd ..
1533 $ cd ..
1534 $ rt tmp/test-*.t
1534 $ rt tmp/test-*.t
1535 running 2 tests using 1 parallel processes
1535 running 2 tests using 1 parallel processes
1536
1536
1537 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1537 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1538 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1538 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1539 @@ -1,2 +1,2 @@
1539 @@ -1,2 +1,2 @@
1540 $ cat $TESTDIR/useful-file.sh
1540 $ cat $TESTDIR/useful-file.sh
1541 - important commando
1541 - important commando
1542 + important command
1542 + important command
1543
1543
1544 ERROR: test-folder-fail.t output changed
1544 ERROR: test-folder-fail.t output changed
1545 !.
1545 !.
1546 Failed test-folder-fail.t: output changed
1546 Failed test-folder-fail.t: output changed
1547 # Ran 2 tests, 0 skipped, 1 failed.
1547 # Ran 2 tests, 0 skipped, 1 failed.
1548 python hash seed: * (glob)
1548 python hash seed: * (glob)
1549 [1]
1549 [1]
1550
1550
1551 support for bisecting failed tests automatically
1551 support for bisecting failed tests automatically
1552 $ hg init bisect
1552 $ hg init bisect
1553 $ cd bisect
1553 $ cd bisect
1554 $ cat >> test-bisect.t <<EOF
1554 $ cat >> test-bisect.t <<EOF
1555 > $ echo pass
1555 > $ echo pass
1556 > pass
1556 > pass
1557 > EOF
1557 > EOF
1558 $ hg add test-bisect.t
1558 $ hg add test-bisect.t
1559 $ hg ci -m 'good'
1559 $ hg ci -m 'good'
1560 $ cat >> test-bisect.t <<EOF
1560 $ cat >> test-bisect.t <<EOF
1561 > $ echo pass
1561 > $ echo pass
1562 > fail
1562 > fail
1563 > EOF
1563 > EOF
1564 $ hg ci -m 'bad'
1564 $ hg ci -m 'bad'
1565 $ rt --known-good-rev=0 test-bisect.t
1565 $ rt --known-good-rev=0 test-bisect.t
1566 running 1 tests using 1 parallel processes
1566 running 1 tests using 1 parallel processes
1567
1567
1568 --- $TESTTMP/anothertests/bisect/test-bisect.t
1568 --- $TESTTMP/anothertests/bisect/test-bisect.t
1569 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1569 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1570 @@ -1,4 +1,4 @@
1570 @@ -1,4 +1,4 @@
1571 $ echo pass
1571 $ echo pass
1572 pass
1572 pass
1573 $ echo pass
1573 $ echo pass
1574 - fail
1574 - fail
1575 + pass
1575 + pass
1576
1576
1577 ERROR: test-bisect.t output changed
1577 ERROR: test-bisect.t output changed
1578 !
1578 !
1579 Failed test-bisect.t: output changed
1579 Failed test-bisect.t: output changed
1580 test-bisect.t broken by 72cbf122d116 (bad)
1580 test-bisect.t broken by 72cbf122d116 (bad)
1581 # Ran 1 tests, 0 skipped, 1 failed.
1581 # Ran 1 tests, 0 skipped, 1 failed.
1582 python hash seed: * (glob)
1582 python hash seed: * (glob)
1583 [1]
1583 [1]
1584
1584
1585 $ cd ..
1585 $ cd ..
1586
1586
1587 support bisecting a separate repo
1587 support bisecting a separate repo
1588
1588
1589 $ hg init bisect-dependent
1589 $ hg init bisect-dependent
1590 $ cd bisect-dependent
1590 $ cd bisect-dependent
1591 $ cat > test-bisect-dependent.t <<EOF
1591 $ cat > test-bisect-dependent.t <<EOF
1592 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1592 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1593 > pass
1593 > pass
1594 > EOF
1594 > EOF
1595 $ hg commit -Am dependent test-bisect-dependent.t
1595 $ hg commit -Am dependent test-bisect-dependent.t
1596
1596
1597 $ rt --known-good-rev=0 test-bisect-dependent.t
1597 $ rt --known-good-rev=0 test-bisect-dependent.t
1598 running 1 tests using 1 parallel processes
1598 running 1 tests using 1 parallel processes
1599
1599
1600 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1600 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1601 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1601 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1602 @@ -1,2 +1,2 @@
1602 @@ -1,2 +1,2 @@
1603 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1603 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1604 - pass
1604 - pass
1605 + fail
1605 + fail
1606
1606
1607 ERROR: test-bisect-dependent.t output changed
1607 ERROR: test-bisect-dependent.t output changed
1608 !
1608 !
1609 Failed test-bisect-dependent.t: output changed
1609 Failed test-bisect-dependent.t: output changed
1610 Failed to identify failure point for test-bisect-dependent.t
1610 Failed to identify failure point for test-bisect-dependent.t
1611 # Ran 1 tests, 0 skipped, 1 failed.
1611 # Ran 1 tests, 0 skipped, 1 failed.
1612 python hash seed: * (glob)
1612 python hash seed: * (glob)
1613 [1]
1613 [1]
1614
1614
1615 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1615 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1616 usage: run-tests.py [options] [tests]
1616 usage: run-tests.py [options] [tests]
1617 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1617 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1618 [2]
1618 [2]
1619
1619
1620 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1620 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1621 running 1 tests using 1 parallel processes
1621 running 1 tests using 1 parallel processes
1622
1622
1623 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1623 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1624 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1624 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1625 @@ -1,2 +1,2 @@
1625 @@ -1,2 +1,2 @@
1626 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1626 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1627 - pass
1627 - pass
1628 + fail
1628 + fail
1629
1629
1630 ERROR: test-bisect-dependent.t output changed
1630 ERROR: test-bisect-dependent.t output changed
1631 !
1631 !
1632 Failed test-bisect-dependent.t: output changed
1632 Failed test-bisect-dependent.t: output changed
1633 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1633 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1634 # Ran 1 tests, 0 skipped, 1 failed.
1634 # Ran 1 tests, 0 skipped, 1 failed.
1635 python hash seed: * (glob)
1635 python hash seed: * (glob)
1636 [1]
1636 [1]
1637
1637
1638 $ cd ..
1638 $ cd ..
1639
1639
1640 Test a broken #if statement doesn't break run-tests threading.
1640 Test a broken #if statement doesn't break run-tests threading.
1641 ==============================================================
1641 ==============================================================
1642 $ mkdir broken
1642 $ mkdir broken
1643 $ cd broken
1643 $ cd broken
1644 $ cat > test-broken.t <<EOF
1644 $ cat > test-broken.t <<EOF
1645 > true
1645 > true
1646 > #if notarealhghavefeature
1646 > #if notarealhghavefeature
1647 > $ false
1647 > $ false
1648 > #endif
1648 > #endif
1649 > EOF
1649 > EOF
1650 $ for f in 1 2 3 4 ; do
1650 $ for f in 1 2 3 4 ; do
1651 > cat > test-works-$f.t <<EOF
1651 > cat > test-works-$f.t <<EOF
1652 > This is test case $f
1652 > This is test case $f
1653 > $ sleep 1
1653 > $ sleep 1
1654 > EOF
1654 > EOF
1655 > done
1655 > done
1656 $ rt -j 2
1656 $ rt -j 2
1657 running 5 tests using 2 parallel processes
1657 running 5 tests using 2 parallel processes
1658 ....
1658 ....
1659 # Ran 5 tests, 0 skipped, 0 failed.
1659 # Ran 5 tests, 0 skipped, 0 failed.
1660 skipped: unknown feature: notarealhghavefeature
1660 skipped: unknown feature: notarealhghavefeature
1661
1661
1662 $ cd ..
1662 $ cd ..
1663 $ rm -rf broken
1663 $ rm -rf broken
1664
1664
1665 Test cases in .t files
1665 Test cases in .t files
1666 ======================
1666 ======================
1667 $ mkdir cases
1667 $ mkdir cases
1668 $ cd cases
1668 $ cd cases
1669 $ cat > test-cases-abc.t <<'EOF'
1669 $ cat > test-cases-abc.t <<'EOF'
1670 > #testcases A B C
1670 > #testcases A B C
1671 > $ V=B
1671 > $ V=B
1672 > #if A
1672 > #if A
1673 > $ V=A
1673 > $ V=A
1674 > #endif
1674 > #endif
1675 > #if C
1675 > #if C
1676 > $ V=C
1676 > $ V=C
1677 > #endif
1677 > #endif
1678 > $ echo $V | sed 's/A/C/'
1678 > $ echo $V | sed 's/A/C/'
1679 > C
1679 > C
1680 > #if C
1680 > #if C
1681 > $ [ $V = C ]
1681 > $ [ $V = C ]
1682 > #endif
1682 > #endif
1683 > #if A
1683 > #if A
1684 > $ [ $V = C ]
1684 > $ [ $V = C ]
1685 > [1]
1685 > [1]
1686 > #endif
1686 > #endif
1687 > #if no-C
1687 > #if no-C
1688 > $ [ $V = C ]
1688 > $ [ $V = C ]
1689 > [1]
1689 > [1]
1690 > #endif
1690 > #endif
1691 > $ [ $V = D ]
1691 > $ [ $V = D ]
1692 > [1]
1692 > [1]
1693 > EOF
1693 > EOF
1694 $ rt
1694 $ rt
1695 running 3 tests using 1 parallel processes
1695 running 3 tests using 1 parallel processes
1696 .
1696 .
1697 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1697 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1698 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1698 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1699 @@ -7,7 +7,7 @@
1699 @@ -7,7 +7,7 @@
1700 $ V=C
1700 $ V=C
1701 #endif
1701 #endif
1702 $ echo $V | sed 's/A/C/'
1702 $ echo $V | sed 's/A/C/'
1703 - C
1703 - C
1704 + B
1704 + B
1705 #if C
1705 #if C
1706 $ [ $V = C ]
1706 $ [ $V = C ]
1707 #endif
1707 #endif
1708
1708
1709 ERROR: test-cases-abc.t#B output changed
1709 ERROR: test-cases-abc.t#B output changed
1710 !.
1710 !.
1711 Failed test-cases-abc.t#B: output changed
1711 Failed test-cases-abc.t#B: output changed
1712 # Ran 3 tests, 0 skipped, 1 failed.
1712 # Ran 3 tests, 0 skipped, 1 failed.
1713 python hash seed: * (glob)
1713 python hash seed: * (glob)
1714 [1]
1714 [1]
1715
1715
1716 --restart works
1716 --restart works
1717
1717
1718 $ rt --restart
1718 $ rt --restart
1719 running 2 tests using 1 parallel processes
1719 running 2 tests using 1 parallel processes
1720
1720
1721 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1721 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1722 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1722 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1723 @@ -7,7 +7,7 @@
1723 @@ -7,7 +7,7 @@
1724 $ V=C
1724 $ V=C
1725 #endif
1725 #endif
1726 $ echo $V | sed 's/A/C/'
1726 $ echo $V | sed 's/A/C/'
1727 - C
1727 - C
1728 + B
1728 + B
1729 #if C
1729 #if C
1730 $ [ $V = C ]
1730 $ [ $V = C ]
1731 #endif
1731 #endif
1732
1732
1733 ERROR: test-cases-abc.t#B output changed
1733 ERROR: test-cases-abc.t#B output changed
1734 !.
1734 !.
1735 Failed test-cases-abc.t#B: output changed
1735 Failed test-cases-abc.t#B: output changed
1736 # Ran 2 tests, 0 skipped, 1 failed.
1736 # Ran 2 tests, 0 skipped, 1 failed.
1737 python hash seed: * (glob)
1737 python hash seed: * (glob)
1738 [1]
1738 [1]
1739
1739
1740 --restart works with outputdir
1740 --restart works with outputdir
1741
1741
1742 $ mkdir output
1742 $ mkdir output
1743 $ mv test-cases-abc.t#B.err output
1743 $ mv test-cases-abc.t#B.err output
1744 $ rt --restart --outputdir output
1744 $ rt --restart --outputdir output
1745 running 2 tests using 1 parallel processes
1745 running 2 tests using 1 parallel processes
1746
1746
1747 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1747 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1748 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1748 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1749 @@ -7,7 +7,7 @@
1749 @@ -7,7 +7,7 @@
1750 $ V=C
1750 $ V=C
1751 #endif
1751 #endif
1752 $ echo $V | sed 's/A/C/'
1752 $ echo $V | sed 's/A/C/'
1753 - C
1753 - C
1754 + B
1754 + B
1755 #if C
1755 #if C
1756 $ [ $V = C ]
1756 $ [ $V = C ]
1757 #endif
1757 #endif
1758
1758
1759 ERROR: test-cases-abc.t#B output changed
1759 ERROR: test-cases-abc.t#B output changed
1760 !.
1760 !.
1761 Failed test-cases-abc.t#B: output changed
1761 Failed test-cases-abc.t#B: output changed
1762 # Ran 2 tests, 0 skipped, 1 failed.
1762 # Ran 2 tests, 0 skipped, 1 failed.
1763 python hash seed: * (glob)
1763 python hash seed: * (glob)
1764 [1]
1764 [1]
1765
1765
1766 Test TESTCASE variable
1766 Test TESTCASE variable
1767
1767
1768 $ cat > test-cases-ab.t <<'EOF'
1768 $ cat > test-cases-ab.t <<'EOF'
1769 > $ dostuff() {
1769 > $ dostuff() {
1770 > > echo "In case $TESTCASE"
1770 > > echo "In case $TESTCASE"
1771 > > }
1771 > > }
1772 > #testcases A B
1772 > #testcases A B
1773 > #if A
1773 > #if A
1774 > $ dostuff
1774 > $ dostuff
1775 > In case A
1775 > In case A
1776 > #endif
1776 > #endif
1777 > #if B
1777 > #if B
1778 > $ dostuff
1778 > $ dostuff
1779 > In case B
1779 > In case B
1780 > #endif
1780 > #endif
1781 > EOF
1781 > EOF
1782 $ rt test-cases-ab.t
1782 $ rt test-cases-ab.t
1783 running 2 tests using 1 parallel processes
1783 running 2 tests using 1 parallel processes
1784 ..
1784 ..
1785 # Ran 2 tests, 0 skipped, 0 failed.
1785 # Ran 2 tests, 0 skipped, 0 failed.
1786
1786
1787 Support running a specific test case
1787 Support running a specific test case
1788
1788
1789 $ rt "test-cases-abc.t#B"
1789 $ rt "test-cases-abc.t#B"
1790 running 1 tests using 1 parallel processes
1790 running 1 tests using 1 parallel processes
1791
1791
1792 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1792 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1793 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1793 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1794 @@ -7,7 +7,7 @@
1794 @@ -7,7 +7,7 @@
1795 $ V=C
1795 $ V=C
1796 #endif
1796 #endif
1797 $ echo $V | sed 's/A/C/'
1797 $ echo $V | sed 's/A/C/'
1798 - C
1798 - C
1799 + B
1799 + B
1800 #if C
1800 #if C
1801 $ [ $V = C ]
1801 $ [ $V = C ]
1802 #endif
1802 #endif
1803
1803
1804 ERROR: test-cases-abc.t#B output changed
1804 ERROR: test-cases-abc.t#B output changed
1805 !
1805 !
1806 Failed test-cases-abc.t#B: output changed
1806 Failed test-cases-abc.t#B: output changed
1807 # Ran 1 tests, 0 skipped, 1 failed.
1807 # Ran 1 tests, 0 skipped, 1 failed.
1808 python hash seed: * (glob)
1808 python hash seed: * (glob)
1809 [1]
1809 [1]
1810
1810
1811 Support running multiple test cases in the same file
1811 Support running multiple test cases in the same file
1812
1812
1813 $ rt test-cases-abc.t#B test-cases-abc.t#C
1813 $ rt test-cases-abc.t#B test-cases-abc.t#C
1814 running 2 tests using 1 parallel processes
1814 running 2 tests using 1 parallel processes
1815
1815
1816 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1816 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1817 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1817 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1818 @@ -7,7 +7,7 @@
1818 @@ -7,7 +7,7 @@
1819 $ V=C
1819 $ V=C
1820 #endif
1820 #endif
1821 $ echo $V | sed 's/A/C/'
1821 $ echo $V | sed 's/A/C/'
1822 - C
1822 - C
1823 + B
1823 + B
1824 #if C
1824 #if C
1825 $ [ $V = C ]
1825 $ [ $V = C ]
1826 #endif
1826 #endif
1827
1827
1828 ERROR: test-cases-abc.t#B output changed
1828 ERROR: test-cases-abc.t#B output changed
1829 !.
1829 !.
1830 Failed test-cases-abc.t#B: output changed
1830 Failed test-cases-abc.t#B: output changed
1831 # Ran 2 tests, 0 skipped, 1 failed.
1831 # Ran 2 tests, 0 skipped, 1 failed.
1832 python hash seed: * (glob)
1832 python hash seed: * (glob)
1833 [1]
1833 [1]
1834
1834
1835 Support ignoring invalid test cases
1835 Support ignoring invalid test cases
1836
1836
1837 $ rt test-cases-abc.t#B test-cases-abc.t#D
1837 $ rt test-cases-abc.t#B test-cases-abc.t#D
1838 running 1 tests using 1 parallel processes
1838 running 1 tests using 1 parallel processes
1839
1839
1840 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1840 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1841 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1841 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1842 @@ -7,7 +7,7 @@
1842 @@ -7,7 +7,7 @@
1843 $ V=C
1843 $ V=C
1844 #endif
1844 #endif
1845 $ echo $V | sed 's/A/C/'
1845 $ echo $V | sed 's/A/C/'
1846 - C
1846 - C
1847 + B
1847 + B
1848 #if C
1848 #if C
1849 $ [ $V = C ]
1849 $ [ $V = C ]
1850 #endif
1850 #endif
1851
1851
1852 ERROR: test-cases-abc.t#B output changed
1852 ERROR: test-cases-abc.t#B output changed
1853 !
1853 !
1854 Failed test-cases-abc.t#B: output changed
1854 Failed test-cases-abc.t#B: output changed
1855 # Ran 1 tests, 0 skipped, 1 failed.
1855 # Ran 1 tests, 0 skipped, 1 failed.
1856 python hash seed: * (glob)
1856 python hash seed: * (glob)
1857 [1]
1857 [1]
1858
1858
1859 Support running complex test cases names
1859 Support running complex test cases names
1860
1860
1861 $ cat > test-cases-advanced-cases.t <<'EOF'
1861 $ cat > test-cases-advanced-cases.t <<'EOF'
1862 > #testcases simple case-with-dashes casewith_-.chars
1862 > #testcases simple case-with-dashes casewith_-.chars
1863 > $ echo $TESTCASE
1863 > $ echo $TESTCASE
1864 > simple
1864 > simple
1865 > EOF
1865 > EOF
1866
1866
1867 $ cat test-cases-advanced-cases.t
1867 $ cat test-cases-advanced-cases.t
1868 #testcases simple case-with-dashes casewith_-.chars
1868 #testcases simple case-with-dashes casewith_-.chars
1869 $ echo $TESTCASE
1869 $ echo $TESTCASE
1870 simple
1870 simple
1871
1871
1872 $ rt test-cases-advanced-cases.t
1872 $ rt test-cases-advanced-cases.t
1873 running 3 tests using 1 parallel processes
1873 running 3 tests using 1 parallel processes
1874
1874
1875 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1875 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1876 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1876 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1877 @@ -1,3 +1,3 @@
1877 @@ -1,3 +1,3 @@
1878 #testcases simple case-with-dashes casewith_-.chars
1878 #testcases simple case-with-dashes casewith_-.chars
1879 $ echo $TESTCASE
1879 $ echo $TESTCASE
1880 - simple
1880 - simple
1881 + case-with-dashes
1881 + case-with-dashes
1882
1882
1883 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1883 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1884 !
1884 !
1885 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1885 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1886 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1886 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1887 @@ -1,3 +1,3 @@
1887 @@ -1,3 +1,3 @@
1888 #testcases simple case-with-dashes casewith_-.chars
1888 #testcases simple case-with-dashes casewith_-.chars
1889 $ echo $TESTCASE
1889 $ echo $TESTCASE
1890 - simple
1890 - simple
1891 + casewith_-.chars
1891 + casewith_-.chars
1892
1892
1893 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1893 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1894 !.
1894 !.
1895 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1895 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1896 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1896 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1897 # Ran 3 tests, 0 skipped, 2 failed.
1897 # Ran 3 tests, 0 skipped, 2 failed.
1898 python hash seed: * (glob)
1898 python hash seed: * (glob)
1899 [1]
1899 [1]
1900
1900
1901 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1901 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1902 running 1 tests using 1 parallel processes
1902 running 1 tests using 1 parallel processes
1903
1903
1904 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1904 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1905 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1905 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1906 @@ -1,3 +1,3 @@
1906 @@ -1,3 +1,3 @@
1907 #testcases simple case-with-dashes casewith_-.chars
1907 #testcases simple case-with-dashes casewith_-.chars
1908 $ echo $TESTCASE
1908 $ echo $TESTCASE
1909 - simple
1909 - simple
1910 + case-with-dashes
1910 + case-with-dashes
1911
1911
1912 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1912 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1913 !
1913 !
1914 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1914 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1915 # Ran 1 tests, 0 skipped, 1 failed.
1915 # Ran 1 tests, 0 skipped, 1 failed.
1916 python hash seed: * (glob)
1916 python hash seed: * (glob)
1917 [1]
1917 [1]
1918
1918
1919 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1919 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1920 running 1 tests using 1 parallel processes
1920 running 1 tests using 1 parallel processes
1921
1921
1922 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1922 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1923 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1923 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1924 @@ -1,3 +1,3 @@
1924 @@ -1,3 +1,3 @@
1925 #testcases simple case-with-dashes casewith_-.chars
1925 #testcases simple case-with-dashes casewith_-.chars
1926 $ echo $TESTCASE
1926 $ echo $TESTCASE
1927 - simple
1927 - simple
1928 + casewith_-.chars
1928 + casewith_-.chars
1929
1929
1930 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1930 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1931 !
1931 !
1932 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1932 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1933 # Ran 1 tests, 0 skipped, 1 failed.
1933 # Ran 1 tests, 0 skipped, 1 failed.
1934 python hash seed: * (glob)
1934 python hash seed: * (glob)
1935 [1]
1935 [1]
1936
1936
1937 Test automatic pattern replacement
1937 Test automatic pattern replacement
1938 ==================================
1938 ==================================
1939
1939
1940 $ cat << EOF >> common-pattern.py
1940 $ cat << EOF >> common-pattern.py
1941 > substitutions = [
1941 > substitutions = [
1942 > (br'foo-(.*)\\b',
1942 > (br'foo-(.*)\\b',
1943 > br'\$XXX=\\1\$'),
1943 > br'\$XXX=\\1\$'),
1944 > (br'bar\\n',
1944 > (br'bar\\n',
1945 > br'\$YYY$\\n'),
1945 > br'\$YYY$\\n'),
1946 > ]
1946 > ]
1947 > EOF
1947 > EOF
1948
1948
1949 $ cat << EOF >> test-substitution.t
1949 $ cat << EOF >> test-substitution.t
1950 > $ echo foo-12
1950 > $ echo foo-12
1951 > \$XXX=12$
1951 > \$XXX=12$
1952 > $ echo foo-42
1952 > $ echo foo-42
1953 > \$XXX=42$
1953 > \$XXX=42$
1954 > $ echo bar prior
1954 > $ echo bar prior
1955 > bar prior
1955 > bar prior
1956 > $ echo lastbar
1956 > $ echo lastbar
1957 > last\$YYY$
1957 > last\$YYY$
1958 > $ echo foo-bar foo-baz
1958 > $ echo foo-bar foo-baz
1959 > EOF
1959 > EOF
1960
1960
1961 $ rt test-substitution.t
1961 $ rt test-substitution.t
1962 running 1 tests using 1 parallel processes
1962 running 1 tests using 1 parallel processes
1963
1963
1964 --- $TESTTMP/anothertests/cases/test-substitution.t
1964 --- $TESTTMP/anothertests/cases/test-substitution.t
1965 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1965 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1966 @@ -7,3 +7,4 @@
1966 @@ -7,3 +7,4 @@
1967 $ echo lastbar
1967 $ echo lastbar
1968 last$YYY$
1968 last$YYY$
1969 $ echo foo-bar foo-baz
1969 $ echo foo-bar foo-baz
1970 + $XXX=bar foo-baz$
1970 + $XXX=bar foo-baz$
1971
1971
1972 ERROR: test-substitution.t output changed
1972 ERROR: test-substitution.t output changed
1973 !
1973 !
1974 Failed test-substitution.t: output changed
1974 Failed test-substitution.t: output changed
1975 # Ran 1 tests, 0 skipped, 1 failed.
1975 # Ran 1 tests, 0 skipped, 1 failed.
1976 python hash seed: * (glob)
1976 python hash seed: * (glob)
1977 [1]
1977 [1]
1978
1978
1979 --extra-config-opt works
1979 --extra-config-opt works
1980
1980
1981 $ cat << EOF >> test-config-opt.t
1981 $ cat << EOF >> test-config-opt.t
1982 > $ hg init test-config-opt
1982 > $ hg init test-config-opt
1983 > $ hg -R test-config-opt purge
1983 > $ hg -R test-config-opt purge
1984 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1984 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1985 > HGTESTEXTRAEXTENSIONS: purge
1985 > HGTESTEXTRAEXTENSIONS: purge
1986 > EOF
1986 > EOF
1987
1987
1988 $ rt --extra-config-opt extensions.purge= \
1988 $ rt --extra-config-opt extensions.purge= \
1989 > --extra-config-opt not.an.extension=True test-config-opt.t
1989 > --extra-config-opt not.an.extension=True test-config-opt.t
1990 running 1 tests using 1 parallel processes
1990 running 1 tests using 1 parallel processes
1991 .
1991 .
1992 # Ran 1 tests, 0 skipped, 0 failed.
1992 # Ran 1 tests, 0 skipped, 0 failed.
1993
1993
1994 Test conditional output matching
1994 Test conditional output matching
1995 ================================
1995 ================================
1996
1996
1997 $ cat << EOF >> test-conditional-matching.t
1997 $ cat << EOF >> test-conditional-matching.t
1998 > #testcases foo bar
1998 > #testcases foo bar
1999 > $ echo richtig
1999 > $ echo richtig
2000 > richtig (true !)
2000 > richtig (true !)
2001 > $ echo falsch
2001 > $ echo falsch
2002 > falsch (false !)
2002 > falsch (false !)
2003 > #if foo
2003 > #if foo
2004 > $ echo arthur
2004 > $ echo arthur
2005 > arthur (bar !)
2005 > arthur (bar !)
2006 > #endif
2006 > #endif
2007 > $ echo celeste
2007 > $ echo celeste
2008 > celeste (foo !)
2008 > celeste (foo !)
2009 > $ echo zephir
2009 > $ echo zephir
2010 > zephir (bar !)
2010 > zephir (bar !)
2011 > EOF
2011 > EOF
2012
2012
2013 $ rt test-conditional-matching.t
2013 $ rt test-conditional-matching.t
2014 running 2 tests using 1 parallel processes
2014 running 2 tests using 1 parallel processes
2015
2015
2016 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2016 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2017 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
2017 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
2018 @@ -3,11 +3,13 @@
2018 @@ -3,11 +3,13 @@
2019 richtig (true !)
2019 richtig (true !)
2020 $ echo falsch
2020 $ echo falsch
2021 falsch (false !)
2021 falsch (false !)
2022 + falsch
2022 + falsch
2023 #if foo
2023 #if foo
2024 $ echo arthur
2024 $ echo arthur
2025 arthur \(bar !\) (re)
2025 arthur \(bar !\) (re)
2026 #endif
2026 #endif
2027 $ echo celeste
2027 $ echo celeste
2028 celeste \(foo !\) (re)
2028 celeste \(foo !\) (re)
2029 + celeste
2029 + celeste
2030 $ echo zephir
2030 $ echo zephir
2031 zephir \(bar !\) (re)
2031 zephir \(bar !\) (re)
2032
2032
2033 ERROR: test-conditional-matching.t#bar output changed
2033 ERROR: test-conditional-matching.t#bar output changed
2034 !
2034 !
2035 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2035 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2036 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2036 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2037 @@ -3,11 +3,14 @@
2037 @@ -3,11 +3,14 @@
2038 richtig (true !)
2038 richtig (true !)
2039 $ echo falsch
2039 $ echo falsch
2040 falsch (false !)
2040 falsch (false !)
2041 + falsch
2041 + falsch
2042 #if foo
2042 #if foo
2043 $ echo arthur
2043 $ echo arthur
2044 arthur \(bar !\) (re)
2044 arthur \(bar !\) (re)
2045 + arthur
2045 + arthur
2046 #endif
2046 #endif
2047 $ echo celeste
2047 $ echo celeste
2048 celeste \(foo !\) (re)
2048 celeste \(foo !\) (re)
2049 $ echo zephir
2049 $ echo zephir
2050 zephir \(bar !\) (re)
2050 zephir \(bar !\) (re)
2051 + zephir
2051 + zephir
2052
2052
2053 ERROR: test-conditional-matching.t#foo output changed
2053 ERROR: test-conditional-matching.t#foo output changed
2054 !
2054 !
2055 Failed test-conditional-matching.t#bar: output changed
2055 Failed test-conditional-matching.t#bar: output changed
2056 Failed test-conditional-matching.t#foo: output changed
2056 Failed test-conditional-matching.t#foo: output changed
2057 # Ran 2 tests, 0 skipped, 2 failed.
2057 # Ran 2 tests, 0 skipped, 2 failed.
2058 python hash seed: * (glob)
2058 python hash seed: * (glob)
2059 [1]
2059 [1]
2060
2060
2061 Test that a proper "python" has been set up
2061 Test that a proper "python" has been set up
2062 ===========================================
2062 ===========================================
2063
2063
2064 (with a small check-code work around)
2064 (with a small check-code work around)
2065 $ printf "#!/usr/bi" > test-py3.tmp
2065 $ printf "#!/usr/bi" > test-py3.tmp
2066 $ printf "n/en" >> test-py3.tmp
2066 $ printf "n/en" >> test-py3.tmp
2067 $ cat << EOF >> test-py3.tmp
2067 $ cat << EOF >> test-py3.tmp
2068 > v python3
2068 > v python3
2069 > import sys
2069 > import sys
2070 > print('.'.join(str(x) for x in sys.version_info))
2070 > print('.'.join(str(x) for x in sys.version_info))
2071 > EOF
2071 > EOF
2072 $ mv test-py3.tmp test-py3.py
2072 $ mv test-py3.tmp test-py3.py
2073 $ chmod +x test-py3.py
2073 $ chmod +x test-py3.py
2074
2074
2075 (with a small check-code work around)
2075 (with a small check-code work around)
2076 $ printf "#!/usr/bi" > test-py.tmp
2076 $ printf "#!/usr/bi" > test-py.tmp
2077 $ printf "n/en" >> test-py.tmp
2077 $ printf "n/en" >> test-py.tmp
2078 $ cat << EOF >> test-py.tmp
2078 $ cat << EOF >> test-py.tmp
2079 > v python
2079 > v python
2080 > import sys
2080 > import sys
2081 > print('.'.join(str(x) for x in sys.version_info))
2081 > print('.'.join(str(x) for x in sys.version_info))
2082 > EOF
2082 > EOF
2083 $ mv test-py.tmp test-py.py
2083 $ mv test-py.tmp test-py.py
2084 $ chmod +x test-py.py
2084 $ chmod +x test-py.py
2085
2085
2086 $ ./test-py3.py
2086 $ ./test-py3.py
2087 3.* (glob)
2087 3.* (glob)
2088 $ ./test-py.py
2088 $ ./test-py.py
2089 3.* (glob)
2089 3.* (glob)
General Comments 0
You need to be logged in to leave comments. Login now