##// END OF EJS Templates
run-tests: introduce a `HGTEST_REAL_HG` variable for test...
marmoute -
r48384:ee1fc8f9 default
parent child Browse files
Show More
@@ -1,3955 +1,3955
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
5 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import contextlib
50 import contextlib
51 import difflib
51 import difflib
52 import distutils.version as version
52 import distutils.version as version
53 import errno
53 import errno
54 import json
54 import json
55 import multiprocessing
55 import multiprocessing
56 import os
56 import os
57 import platform
57 import platform
58 import random
58 import random
59 import re
59 import re
60 import shutil
60 import shutil
61 import signal
61 import signal
62 import socket
62 import socket
63 import subprocess
63 import subprocess
64 import sys
64 import sys
65 import sysconfig
65 import sysconfig
66 import tempfile
66 import tempfile
67 import threading
67 import threading
68 import time
68 import time
69 import unittest
69 import unittest
70 import uuid
70 import uuid
71 import xml.dom.minidom as minidom
71 import xml.dom.minidom as minidom
72
72
73 WINDOWS = os.name == r'nt'
73 WINDOWS = os.name == r'nt'
74
74
75 try:
75 try:
76 import Queue as queue
76 import Queue as queue
77 except ImportError:
77 except ImportError:
78 import queue
78 import queue
79
79
80 try:
80 try:
81 import shlex
81 import shlex
82
82
83 shellquote = shlex.quote
83 shellquote = shlex.quote
84 except (ImportError, AttributeError):
84 except (ImportError, AttributeError):
85 import pipes
85 import pipes
86
86
87 shellquote = pipes.quote
87 shellquote = pipes.quote
88
88
89
89
90 processlock = threading.Lock()
90 processlock = threading.Lock()
91
91
92 pygmentspresent = False
92 pygmentspresent = False
93 try: # is pygments installed
93 try: # is pygments installed
94 import pygments
94 import pygments
95 import pygments.lexers as lexers
95 import pygments.lexers as lexers
96 import pygments.lexer as lexer
96 import pygments.lexer as lexer
97 import pygments.formatters as formatters
97 import pygments.formatters as formatters
98 import pygments.token as token
98 import pygments.token as token
99 import pygments.style as style
99 import pygments.style as style
100
100
101 if WINDOWS:
101 if WINDOWS:
102 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
102 hgpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
103 sys.path.append(hgpath)
103 sys.path.append(hgpath)
104 try:
104 try:
105 from mercurial import win32 # pytype: disable=import-error
105 from mercurial import win32 # pytype: disable=import-error
106
106
107 # Don't check the result code because it fails on heptapod, but
107 # Don't check the result code because it fails on heptapod, but
108 # something is able to convert to color anyway.
108 # something is able to convert to color anyway.
109 win32.enablevtmode()
109 win32.enablevtmode()
110 finally:
110 finally:
111 sys.path = sys.path[:-1]
111 sys.path = sys.path[:-1]
112
112
113 pygmentspresent = True
113 pygmentspresent = True
114 difflexer = lexers.DiffLexer()
114 difflexer = lexers.DiffLexer()
115 terminal256formatter = formatters.Terminal256Formatter()
115 terminal256formatter = formatters.Terminal256Formatter()
116 except ImportError:
116 except ImportError:
117 pass
117 pass
118
118
119 if pygmentspresent:
119 if pygmentspresent:
120
120
121 class TestRunnerStyle(style.Style):
121 class TestRunnerStyle(style.Style):
122 default_style = ""
122 default_style = ""
123 skipped = token.string_to_tokentype("Token.Generic.Skipped")
123 skipped = token.string_to_tokentype("Token.Generic.Skipped")
124 failed = token.string_to_tokentype("Token.Generic.Failed")
124 failed = token.string_to_tokentype("Token.Generic.Failed")
125 skippedname = token.string_to_tokentype("Token.Generic.SName")
125 skippedname = token.string_to_tokentype("Token.Generic.SName")
126 failedname = token.string_to_tokentype("Token.Generic.FName")
126 failedname = token.string_to_tokentype("Token.Generic.FName")
127 styles = {
127 styles = {
128 skipped: '#e5e5e5',
128 skipped: '#e5e5e5',
129 skippedname: '#00ffff',
129 skippedname: '#00ffff',
130 failed: '#7f0000',
130 failed: '#7f0000',
131 failedname: '#ff0000',
131 failedname: '#ff0000',
132 }
132 }
133
133
134 class TestRunnerLexer(lexer.RegexLexer):
134 class TestRunnerLexer(lexer.RegexLexer):
135 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
135 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
136 tokens = {
136 tokens = {
137 'root': [
137 'root': [
138 (r'^Skipped', token.Generic.Skipped, 'skipped'),
138 (r'^Skipped', token.Generic.Skipped, 'skipped'),
139 (r'^Failed ', token.Generic.Failed, 'failed'),
139 (r'^Failed ', token.Generic.Failed, 'failed'),
140 (r'^ERROR: ', token.Generic.Failed, 'failed'),
140 (r'^ERROR: ', token.Generic.Failed, 'failed'),
141 ],
141 ],
142 'skipped': [
142 'skipped': [
143 (testpattern, token.Generic.SName),
143 (testpattern, token.Generic.SName),
144 (r':.*', token.Generic.Skipped),
144 (r':.*', token.Generic.Skipped),
145 ],
145 ],
146 'failed': [
146 'failed': [
147 (testpattern, token.Generic.FName),
147 (testpattern, token.Generic.FName),
148 (r'(:| ).*', token.Generic.Failed),
148 (r'(:| ).*', token.Generic.Failed),
149 ],
149 ],
150 }
150 }
151
151
152 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
152 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
153 runnerlexer = TestRunnerLexer()
153 runnerlexer = TestRunnerLexer()
154
154
155 origenviron = os.environ.copy()
155 origenviron = os.environ.copy()
156
156
157
157
158 if sys.version_info > (3, 5, 0):
158 if sys.version_info > (3, 5, 0):
159 PYTHON3 = True
159 PYTHON3 = True
160 xrange = range # we use xrange in one place, and we'd rather not use range
160 xrange = range # we use xrange in one place, and we'd rather not use range
161
161
162 def _sys2bytes(p):
162 def _sys2bytes(p):
163 if p is None:
163 if p is None:
164 return p
164 return p
165 return p.encode('utf-8')
165 return p.encode('utf-8')
166
166
167 def _bytes2sys(p):
167 def _bytes2sys(p):
168 if p is None:
168 if p is None:
169 return p
169 return p
170 return p.decode('utf-8')
170 return p.decode('utf-8')
171
171
172 osenvironb = getattr(os, 'environb', None)
172 osenvironb = getattr(os, 'environb', None)
173 if osenvironb is None:
173 if osenvironb is None:
174 # Windows lacks os.environb, for instance. A proxy over the real thing
174 # Windows lacks os.environb, for instance. A proxy over the real thing
175 # instead of a copy allows the environment to be updated via bytes on
175 # instead of a copy allows the environment to be updated via bytes on
176 # all platforms.
176 # all platforms.
177 class environbytes(object):
177 class environbytes(object):
178 def __init__(self, strenv):
178 def __init__(self, strenv):
179 self.__len__ = strenv.__len__
179 self.__len__ = strenv.__len__
180 self.clear = strenv.clear
180 self.clear = strenv.clear
181 self._strenv = strenv
181 self._strenv = strenv
182
182
183 def __getitem__(self, k):
183 def __getitem__(self, k):
184 v = self._strenv.__getitem__(_bytes2sys(k))
184 v = self._strenv.__getitem__(_bytes2sys(k))
185 return _sys2bytes(v)
185 return _sys2bytes(v)
186
186
187 def __setitem__(self, k, v):
187 def __setitem__(self, k, v):
188 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
188 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
189
189
190 def __delitem__(self, k):
190 def __delitem__(self, k):
191 self._strenv.__delitem__(_bytes2sys(k))
191 self._strenv.__delitem__(_bytes2sys(k))
192
192
193 def __contains__(self, k):
193 def __contains__(self, k):
194 return self._strenv.__contains__(_bytes2sys(k))
194 return self._strenv.__contains__(_bytes2sys(k))
195
195
196 def __iter__(self):
196 def __iter__(self):
197 return iter([_sys2bytes(k) for k in iter(self._strenv)])
197 return iter([_sys2bytes(k) for k in iter(self._strenv)])
198
198
199 def get(self, k, default=None):
199 def get(self, k, default=None):
200 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
200 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
201 return _sys2bytes(v)
201 return _sys2bytes(v)
202
202
203 def pop(self, k, default=None):
203 def pop(self, k, default=None):
204 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
204 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
205 return _sys2bytes(v)
205 return _sys2bytes(v)
206
206
207 osenvironb = environbytes(os.environ)
207 osenvironb = environbytes(os.environ)
208
208
209 getcwdb = getattr(os, 'getcwdb')
209 getcwdb = getattr(os, 'getcwdb')
210 if not getcwdb or WINDOWS:
210 if not getcwdb or WINDOWS:
211 getcwdb = lambda: _sys2bytes(os.getcwd())
211 getcwdb = lambda: _sys2bytes(os.getcwd())
212
212
213 elif sys.version_info >= (3, 0, 0):
213 elif sys.version_info >= (3, 0, 0):
214 print(
214 print(
215 '%s is only supported on Python 3.5+ and 2.7, not %s'
215 '%s is only supported on Python 3.5+ and 2.7, not %s'
216 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
216 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
217 )
217 )
218 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
218 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
219 else:
219 else:
220 PYTHON3 = False
220 PYTHON3 = False
221
221
222 # In python 2.x, path operations are generally done using
222 # In python 2.x, path operations are generally done using
223 # bytestrings by default, so we don't have to do any extra
223 # bytestrings by default, so we don't have to do any extra
224 # fiddling there. We define the wrapper functions anyway just to
224 # fiddling there. We define the wrapper functions anyway just to
225 # help keep code consistent between platforms.
225 # help keep code consistent between platforms.
226 def _sys2bytes(p):
226 def _sys2bytes(p):
227 return p
227 return p
228
228
229 _bytes2sys = _sys2bytes
229 _bytes2sys = _sys2bytes
230 osenvironb = os.environ
230 osenvironb = os.environ
231 getcwdb = os.getcwd
231 getcwdb = os.getcwd
232
232
233 # For Windows support
233 # For Windows support
234 wifexited = getattr(os, "WIFEXITED", lambda x: False)
234 wifexited = getattr(os, "WIFEXITED", lambda x: False)
235
235
236 # Whether to use IPv6
236 # Whether to use IPv6
237 def checksocketfamily(name, port=20058):
237 def checksocketfamily(name, port=20058):
238 """return true if we can listen on localhost using family=name
238 """return true if we can listen on localhost using family=name
239
239
240 name should be either 'AF_INET', or 'AF_INET6'.
240 name should be either 'AF_INET', or 'AF_INET6'.
241 port being used is okay - EADDRINUSE is considered as successful.
241 port being used is okay - EADDRINUSE is considered as successful.
242 """
242 """
243 family = getattr(socket, name, None)
243 family = getattr(socket, name, None)
244 if family is None:
244 if family is None:
245 return False
245 return False
246 try:
246 try:
247 s = socket.socket(family, socket.SOCK_STREAM)
247 s = socket.socket(family, socket.SOCK_STREAM)
248 s.bind(('localhost', port))
248 s.bind(('localhost', port))
249 s.close()
249 s.close()
250 return True
250 return True
251 except socket.error as exc:
251 except socket.error as exc:
252 if exc.errno == errno.EADDRINUSE:
252 if exc.errno == errno.EADDRINUSE:
253 return True
253 return True
254 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
254 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
255 return False
255 return False
256 else:
256 else:
257 raise
257 raise
258 else:
258 else:
259 return False
259 return False
260
260
261
261
262 # useipv6 will be set by parseargs
262 # useipv6 will be set by parseargs
263 useipv6 = None
263 useipv6 = None
264
264
265
265
266 def checkportisavailable(port):
266 def checkportisavailable(port):
267 """return true if a port seems free to bind on localhost"""
267 """return true if a port seems free to bind on localhost"""
268 if useipv6:
268 if useipv6:
269 family = socket.AF_INET6
269 family = socket.AF_INET6
270 else:
270 else:
271 family = socket.AF_INET
271 family = socket.AF_INET
272 try:
272 try:
273 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
273 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
274 s.bind(('localhost', port))
274 s.bind(('localhost', port))
275 return True
275 return True
276 except socket.error as exc:
276 except socket.error as exc:
277 if WINDOWS and exc.errno == errno.WSAEACCES:
277 if WINDOWS and exc.errno == errno.WSAEACCES:
278 return False
278 return False
279 elif PYTHON3:
279 elif PYTHON3:
280 # TODO: make a proper exception handler after dropping py2. This
280 # TODO: make a proper exception handler after dropping py2. This
281 # works because socket.error is an alias for OSError on py3,
281 # works because socket.error is an alias for OSError on py3,
282 # which is also the baseclass of PermissionError.
282 # which is also the baseclass of PermissionError.
283 if isinstance(exc, PermissionError):
283 if isinstance(exc, PermissionError):
284 return False
284 return False
285 if exc.errno not in (
285 if exc.errno not in (
286 errno.EADDRINUSE,
286 errno.EADDRINUSE,
287 errno.EADDRNOTAVAIL,
287 errno.EADDRNOTAVAIL,
288 errno.EPROTONOSUPPORT,
288 errno.EPROTONOSUPPORT,
289 ):
289 ):
290 raise
290 raise
291 return False
291 return False
292
292
293
293
294 closefds = os.name == 'posix'
294 closefds = os.name == 'posix'
295
295
296
296
297 def Popen4(cmd, wd, timeout, env=None):
297 def Popen4(cmd, wd, timeout, env=None):
298 processlock.acquire()
298 processlock.acquire()
299 p = subprocess.Popen(
299 p = subprocess.Popen(
300 _bytes2sys(cmd),
300 _bytes2sys(cmd),
301 shell=True,
301 shell=True,
302 bufsize=-1,
302 bufsize=-1,
303 cwd=_bytes2sys(wd),
303 cwd=_bytes2sys(wd),
304 env=env,
304 env=env,
305 close_fds=closefds,
305 close_fds=closefds,
306 stdin=subprocess.PIPE,
306 stdin=subprocess.PIPE,
307 stdout=subprocess.PIPE,
307 stdout=subprocess.PIPE,
308 stderr=subprocess.STDOUT,
308 stderr=subprocess.STDOUT,
309 )
309 )
310 processlock.release()
310 processlock.release()
311
311
312 p.fromchild = p.stdout
312 p.fromchild = p.stdout
313 p.tochild = p.stdin
313 p.tochild = p.stdin
314 p.childerr = p.stderr
314 p.childerr = p.stderr
315
315
316 p.timeout = False
316 p.timeout = False
317 if timeout:
317 if timeout:
318
318
319 def t():
319 def t():
320 start = time.time()
320 start = time.time()
321 while time.time() - start < timeout and p.returncode is None:
321 while time.time() - start < timeout and p.returncode is None:
322 time.sleep(0.1)
322 time.sleep(0.1)
323 p.timeout = True
323 p.timeout = True
324 vlog('# Timout reached for process %d' % p.pid)
324 vlog('# Timout reached for process %d' % p.pid)
325 if p.returncode is None:
325 if p.returncode is None:
326 terminate(p)
326 terminate(p)
327
327
328 threading.Thread(target=t).start()
328 threading.Thread(target=t).start()
329
329
330 return p
330 return p
331
331
332
332
333 if sys.executable:
333 if sys.executable:
334 sysexecutable = sys.executable
334 sysexecutable = sys.executable
335 elif os.environ.get('PYTHONEXECUTABLE'):
335 elif os.environ.get('PYTHONEXECUTABLE'):
336 sysexecutable = os.environ['PYTHONEXECUTABLE']
336 sysexecutable = os.environ['PYTHONEXECUTABLE']
337 elif os.environ.get('PYTHON'):
337 elif os.environ.get('PYTHON'):
338 sysexecutable = os.environ['PYTHON']
338 sysexecutable = os.environ['PYTHON']
339 else:
339 else:
340 raise AssertionError('Could not find Python interpreter')
340 raise AssertionError('Could not find Python interpreter')
341
341
342 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
342 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
343 IMPL_PATH = b'PYTHONPATH'
343 IMPL_PATH = b'PYTHONPATH'
344 if 'java' in sys.platform:
344 if 'java' in sys.platform:
345 IMPL_PATH = b'JYTHONPATH'
345 IMPL_PATH = b'JYTHONPATH'
346
346
347 default_defaults = {
347 default_defaults = {
348 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
348 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
349 'timeout': ('HGTEST_TIMEOUT', 360),
349 'timeout': ('HGTEST_TIMEOUT', 360),
350 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
350 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
351 'port': ('HGTEST_PORT', 20059),
351 'port': ('HGTEST_PORT', 20059),
352 'shell': ('HGTEST_SHELL', 'sh'),
352 'shell': ('HGTEST_SHELL', 'sh'),
353 }
353 }
354
354
355 defaults = default_defaults.copy()
355 defaults = default_defaults.copy()
356
356
357
357
358 def canonpath(path):
358 def canonpath(path):
359 return os.path.realpath(os.path.expanduser(path))
359 return os.path.realpath(os.path.expanduser(path))
360
360
361
361
362 def parselistfiles(files, listtype, warn=True):
362 def parselistfiles(files, listtype, warn=True):
363 entries = dict()
363 entries = dict()
364 for filename in files:
364 for filename in files:
365 try:
365 try:
366 path = os.path.expanduser(os.path.expandvars(filename))
366 path = os.path.expanduser(os.path.expandvars(filename))
367 f = open(path, "rb")
367 f = open(path, "rb")
368 except IOError as err:
368 except IOError as err:
369 if err.errno != errno.ENOENT:
369 if err.errno != errno.ENOENT:
370 raise
370 raise
371 if warn:
371 if warn:
372 print("warning: no such %s file: %s" % (listtype, filename))
372 print("warning: no such %s file: %s" % (listtype, filename))
373 continue
373 continue
374
374
375 for line in f.readlines():
375 for line in f.readlines():
376 line = line.split(b'#', 1)[0].strip()
376 line = line.split(b'#', 1)[0].strip()
377 if line:
377 if line:
378 # Ensure path entries are compatible with os.path.relpath()
378 # Ensure path entries are compatible with os.path.relpath()
379 entries[os.path.normpath(line)] = filename
379 entries[os.path.normpath(line)] = filename
380
380
381 f.close()
381 f.close()
382 return entries
382 return entries
383
383
384
384
385 def parsettestcases(path):
385 def parsettestcases(path):
386 """read a .t test file, return a set of test case names
386 """read a .t test file, return a set of test case names
387
387
388 If path does not exist, return an empty set.
388 If path does not exist, return an empty set.
389 """
389 """
390 cases = []
390 cases = []
391 try:
391 try:
392 with open(path, 'rb') as f:
392 with open(path, 'rb') as f:
393 for l in f:
393 for l in f:
394 if l.startswith(b'#testcases '):
394 if l.startswith(b'#testcases '):
395 cases.append(sorted(l[11:].split()))
395 cases.append(sorted(l[11:].split()))
396 except IOError as ex:
396 except IOError as ex:
397 if ex.errno != errno.ENOENT:
397 if ex.errno != errno.ENOENT:
398 raise
398 raise
399 return cases
399 return cases
400
400
401
401
402 def getparser():
402 def getparser():
403 """Obtain the OptionParser used by the CLI."""
403 """Obtain the OptionParser used by the CLI."""
404 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
404 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
405
405
406 selection = parser.add_argument_group('Test Selection')
406 selection = parser.add_argument_group('Test Selection')
407 selection.add_argument(
407 selection.add_argument(
408 '--allow-slow-tests',
408 '--allow-slow-tests',
409 action='store_true',
409 action='store_true',
410 help='allow extremely slow tests',
410 help='allow extremely slow tests',
411 )
411 )
412 selection.add_argument(
412 selection.add_argument(
413 "--blacklist",
413 "--blacklist",
414 action="append",
414 action="append",
415 help="skip tests listed in the specified blacklist file",
415 help="skip tests listed in the specified blacklist file",
416 )
416 )
417 selection.add_argument(
417 selection.add_argument(
418 "--changed",
418 "--changed",
419 help="run tests that are changed in parent rev or working directory",
419 help="run tests that are changed in parent rev or working directory",
420 )
420 )
421 selection.add_argument(
421 selection.add_argument(
422 "-k", "--keywords", help="run tests matching keywords"
422 "-k", "--keywords", help="run tests matching keywords"
423 )
423 )
424 selection.add_argument(
424 selection.add_argument(
425 "-r", "--retest", action="store_true", help="retest failed tests"
425 "-r", "--retest", action="store_true", help="retest failed tests"
426 )
426 )
427 selection.add_argument(
427 selection.add_argument(
428 "--test-list",
428 "--test-list",
429 action="append",
429 action="append",
430 help="read tests to run from the specified file",
430 help="read tests to run from the specified file",
431 )
431 )
432 selection.add_argument(
432 selection.add_argument(
433 "--whitelist",
433 "--whitelist",
434 action="append",
434 action="append",
435 help="always run tests listed in the specified whitelist file",
435 help="always run tests listed in the specified whitelist file",
436 )
436 )
437 selection.add_argument(
437 selection.add_argument(
438 'tests', metavar='TESTS', nargs='*', help='Tests to run'
438 'tests', metavar='TESTS', nargs='*', help='Tests to run'
439 )
439 )
440
440
441 harness = parser.add_argument_group('Test Harness Behavior')
441 harness = parser.add_argument_group('Test Harness Behavior')
442 harness.add_argument(
442 harness.add_argument(
443 '--bisect-repo',
443 '--bisect-repo',
444 metavar='bisect_repo',
444 metavar='bisect_repo',
445 help=(
445 help=(
446 "Path of a repo to bisect. Use together with " "--known-good-rev"
446 "Path of a repo to bisect. Use together with " "--known-good-rev"
447 ),
447 ),
448 )
448 )
449 harness.add_argument(
449 harness.add_argument(
450 "-d",
450 "-d",
451 "--debug",
451 "--debug",
452 action="store_true",
452 action="store_true",
453 help="debug mode: write output of test scripts to console"
453 help="debug mode: write output of test scripts to console"
454 " rather than capturing and diffing it (disables timeout)",
454 " rather than capturing and diffing it (disables timeout)",
455 )
455 )
456 harness.add_argument(
456 harness.add_argument(
457 "-f",
457 "-f",
458 "--first",
458 "--first",
459 action="store_true",
459 action="store_true",
460 help="exit on the first test failure",
460 help="exit on the first test failure",
461 )
461 )
462 harness.add_argument(
462 harness.add_argument(
463 "-i",
463 "-i",
464 "--interactive",
464 "--interactive",
465 action="store_true",
465 action="store_true",
466 help="prompt to accept changed output",
466 help="prompt to accept changed output",
467 )
467 )
468 harness.add_argument(
468 harness.add_argument(
469 "-j",
469 "-j",
470 "--jobs",
470 "--jobs",
471 type=int,
471 type=int,
472 help="number of jobs to run in parallel"
472 help="number of jobs to run in parallel"
473 " (default: $%s or %d)" % defaults['jobs'],
473 " (default: $%s or %d)" % defaults['jobs'],
474 )
474 )
475 harness.add_argument(
475 harness.add_argument(
476 "--keep-tmpdir",
476 "--keep-tmpdir",
477 action="store_true",
477 action="store_true",
478 help="keep temporary directory after running tests",
478 help="keep temporary directory after running tests",
479 )
479 )
480 harness.add_argument(
480 harness.add_argument(
481 '--known-good-rev',
481 '--known-good-rev',
482 metavar="known_good_rev",
482 metavar="known_good_rev",
483 help=(
483 help=(
484 "Automatically bisect any failures using this "
484 "Automatically bisect any failures using this "
485 "revision as a known-good revision."
485 "revision as a known-good revision."
486 ),
486 ),
487 )
487 )
488 harness.add_argument(
488 harness.add_argument(
489 "--list-tests",
489 "--list-tests",
490 action="store_true",
490 action="store_true",
491 help="list tests instead of running them",
491 help="list tests instead of running them",
492 )
492 )
493 harness.add_argument(
493 harness.add_argument(
494 "--loop", action="store_true", help="loop tests repeatedly"
494 "--loop", action="store_true", help="loop tests repeatedly"
495 )
495 )
496 harness.add_argument(
496 harness.add_argument(
497 '--random', action="store_true", help='run tests in random order'
497 '--random', action="store_true", help='run tests in random order'
498 )
498 )
499 harness.add_argument(
499 harness.add_argument(
500 '--order-by-runtime',
500 '--order-by-runtime',
501 action="store_true",
501 action="store_true",
502 help='run slowest tests first, according to .testtimes',
502 help='run slowest tests first, according to .testtimes',
503 )
503 )
504 harness.add_argument(
504 harness.add_argument(
505 "-p",
505 "-p",
506 "--port",
506 "--port",
507 type=int,
507 type=int,
508 help="port on which servers should listen"
508 help="port on which servers should listen"
509 " (default: $%s or %d)" % defaults['port'],
509 " (default: $%s or %d)" % defaults['port'],
510 )
510 )
511 harness.add_argument(
511 harness.add_argument(
512 '--profile-runner',
512 '--profile-runner',
513 action='store_true',
513 action='store_true',
514 help='run statprof on run-tests',
514 help='run statprof on run-tests',
515 )
515 )
516 harness.add_argument(
516 harness.add_argument(
517 "-R", "--restart", action="store_true", help="restart at last error"
517 "-R", "--restart", action="store_true", help="restart at last error"
518 )
518 )
519 harness.add_argument(
519 harness.add_argument(
520 "--runs-per-test",
520 "--runs-per-test",
521 type=int,
521 type=int,
522 dest="runs_per_test",
522 dest="runs_per_test",
523 help="run each test N times (default=1)",
523 help="run each test N times (default=1)",
524 default=1,
524 default=1,
525 )
525 )
526 harness.add_argument(
526 harness.add_argument(
527 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
527 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
528 )
528 )
529 harness.add_argument(
529 harness.add_argument(
530 '--showchannels', action='store_true', help='show scheduling channels'
530 '--showchannels', action='store_true', help='show scheduling channels'
531 )
531 )
532 harness.add_argument(
532 harness.add_argument(
533 "--slowtimeout",
533 "--slowtimeout",
534 type=int,
534 type=int,
535 help="kill errant slow tests after SLOWTIMEOUT seconds"
535 help="kill errant slow tests after SLOWTIMEOUT seconds"
536 " (default: $%s or %d)" % defaults['slowtimeout'],
536 " (default: $%s or %d)" % defaults['slowtimeout'],
537 )
537 )
538 harness.add_argument(
538 harness.add_argument(
539 "-t",
539 "-t",
540 "--timeout",
540 "--timeout",
541 type=int,
541 type=int,
542 help="kill errant tests after TIMEOUT seconds"
542 help="kill errant tests after TIMEOUT seconds"
543 " (default: $%s or %d)" % defaults['timeout'],
543 " (default: $%s or %d)" % defaults['timeout'],
544 )
544 )
545 harness.add_argument(
545 harness.add_argument(
546 "--tmpdir",
546 "--tmpdir",
547 help="run tests in the given temporary directory"
547 help="run tests in the given temporary directory"
548 " (implies --keep-tmpdir)",
548 " (implies --keep-tmpdir)",
549 )
549 )
550 harness.add_argument(
550 harness.add_argument(
551 "-v", "--verbose", action="store_true", help="output verbose messages"
551 "-v", "--verbose", action="store_true", help="output verbose messages"
552 )
552 )
553
553
554 hgconf = parser.add_argument_group('Mercurial Configuration')
554 hgconf = parser.add_argument_group('Mercurial Configuration')
555 hgconf.add_argument(
555 hgconf.add_argument(
556 "--chg",
556 "--chg",
557 action="store_true",
557 action="store_true",
558 help="install and use chg wrapper in place of hg",
558 help="install and use chg wrapper in place of hg",
559 )
559 )
560 hgconf.add_argument(
560 hgconf.add_argument(
561 "--chg-debug",
561 "--chg-debug",
562 action="store_true",
562 action="store_true",
563 help="show chg debug logs",
563 help="show chg debug logs",
564 )
564 )
565 hgconf.add_argument(
565 hgconf.add_argument(
566 "--rhg",
566 "--rhg",
567 action="store_true",
567 action="store_true",
568 help="install and use rhg Rust implementation in place of hg",
568 help="install and use rhg Rust implementation in place of hg",
569 )
569 )
570 hgconf.add_argument("--compiler", help="compiler to build with")
570 hgconf.add_argument("--compiler", help="compiler to build with")
571 hgconf.add_argument(
571 hgconf.add_argument(
572 '--extra-config-opt',
572 '--extra-config-opt',
573 action="append",
573 action="append",
574 default=[],
574 default=[],
575 help='set the given config opt in the test hgrc',
575 help='set the given config opt in the test hgrc',
576 )
576 )
577 hgconf.add_argument(
577 hgconf.add_argument(
578 "-l",
578 "-l",
579 "--local",
579 "--local",
580 action="store_true",
580 action="store_true",
581 help="shortcut for --with-hg=<testdir>/../hg, "
581 help="shortcut for --with-hg=<testdir>/../hg, "
582 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
582 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
583 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
583 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
584 )
584 )
585 hgconf.add_argument(
585 hgconf.add_argument(
586 "--ipv6",
586 "--ipv6",
587 action="store_true",
587 action="store_true",
588 help="prefer IPv6 to IPv4 for network related tests",
588 help="prefer IPv6 to IPv4 for network related tests",
589 )
589 )
590 hgconf.add_argument(
590 hgconf.add_argument(
591 "--pure",
591 "--pure",
592 action="store_true",
592 action="store_true",
593 help="use pure Python code instead of C extensions",
593 help="use pure Python code instead of C extensions",
594 )
594 )
595 hgconf.add_argument(
595 hgconf.add_argument(
596 "--rust",
596 "--rust",
597 action="store_true",
597 action="store_true",
598 help="use Rust code alongside C extensions",
598 help="use Rust code alongside C extensions",
599 )
599 )
600 hgconf.add_argument(
600 hgconf.add_argument(
601 "--no-rust",
601 "--no-rust",
602 action="store_true",
602 action="store_true",
603 help="do not use Rust code even if compiled",
603 help="do not use Rust code even if compiled",
604 )
604 )
605 hgconf.add_argument(
605 hgconf.add_argument(
606 "--with-chg",
606 "--with-chg",
607 metavar="CHG",
607 metavar="CHG",
608 help="use specified chg wrapper in place of hg",
608 help="use specified chg wrapper in place of hg",
609 )
609 )
610 hgconf.add_argument(
610 hgconf.add_argument(
611 "--with-rhg",
611 "--with-rhg",
612 metavar="RHG",
612 metavar="RHG",
613 help="use specified rhg Rust implementation in place of hg",
613 help="use specified rhg Rust implementation in place of hg",
614 )
614 )
615 hgconf.add_argument(
615 hgconf.add_argument(
616 "--with-hg",
616 "--with-hg",
617 metavar="HG",
617 metavar="HG",
618 help="test using specified hg script rather than a "
618 help="test using specified hg script rather than a "
619 "temporary installation",
619 "temporary installation",
620 )
620 )
621
621
622 reporting = parser.add_argument_group('Results Reporting')
622 reporting = parser.add_argument_group('Results Reporting')
623 reporting.add_argument(
623 reporting.add_argument(
624 "-C",
624 "-C",
625 "--annotate",
625 "--annotate",
626 action="store_true",
626 action="store_true",
627 help="output files annotated with coverage",
627 help="output files annotated with coverage",
628 )
628 )
629 reporting.add_argument(
629 reporting.add_argument(
630 "--color",
630 "--color",
631 choices=["always", "auto", "never"],
631 choices=["always", "auto", "never"],
632 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
632 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
633 help="colorisation: always|auto|never (default: auto)",
633 help="colorisation: always|auto|never (default: auto)",
634 )
634 )
635 reporting.add_argument(
635 reporting.add_argument(
636 "-c",
636 "-c",
637 "--cover",
637 "--cover",
638 action="store_true",
638 action="store_true",
639 help="print a test coverage report",
639 help="print a test coverage report",
640 )
640 )
641 reporting.add_argument(
641 reporting.add_argument(
642 '--exceptions',
642 '--exceptions',
643 action='store_true',
643 action='store_true',
644 help='log all exceptions and generate an exception report',
644 help='log all exceptions and generate an exception report',
645 )
645 )
646 reporting.add_argument(
646 reporting.add_argument(
647 "-H",
647 "-H",
648 "--htmlcov",
648 "--htmlcov",
649 action="store_true",
649 action="store_true",
650 help="create an HTML report of the coverage of the files",
650 help="create an HTML report of the coverage of the files",
651 )
651 )
652 reporting.add_argument(
652 reporting.add_argument(
653 "--json",
653 "--json",
654 action="store_true",
654 action="store_true",
655 help="store test result data in 'report.json' file",
655 help="store test result data in 'report.json' file",
656 )
656 )
657 reporting.add_argument(
657 reporting.add_argument(
658 "--outputdir",
658 "--outputdir",
659 help="directory to write error logs to (default=test directory)",
659 help="directory to write error logs to (default=test directory)",
660 )
660 )
661 reporting.add_argument(
661 reporting.add_argument(
662 "-n", "--nodiff", action="store_true", help="skip showing test changes"
662 "-n", "--nodiff", action="store_true", help="skip showing test changes"
663 )
663 )
664 reporting.add_argument(
664 reporting.add_argument(
665 "-S",
665 "-S",
666 "--noskips",
666 "--noskips",
667 action="store_true",
667 action="store_true",
668 help="don't report skip tests verbosely",
668 help="don't report skip tests verbosely",
669 )
669 )
670 reporting.add_argument(
670 reporting.add_argument(
671 "--time", action="store_true", help="time how long each test takes"
671 "--time", action="store_true", help="time how long each test takes"
672 )
672 )
673 reporting.add_argument("--view", help="external diff viewer")
673 reporting.add_argument("--view", help="external diff viewer")
674 reporting.add_argument(
674 reporting.add_argument(
675 "--xunit", help="record xunit results at specified path"
675 "--xunit", help="record xunit results at specified path"
676 )
676 )
677
677
678 for option, (envvar, default) in defaults.items():
678 for option, (envvar, default) in defaults.items():
679 defaults[option] = type(default)(os.environ.get(envvar, default))
679 defaults[option] = type(default)(os.environ.get(envvar, default))
680 parser.set_defaults(**defaults)
680 parser.set_defaults(**defaults)
681
681
682 return parser
682 return parser
683
683
684
684
685 def parseargs(args, parser):
685 def parseargs(args, parser):
686 """Parse arguments with our OptionParser and validate results."""
686 """Parse arguments with our OptionParser and validate results."""
687 options = parser.parse_args(args)
687 options = parser.parse_args(args)
688
688
689 # jython is always pure
689 # jython is always pure
690 if 'java' in sys.platform or '__pypy__' in sys.modules:
690 if 'java' in sys.platform or '__pypy__' in sys.modules:
691 options.pure = True
691 options.pure = True
692
692
693 if platform.python_implementation() != 'CPython' and options.rust:
693 if platform.python_implementation() != 'CPython' and options.rust:
694 parser.error('Rust extensions are only available with CPython')
694 parser.error('Rust extensions are only available with CPython')
695
695
696 if options.pure and options.rust:
696 if options.pure and options.rust:
697 parser.error('--rust cannot be used with --pure')
697 parser.error('--rust cannot be used with --pure')
698
698
699 if options.rust and options.no_rust:
699 if options.rust and options.no_rust:
700 parser.error('--rust cannot be used with --no-rust')
700 parser.error('--rust cannot be used with --no-rust')
701
701
702 if options.local:
702 if options.local:
703 if options.with_hg or options.with_rhg or options.with_chg:
703 if options.with_hg or options.with_rhg or options.with_chg:
704 parser.error(
704 parser.error(
705 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
705 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
706 )
706 )
707 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
707 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
708 reporootdir = os.path.dirname(testdir)
708 reporootdir = os.path.dirname(testdir)
709 pathandattrs = [(b'hg', 'with_hg')]
709 pathandattrs = [(b'hg', 'with_hg')]
710 if options.chg:
710 if options.chg:
711 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
711 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
712 if options.rhg:
712 if options.rhg:
713 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
713 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
714 for relpath, attr in pathandattrs:
714 for relpath, attr in pathandattrs:
715 binpath = os.path.join(reporootdir, relpath)
715 binpath = os.path.join(reporootdir, relpath)
716 if not (WINDOWS or os.access(binpath, os.X_OK)):
716 if not (WINDOWS or os.access(binpath, os.X_OK)):
717 parser.error(
717 parser.error(
718 '--local specified, but %r not found or '
718 '--local specified, but %r not found or '
719 'not executable' % binpath
719 'not executable' % binpath
720 )
720 )
721 setattr(options, attr, _bytes2sys(binpath))
721 setattr(options, attr, _bytes2sys(binpath))
722
722
723 if options.with_hg:
723 if options.with_hg:
724 options.with_hg = canonpath(_sys2bytes(options.with_hg))
724 options.with_hg = canonpath(_sys2bytes(options.with_hg))
725 if not (
725 if not (
726 os.path.isfile(options.with_hg)
726 os.path.isfile(options.with_hg)
727 and os.access(options.with_hg, os.X_OK)
727 and os.access(options.with_hg, os.X_OK)
728 ):
728 ):
729 parser.error('--with-hg must specify an executable hg script')
729 parser.error('--with-hg must specify an executable hg script')
730 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
730 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
731 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
731 msg = 'warning: --with-hg should specify an hg script, not: %s\n'
732 msg %= _bytes2sys(os.path.basename(options.with_hg))
732 msg %= _bytes2sys(os.path.basename(options.with_hg))
733 sys.stderr.write(msg)
733 sys.stderr.write(msg)
734 sys.stderr.flush()
734 sys.stderr.flush()
735
735
736 if (options.chg or options.with_chg) and WINDOWS:
736 if (options.chg or options.with_chg) and WINDOWS:
737 parser.error('chg does not work on %s' % os.name)
737 parser.error('chg does not work on %s' % os.name)
738 if (options.rhg or options.with_rhg) and WINDOWS:
738 if (options.rhg or options.with_rhg) and WINDOWS:
739 parser.error('rhg does not work on %s' % os.name)
739 parser.error('rhg does not work on %s' % os.name)
740 if options.with_chg:
740 if options.with_chg:
741 options.chg = False # no installation to temporary location
741 options.chg = False # no installation to temporary location
742 options.with_chg = canonpath(_sys2bytes(options.with_chg))
742 options.with_chg = canonpath(_sys2bytes(options.with_chg))
743 if not (
743 if not (
744 os.path.isfile(options.with_chg)
744 os.path.isfile(options.with_chg)
745 and os.access(options.with_chg, os.X_OK)
745 and os.access(options.with_chg, os.X_OK)
746 ):
746 ):
747 parser.error('--with-chg must specify a chg executable')
747 parser.error('--with-chg must specify a chg executable')
748 if options.with_rhg:
748 if options.with_rhg:
749 options.rhg = False # no installation to temporary location
749 options.rhg = False # no installation to temporary location
750 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
750 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
751 if not (
751 if not (
752 os.path.isfile(options.with_rhg)
752 os.path.isfile(options.with_rhg)
753 and os.access(options.with_rhg, os.X_OK)
753 and os.access(options.with_rhg, os.X_OK)
754 ):
754 ):
755 parser.error('--with-rhg must specify a rhg executable')
755 parser.error('--with-rhg must specify a rhg executable')
756 if options.chg and options.with_hg:
756 if options.chg and options.with_hg:
757 # chg shares installation location with hg
757 # chg shares installation location with hg
758 parser.error(
758 parser.error(
759 '--chg does not work when --with-hg is specified '
759 '--chg does not work when --with-hg is specified '
760 '(use --with-chg instead)'
760 '(use --with-chg instead)'
761 )
761 )
762 if options.rhg and options.with_hg:
762 if options.rhg and options.with_hg:
763 # rhg shares installation location with hg
763 # rhg shares installation location with hg
764 parser.error(
764 parser.error(
765 '--rhg does not work when --with-hg is specified '
765 '--rhg does not work when --with-hg is specified '
766 '(use --with-rhg instead)'
766 '(use --with-rhg instead)'
767 )
767 )
768 if options.rhg and options.chg:
768 if options.rhg and options.chg:
769 parser.error('--rhg and --chg do not work together')
769 parser.error('--rhg and --chg do not work together')
770
770
771 if options.color == 'always' and not pygmentspresent:
771 if options.color == 'always' and not pygmentspresent:
772 sys.stderr.write(
772 sys.stderr.write(
773 'warning: --color=always ignored because '
773 'warning: --color=always ignored because '
774 'pygments is not installed\n'
774 'pygments is not installed\n'
775 )
775 )
776
776
777 if options.bisect_repo and not options.known_good_rev:
777 if options.bisect_repo and not options.known_good_rev:
778 parser.error("--bisect-repo cannot be used without --known-good-rev")
778 parser.error("--bisect-repo cannot be used without --known-good-rev")
779
779
780 global useipv6
780 global useipv6
781 if options.ipv6:
781 if options.ipv6:
782 useipv6 = checksocketfamily('AF_INET6')
782 useipv6 = checksocketfamily('AF_INET6')
783 else:
783 else:
784 # only use IPv6 if IPv4 is unavailable and IPv6 is available
784 # only use IPv6 if IPv4 is unavailable and IPv6 is available
785 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
785 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
786 'AF_INET6'
786 'AF_INET6'
787 )
787 )
788
788
789 options.anycoverage = options.cover or options.annotate or options.htmlcov
789 options.anycoverage = options.cover or options.annotate or options.htmlcov
790 if options.anycoverage:
790 if options.anycoverage:
791 try:
791 try:
792 import coverage
792 import coverage
793
793
794 covver = version.StrictVersion(coverage.__version__).version
794 covver = version.StrictVersion(coverage.__version__).version
795 if covver < (3, 3):
795 if covver < (3, 3):
796 parser.error('coverage options require coverage 3.3 or later')
796 parser.error('coverage options require coverage 3.3 or later')
797 except ImportError:
797 except ImportError:
798 parser.error('coverage options now require the coverage package')
798 parser.error('coverage options now require the coverage package')
799
799
800 if options.anycoverage and options.local:
800 if options.anycoverage and options.local:
801 # this needs some path mangling somewhere, I guess
801 # this needs some path mangling somewhere, I guess
802 parser.error(
802 parser.error(
803 "sorry, coverage options do not work when --local " "is specified"
803 "sorry, coverage options do not work when --local " "is specified"
804 )
804 )
805
805
806 if options.anycoverage and options.with_hg:
806 if options.anycoverage and options.with_hg:
807 parser.error(
807 parser.error(
808 "sorry, coverage options do not work when --with-hg " "is specified"
808 "sorry, coverage options do not work when --with-hg " "is specified"
809 )
809 )
810
810
811 global verbose
811 global verbose
812 if options.verbose:
812 if options.verbose:
813 verbose = ''
813 verbose = ''
814
814
815 if options.tmpdir:
815 if options.tmpdir:
816 options.tmpdir = canonpath(options.tmpdir)
816 options.tmpdir = canonpath(options.tmpdir)
817
817
818 if options.jobs < 1:
818 if options.jobs < 1:
819 parser.error('--jobs must be positive')
819 parser.error('--jobs must be positive')
820 if options.interactive and options.debug:
820 if options.interactive and options.debug:
821 parser.error("-i/--interactive and -d/--debug are incompatible")
821 parser.error("-i/--interactive and -d/--debug are incompatible")
822 if options.debug:
822 if options.debug:
823 if options.timeout != defaults['timeout']:
823 if options.timeout != defaults['timeout']:
824 sys.stderr.write('warning: --timeout option ignored with --debug\n')
824 sys.stderr.write('warning: --timeout option ignored with --debug\n')
825 if options.slowtimeout != defaults['slowtimeout']:
825 if options.slowtimeout != defaults['slowtimeout']:
826 sys.stderr.write(
826 sys.stderr.write(
827 'warning: --slowtimeout option ignored with --debug\n'
827 'warning: --slowtimeout option ignored with --debug\n'
828 )
828 )
829 options.timeout = 0
829 options.timeout = 0
830 options.slowtimeout = 0
830 options.slowtimeout = 0
831
831
832 if options.blacklist:
832 if options.blacklist:
833 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
833 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
834 if options.whitelist:
834 if options.whitelist:
835 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
835 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
836 else:
836 else:
837 options.whitelisted = {}
837 options.whitelisted = {}
838
838
839 if options.showchannels:
839 if options.showchannels:
840 options.nodiff = True
840 options.nodiff = True
841
841
842 return options
842 return options
843
843
844
844
845 def rename(src, dst):
845 def rename(src, dst):
846 """Like os.rename(), trade atomicity and opened files friendliness
846 """Like os.rename(), trade atomicity and opened files friendliness
847 for existing destination support.
847 for existing destination support.
848 """
848 """
849 shutil.copy(src, dst)
849 shutil.copy(src, dst)
850 os.remove(src)
850 os.remove(src)
851
851
852
852
853 def makecleanable(path):
853 def makecleanable(path):
854 """Try to fix directory permission recursively so that the entire tree
854 """Try to fix directory permission recursively so that the entire tree
855 can be deleted"""
855 can be deleted"""
856 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
856 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
857 for d in dirnames:
857 for d in dirnames:
858 p = os.path.join(dirpath, d)
858 p = os.path.join(dirpath, d)
859 try:
859 try:
860 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
860 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
861 except OSError:
861 except OSError:
862 pass
862 pass
863
863
864
864
865 _unified_diff = difflib.unified_diff
865 _unified_diff = difflib.unified_diff
866 if PYTHON3:
866 if PYTHON3:
867 import functools
867 import functools
868
868
869 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
869 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
870
870
871
871
872 def getdiff(expected, output, ref, err):
872 def getdiff(expected, output, ref, err):
873 servefail = False
873 servefail = False
874 lines = []
874 lines = []
875 for line in _unified_diff(expected, output, ref, err):
875 for line in _unified_diff(expected, output, ref, err):
876 if line.startswith(b'+++') or line.startswith(b'---'):
876 if line.startswith(b'+++') or line.startswith(b'---'):
877 line = line.replace(b'\\', b'/')
877 line = line.replace(b'\\', b'/')
878 if line.endswith(b' \n'):
878 if line.endswith(b' \n'):
879 line = line[:-2] + b'\n'
879 line = line[:-2] + b'\n'
880 lines.append(line)
880 lines.append(line)
881 if not servefail and line.startswith(
881 if not servefail and line.startswith(
882 b'+ abort: child process failed to start'
882 b'+ abort: child process failed to start'
883 ):
883 ):
884 servefail = True
884 servefail = True
885
885
886 return servefail, lines
886 return servefail, lines
887
887
888
888
889 verbose = False
889 verbose = False
890
890
891
891
892 def vlog(*msg):
892 def vlog(*msg):
893 """Log only when in verbose mode."""
893 """Log only when in verbose mode."""
894 if verbose is False:
894 if verbose is False:
895 return
895 return
896
896
897 return log(*msg)
897 return log(*msg)
898
898
899
899
900 # Bytes that break XML even in a CDATA block: control characters 0-31
900 # Bytes that break XML even in a CDATA block: control characters 0-31
901 # sans \t, \n and \r
901 # sans \t, \n and \r
902 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
902 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
903
903
904 # Match feature conditionalized output lines in the form, capturing the feature
904 # Match feature conditionalized output lines in the form, capturing the feature
905 # list in group 2, and the preceeding line output in group 1:
905 # list in group 2, and the preceeding line output in group 1:
906 #
906 #
907 # output..output (feature !)\n
907 # output..output (feature !)\n
908 optline = re.compile(br'(.*) \((.+?) !\)\n$')
908 optline = re.compile(br'(.*) \((.+?) !\)\n$')
909
909
910
910
911 def cdatasafe(data):
911 def cdatasafe(data):
912 """Make a string safe to include in a CDATA block.
912 """Make a string safe to include in a CDATA block.
913
913
914 Certain control characters are illegal in a CDATA block, and
914 Certain control characters are illegal in a CDATA block, and
915 there's no way to include a ]]> in a CDATA either. This function
915 there's no way to include a ]]> in a CDATA either. This function
916 replaces illegal bytes with ? and adds a space between the ]] so
916 replaces illegal bytes with ? and adds a space between the ]] so
917 that it won't break the CDATA block.
917 that it won't break the CDATA block.
918 """
918 """
919 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
919 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
920
920
921
921
922 def log(*msg):
922 def log(*msg):
923 """Log something to stdout.
923 """Log something to stdout.
924
924
925 Arguments are strings to print.
925 Arguments are strings to print.
926 """
926 """
927 with iolock:
927 with iolock:
928 if verbose:
928 if verbose:
929 print(verbose, end=' ')
929 print(verbose, end=' ')
930 for m in msg:
930 for m in msg:
931 print(m, end=' ')
931 print(m, end=' ')
932 print()
932 print()
933 sys.stdout.flush()
933 sys.stdout.flush()
934
934
935
935
936 def highlightdiff(line, color):
936 def highlightdiff(line, color):
937 if not color:
937 if not color:
938 return line
938 return line
939 assert pygmentspresent
939 assert pygmentspresent
940 return pygments.highlight(
940 return pygments.highlight(
941 line.decode('latin1'), difflexer, terminal256formatter
941 line.decode('latin1'), difflexer, terminal256formatter
942 ).encode('latin1')
942 ).encode('latin1')
943
943
944
944
945 def highlightmsg(msg, color):
945 def highlightmsg(msg, color):
946 if not color:
946 if not color:
947 return msg
947 return msg
948 assert pygmentspresent
948 assert pygmentspresent
949 return pygments.highlight(msg, runnerlexer, runnerformatter)
949 return pygments.highlight(msg, runnerlexer, runnerformatter)
950
950
951
951
952 def terminate(proc):
952 def terminate(proc):
953 """Terminate subprocess"""
953 """Terminate subprocess"""
954 vlog('# Terminating process %d' % proc.pid)
954 vlog('# Terminating process %d' % proc.pid)
955 try:
955 try:
956 proc.terminate()
956 proc.terminate()
957 except OSError:
957 except OSError:
958 pass
958 pass
959
959
960
960
961 def killdaemons(pidfile):
961 def killdaemons(pidfile):
962 import killdaemons as killmod
962 import killdaemons as killmod
963
963
964 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
964 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
965
965
966
966
967 class Test(unittest.TestCase):
967 class Test(unittest.TestCase):
968 """Encapsulates a single, runnable test.
968 """Encapsulates a single, runnable test.
969
969
970 While this class conforms to the unittest.TestCase API, it differs in that
970 While this class conforms to the unittest.TestCase API, it differs in that
971 instances need to be instantiated manually. (Typically, unittest.TestCase
971 instances need to be instantiated manually. (Typically, unittest.TestCase
972 classes are instantiated automatically by scanning modules.)
972 classes are instantiated automatically by scanning modules.)
973 """
973 """
974
974
975 # Status code reserved for skipped tests (used by hghave).
975 # Status code reserved for skipped tests (used by hghave).
976 SKIPPED_STATUS = 80
976 SKIPPED_STATUS = 80
977
977
978 def __init__(
978 def __init__(
979 self,
979 self,
980 path,
980 path,
981 outputdir,
981 outputdir,
982 tmpdir,
982 tmpdir,
983 keeptmpdir=False,
983 keeptmpdir=False,
984 debug=False,
984 debug=False,
985 first=False,
985 first=False,
986 timeout=None,
986 timeout=None,
987 startport=None,
987 startport=None,
988 extraconfigopts=None,
988 extraconfigopts=None,
989 shell=None,
989 shell=None,
990 hgcommand=None,
990 hgcommand=None,
991 slowtimeout=None,
991 slowtimeout=None,
992 usechg=False,
992 usechg=False,
993 chgdebug=False,
993 chgdebug=False,
994 useipv6=False,
994 useipv6=False,
995 ):
995 ):
996 """Create a test from parameters.
996 """Create a test from parameters.
997
997
998 path is the full path to the file defining the test.
998 path is the full path to the file defining the test.
999
999
1000 tmpdir is the main temporary directory to use for this test.
1000 tmpdir is the main temporary directory to use for this test.
1001
1001
1002 keeptmpdir determines whether to keep the test's temporary directory
1002 keeptmpdir determines whether to keep the test's temporary directory
1003 after execution. It defaults to removal (False).
1003 after execution. It defaults to removal (False).
1004
1004
1005 debug mode will make the test execute verbosely, with unfiltered
1005 debug mode will make the test execute verbosely, with unfiltered
1006 output.
1006 output.
1007
1007
1008 timeout controls the maximum run time of the test. It is ignored when
1008 timeout controls the maximum run time of the test. It is ignored when
1009 debug is True. See slowtimeout for tests with #require slow.
1009 debug is True. See slowtimeout for tests with #require slow.
1010
1010
1011 slowtimeout overrides timeout if the test has #require slow.
1011 slowtimeout overrides timeout if the test has #require slow.
1012
1012
1013 startport controls the starting port number to use for this test. Each
1013 startport controls the starting port number to use for this test. Each
1014 test will reserve 3 port numbers for execution. It is the caller's
1014 test will reserve 3 port numbers for execution. It is the caller's
1015 responsibility to allocate a non-overlapping port range to Test
1015 responsibility to allocate a non-overlapping port range to Test
1016 instances.
1016 instances.
1017
1017
1018 extraconfigopts is an iterable of extra hgrc config options. Values
1018 extraconfigopts is an iterable of extra hgrc config options. Values
1019 must have the form "key=value" (something understood by hgrc). Values
1019 must have the form "key=value" (something understood by hgrc). Values
1020 of the form "foo.key=value" will result in "[foo] key=value".
1020 of the form "foo.key=value" will result in "[foo] key=value".
1021
1021
1022 shell is the shell to execute tests in.
1022 shell is the shell to execute tests in.
1023 """
1023 """
1024 if timeout is None:
1024 if timeout is None:
1025 timeout = defaults['timeout']
1025 timeout = defaults['timeout']
1026 if startport is None:
1026 if startport is None:
1027 startport = defaults['port']
1027 startport = defaults['port']
1028 if slowtimeout is None:
1028 if slowtimeout is None:
1029 slowtimeout = defaults['slowtimeout']
1029 slowtimeout = defaults['slowtimeout']
1030 self.path = path
1030 self.path = path
1031 self.relpath = os.path.relpath(path)
1031 self.relpath = os.path.relpath(path)
1032 self.bname = os.path.basename(path)
1032 self.bname = os.path.basename(path)
1033 self.name = _bytes2sys(self.bname)
1033 self.name = _bytes2sys(self.bname)
1034 self._testdir = os.path.dirname(path)
1034 self._testdir = os.path.dirname(path)
1035 self._outputdir = outputdir
1035 self._outputdir = outputdir
1036 self._tmpname = os.path.basename(path)
1036 self._tmpname = os.path.basename(path)
1037 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1037 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1038
1038
1039 self._threadtmp = tmpdir
1039 self._threadtmp = tmpdir
1040 self._keeptmpdir = keeptmpdir
1040 self._keeptmpdir = keeptmpdir
1041 self._debug = debug
1041 self._debug = debug
1042 self._first = first
1042 self._first = first
1043 self._timeout = timeout
1043 self._timeout = timeout
1044 self._slowtimeout = slowtimeout
1044 self._slowtimeout = slowtimeout
1045 self._startport = startport
1045 self._startport = startport
1046 self._extraconfigopts = extraconfigopts or []
1046 self._extraconfigopts = extraconfigopts or []
1047 self._shell = _sys2bytes(shell)
1047 self._shell = _sys2bytes(shell)
1048 self._hgcommand = hgcommand or b'hg'
1048 self._hgcommand = hgcommand or b'hg'
1049 self._usechg = usechg
1049 self._usechg = usechg
1050 self._chgdebug = chgdebug
1050 self._chgdebug = chgdebug
1051 self._useipv6 = useipv6
1051 self._useipv6 = useipv6
1052
1052
1053 self._aborted = False
1053 self._aborted = False
1054 self._daemonpids = []
1054 self._daemonpids = []
1055 self._finished = None
1055 self._finished = None
1056 self._ret = None
1056 self._ret = None
1057 self._out = None
1057 self._out = None
1058 self._skipped = None
1058 self._skipped = None
1059 self._testtmp = None
1059 self._testtmp = None
1060 self._chgsockdir = None
1060 self._chgsockdir = None
1061
1061
1062 self._refout = self.readrefout()
1062 self._refout = self.readrefout()
1063
1063
1064 def readrefout(self):
1064 def readrefout(self):
1065 """read reference output"""
1065 """read reference output"""
1066 # If we're not in --debug mode and reference output file exists,
1066 # If we're not in --debug mode and reference output file exists,
1067 # check test output against it.
1067 # check test output against it.
1068 if self._debug:
1068 if self._debug:
1069 return None # to match "out is None"
1069 return None # to match "out is None"
1070 elif os.path.exists(self.refpath):
1070 elif os.path.exists(self.refpath):
1071 with open(self.refpath, 'rb') as f:
1071 with open(self.refpath, 'rb') as f:
1072 return f.read().splitlines(True)
1072 return f.read().splitlines(True)
1073 else:
1073 else:
1074 return []
1074 return []
1075
1075
1076 # needed to get base class __repr__ running
1076 # needed to get base class __repr__ running
1077 @property
1077 @property
1078 def _testMethodName(self):
1078 def _testMethodName(self):
1079 return self.name
1079 return self.name
1080
1080
1081 def __str__(self):
1081 def __str__(self):
1082 return self.name
1082 return self.name
1083
1083
1084 def shortDescription(self):
1084 def shortDescription(self):
1085 return self.name
1085 return self.name
1086
1086
1087 def setUp(self):
1087 def setUp(self):
1088 """Tasks to perform before run()."""
1088 """Tasks to perform before run()."""
1089 self._finished = False
1089 self._finished = False
1090 self._ret = None
1090 self._ret = None
1091 self._out = None
1091 self._out = None
1092 self._skipped = None
1092 self._skipped = None
1093
1093
1094 try:
1094 try:
1095 os.mkdir(self._threadtmp)
1095 os.mkdir(self._threadtmp)
1096 except OSError as e:
1096 except OSError as e:
1097 if e.errno != errno.EEXIST:
1097 if e.errno != errno.EEXIST:
1098 raise
1098 raise
1099
1099
1100 name = self._tmpname
1100 name = self._tmpname
1101 self._testtmp = os.path.join(self._threadtmp, name)
1101 self._testtmp = os.path.join(self._threadtmp, name)
1102 os.mkdir(self._testtmp)
1102 os.mkdir(self._testtmp)
1103
1103
1104 # Remove any previous output files.
1104 # Remove any previous output files.
1105 if os.path.exists(self.errpath):
1105 if os.path.exists(self.errpath):
1106 try:
1106 try:
1107 os.remove(self.errpath)
1107 os.remove(self.errpath)
1108 except OSError as e:
1108 except OSError as e:
1109 # We might have raced another test to clean up a .err
1109 # We might have raced another test to clean up a .err
1110 # file, so ignore ENOENT when removing a previous .err
1110 # file, so ignore ENOENT when removing a previous .err
1111 # file.
1111 # file.
1112 if e.errno != errno.ENOENT:
1112 if e.errno != errno.ENOENT:
1113 raise
1113 raise
1114
1114
1115 if self._usechg:
1115 if self._usechg:
1116 self._chgsockdir = os.path.join(
1116 self._chgsockdir = os.path.join(
1117 self._threadtmp, b'%s.chgsock' % name
1117 self._threadtmp, b'%s.chgsock' % name
1118 )
1118 )
1119 os.mkdir(self._chgsockdir)
1119 os.mkdir(self._chgsockdir)
1120
1120
1121 def run(self, result):
1121 def run(self, result):
1122 """Run this test and report results against a TestResult instance."""
1122 """Run this test and report results against a TestResult instance."""
1123 # This function is extremely similar to unittest.TestCase.run(). Once
1123 # This function is extremely similar to unittest.TestCase.run(). Once
1124 # we require Python 2.7 (or at least its version of unittest), this
1124 # we require Python 2.7 (or at least its version of unittest), this
1125 # function can largely go away.
1125 # function can largely go away.
1126 self._result = result
1126 self._result = result
1127 result.startTest(self)
1127 result.startTest(self)
1128 try:
1128 try:
1129 try:
1129 try:
1130 self.setUp()
1130 self.setUp()
1131 except (KeyboardInterrupt, SystemExit):
1131 except (KeyboardInterrupt, SystemExit):
1132 self._aborted = True
1132 self._aborted = True
1133 raise
1133 raise
1134 except Exception:
1134 except Exception:
1135 result.addError(self, sys.exc_info())
1135 result.addError(self, sys.exc_info())
1136 return
1136 return
1137
1137
1138 success = False
1138 success = False
1139 try:
1139 try:
1140 self.runTest()
1140 self.runTest()
1141 except KeyboardInterrupt:
1141 except KeyboardInterrupt:
1142 self._aborted = True
1142 self._aborted = True
1143 raise
1143 raise
1144 except unittest.SkipTest as e:
1144 except unittest.SkipTest as e:
1145 result.addSkip(self, str(e))
1145 result.addSkip(self, str(e))
1146 # The base class will have already counted this as a
1146 # The base class will have already counted this as a
1147 # test we "ran", but we want to exclude skipped tests
1147 # test we "ran", but we want to exclude skipped tests
1148 # from those we count towards those run.
1148 # from those we count towards those run.
1149 result.testsRun -= 1
1149 result.testsRun -= 1
1150 except self.failureException as e:
1150 except self.failureException as e:
1151 # This differs from unittest in that we don't capture
1151 # This differs from unittest in that we don't capture
1152 # the stack trace. This is for historical reasons and
1152 # the stack trace. This is for historical reasons and
1153 # this decision could be revisited in the future,
1153 # this decision could be revisited in the future,
1154 # especially for PythonTest instances.
1154 # especially for PythonTest instances.
1155 if result.addFailure(self, str(e)):
1155 if result.addFailure(self, str(e)):
1156 success = True
1156 success = True
1157 except Exception:
1157 except Exception:
1158 result.addError(self, sys.exc_info())
1158 result.addError(self, sys.exc_info())
1159 else:
1159 else:
1160 success = True
1160 success = True
1161
1161
1162 try:
1162 try:
1163 self.tearDown()
1163 self.tearDown()
1164 except (KeyboardInterrupt, SystemExit):
1164 except (KeyboardInterrupt, SystemExit):
1165 self._aborted = True
1165 self._aborted = True
1166 raise
1166 raise
1167 except Exception:
1167 except Exception:
1168 result.addError(self, sys.exc_info())
1168 result.addError(self, sys.exc_info())
1169 success = False
1169 success = False
1170
1170
1171 if success:
1171 if success:
1172 result.addSuccess(self)
1172 result.addSuccess(self)
1173 finally:
1173 finally:
1174 result.stopTest(self, interrupted=self._aborted)
1174 result.stopTest(self, interrupted=self._aborted)
1175
1175
1176 def runTest(self):
1176 def runTest(self):
1177 """Run this test instance.
1177 """Run this test instance.
1178
1178
1179 This will return a tuple describing the result of the test.
1179 This will return a tuple describing the result of the test.
1180 """
1180 """
1181 env = self._getenv()
1181 env = self._getenv()
1182 self._genrestoreenv(env)
1182 self._genrestoreenv(env)
1183 self._daemonpids.append(env['DAEMON_PIDS'])
1183 self._daemonpids.append(env['DAEMON_PIDS'])
1184 self._createhgrc(env['HGRCPATH'])
1184 self._createhgrc(env['HGRCPATH'])
1185
1185
1186 vlog('# Test', self.name)
1186 vlog('# Test', self.name)
1187
1187
1188 ret, out = self._run(env)
1188 ret, out = self._run(env)
1189 self._finished = True
1189 self._finished = True
1190 self._ret = ret
1190 self._ret = ret
1191 self._out = out
1191 self._out = out
1192
1192
1193 def describe(ret):
1193 def describe(ret):
1194 if ret < 0:
1194 if ret < 0:
1195 return 'killed by signal: %d' % -ret
1195 return 'killed by signal: %d' % -ret
1196 return 'returned error code %d' % ret
1196 return 'returned error code %d' % ret
1197
1197
1198 self._skipped = False
1198 self._skipped = False
1199
1199
1200 if ret == self.SKIPPED_STATUS:
1200 if ret == self.SKIPPED_STATUS:
1201 if out is None: # Debug mode, nothing to parse.
1201 if out is None: # Debug mode, nothing to parse.
1202 missing = ['unknown']
1202 missing = ['unknown']
1203 failed = None
1203 failed = None
1204 else:
1204 else:
1205 missing, failed = TTest.parsehghaveoutput(out)
1205 missing, failed = TTest.parsehghaveoutput(out)
1206
1206
1207 if not missing:
1207 if not missing:
1208 missing = ['skipped']
1208 missing = ['skipped']
1209
1209
1210 if failed:
1210 if failed:
1211 self.fail('hg have failed checking for %s' % failed[-1])
1211 self.fail('hg have failed checking for %s' % failed[-1])
1212 else:
1212 else:
1213 self._skipped = True
1213 self._skipped = True
1214 raise unittest.SkipTest(missing[-1])
1214 raise unittest.SkipTest(missing[-1])
1215 elif ret == 'timeout':
1215 elif ret == 'timeout':
1216 self.fail('timed out')
1216 self.fail('timed out')
1217 elif ret is False:
1217 elif ret is False:
1218 self.fail('no result code from test')
1218 self.fail('no result code from test')
1219 elif out != self._refout:
1219 elif out != self._refout:
1220 # Diff generation may rely on written .err file.
1220 # Diff generation may rely on written .err file.
1221 if (
1221 if (
1222 (ret != 0 or out != self._refout)
1222 (ret != 0 or out != self._refout)
1223 and not self._skipped
1223 and not self._skipped
1224 and not self._debug
1224 and not self._debug
1225 ):
1225 ):
1226 with open(self.errpath, 'wb') as f:
1226 with open(self.errpath, 'wb') as f:
1227 for line in out:
1227 for line in out:
1228 f.write(line)
1228 f.write(line)
1229
1229
1230 # The result object handles diff calculation for us.
1230 # The result object handles diff calculation for us.
1231 with firstlock:
1231 with firstlock:
1232 if self._result.addOutputMismatch(self, ret, out, self._refout):
1232 if self._result.addOutputMismatch(self, ret, out, self._refout):
1233 # change was accepted, skip failing
1233 # change was accepted, skip failing
1234 return
1234 return
1235 if self._first:
1235 if self._first:
1236 global firsterror
1236 global firsterror
1237 firsterror = True
1237 firsterror = True
1238
1238
1239 if ret:
1239 if ret:
1240 msg = 'output changed and ' + describe(ret)
1240 msg = 'output changed and ' + describe(ret)
1241 else:
1241 else:
1242 msg = 'output changed'
1242 msg = 'output changed'
1243
1243
1244 self.fail(msg)
1244 self.fail(msg)
1245 elif ret:
1245 elif ret:
1246 self.fail(describe(ret))
1246 self.fail(describe(ret))
1247
1247
1248 def tearDown(self):
1248 def tearDown(self):
1249 """Tasks to perform after run()."""
1249 """Tasks to perform after run()."""
1250 for entry in self._daemonpids:
1250 for entry in self._daemonpids:
1251 killdaemons(entry)
1251 killdaemons(entry)
1252 self._daemonpids = []
1252 self._daemonpids = []
1253
1253
1254 if self._keeptmpdir:
1254 if self._keeptmpdir:
1255 log(
1255 log(
1256 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1256 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1257 % (
1257 % (
1258 _bytes2sys(self._testtmp),
1258 _bytes2sys(self._testtmp),
1259 _bytes2sys(self._threadtmp),
1259 _bytes2sys(self._threadtmp),
1260 )
1260 )
1261 )
1261 )
1262 else:
1262 else:
1263 try:
1263 try:
1264 shutil.rmtree(self._testtmp)
1264 shutil.rmtree(self._testtmp)
1265 except OSError:
1265 except OSError:
1266 # unreadable directory may be left in $TESTTMP; fix permission
1266 # unreadable directory may be left in $TESTTMP; fix permission
1267 # and try again
1267 # and try again
1268 makecleanable(self._testtmp)
1268 makecleanable(self._testtmp)
1269 shutil.rmtree(self._testtmp, True)
1269 shutil.rmtree(self._testtmp, True)
1270 shutil.rmtree(self._threadtmp, True)
1270 shutil.rmtree(self._threadtmp, True)
1271
1271
1272 if self._usechg:
1272 if self._usechg:
1273 # chgservers will stop automatically after they find the socket
1273 # chgservers will stop automatically after they find the socket
1274 # files are deleted
1274 # files are deleted
1275 shutil.rmtree(self._chgsockdir, True)
1275 shutil.rmtree(self._chgsockdir, True)
1276
1276
1277 if (
1277 if (
1278 (self._ret != 0 or self._out != self._refout)
1278 (self._ret != 0 or self._out != self._refout)
1279 and not self._skipped
1279 and not self._skipped
1280 and not self._debug
1280 and not self._debug
1281 and self._out
1281 and self._out
1282 ):
1282 ):
1283 with open(self.errpath, 'wb') as f:
1283 with open(self.errpath, 'wb') as f:
1284 for line in self._out:
1284 for line in self._out:
1285 f.write(line)
1285 f.write(line)
1286
1286
1287 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1287 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1288
1288
1289 def _run(self, env):
1289 def _run(self, env):
1290 # This should be implemented in child classes to run tests.
1290 # This should be implemented in child classes to run tests.
1291 raise unittest.SkipTest('unknown test type')
1291 raise unittest.SkipTest('unknown test type')
1292
1292
1293 def abort(self):
1293 def abort(self):
1294 """Terminate execution of this test."""
1294 """Terminate execution of this test."""
1295 self._aborted = True
1295 self._aborted = True
1296
1296
1297 def _portmap(self, i):
1297 def _portmap(self, i):
1298 offset = b'' if i == 0 else b'%d' % i
1298 offset = b'' if i == 0 else b'%d' % i
1299 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1299 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1300
1300
1301 def _getreplacements(self):
1301 def _getreplacements(self):
1302 """Obtain a mapping of text replacements to apply to test output.
1302 """Obtain a mapping of text replacements to apply to test output.
1303
1303
1304 Test output needs to be normalized so it can be compared to expected
1304 Test output needs to be normalized so it can be compared to expected
1305 output. This function defines how some of that normalization will
1305 output. This function defines how some of that normalization will
1306 occur.
1306 occur.
1307 """
1307 """
1308 r = [
1308 r = [
1309 # This list should be parallel to defineport in _getenv
1309 # This list should be parallel to defineport in _getenv
1310 self._portmap(0),
1310 self._portmap(0),
1311 self._portmap(1),
1311 self._portmap(1),
1312 self._portmap(2),
1312 self._portmap(2),
1313 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1313 (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$'),
1314 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1315 ]
1315 ]
1316 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1316 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1317 if WINDOWS:
1317 if WINDOWS:
1318 # JSON output escapes backslashes in Windows paths, so also catch a
1318 # JSON output escapes backslashes in Windows paths, so also catch a
1319 # double-escape.
1319 # double-escape.
1320 replaced = self._testtmp.replace(b'\\', br'\\')
1320 replaced = self._testtmp.replace(b'\\', br'\\')
1321 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1321 r.append((self._escapepath(replaced), b'$STR_REPR_TESTTMP'))
1322
1322
1323 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1323 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1324
1324
1325 if os.path.exists(replacementfile):
1325 if os.path.exists(replacementfile):
1326 data = {}
1326 data = {}
1327 with open(replacementfile, mode='rb') as source:
1327 with open(replacementfile, mode='rb') as source:
1328 # the intermediate 'compile' step help with debugging
1328 # the intermediate 'compile' step help with debugging
1329 code = compile(source.read(), replacementfile, 'exec')
1329 code = compile(source.read(), replacementfile, 'exec')
1330 exec(code, data)
1330 exec(code, data)
1331 for value in data.get('substitutions', ()):
1331 for value in data.get('substitutions', ()):
1332 if len(value) != 2:
1332 if len(value) != 2:
1333 msg = 'malformatted substitution in %s: %r'
1333 msg = 'malformatted substitution in %s: %r'
1334 msg %= (replacementfile, value)
1334 msg %= (replacementfile, value)
1335 raise ValueError(msg)
1335 raise ValueError(msg)
1336 r.append(value)
1336 r.append(value)
1337 return r
1337 return r
1338
1338
1339 def _escapepath(self, p):
1339 def _escapepath(self, p):
1340 if WINDOWS:
1340 if WINDOWS:
1341 return b''.join(
1341 return b''.join(
1342 c.isalpha()
1342 c.isalpha()
1343 and b'[%s%s]' % (c.lower(), c.upper())
1343 and b'[%s%s]' % (c.lower(), c.upper())
1344 or c in b'/\\'
1344 or c in b'/\\'
1345 and br'[/\\]'
1345 and br'[/\\]'
1346 or c.isdigit()
1346 or c.isdigit()
1347 and c
1347 and c
1348 or b'\\' + c
1348 or b'\\' + c
1349 for c in [p[i : i + 1] for i in range(len(p))]
1349 for c in [p[i : i + 1] for i in range(len(p))]
1350 )
1350 )
1351 else:
1351 else:
1352 return re.escape(p)
1352 return re.escape(p)
1353
1353
1354 def _localip(self):
1354 def _localip(self):
1355 if self._useipv6:
1355 if self._useipv6:
1356 return b'::1'
1356 return b'::1'
1357 else:
1357 else:
1358 return b'127.0.0.1'
1358 return b'127.0.0.1'
1359
1359
1360 def _genrestoreenv(self, testenv):
1360 def _genrestoreenv(self, testenv):
1361 """Generate a script that can be used by tests to restore the original
1361 """Generate a script that can be used by tests to restore the original
1362 environment."""
1362 environment."""
1363 # Put the restoreenv script inside self._threadtmp
1363 # Put the restoreenv script inside self._threadtmp
1364 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1364 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1365 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1365 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1366
1366
1367 # Only restore environment variable names that the shell allows
1367 # Only restore environment variable names that the shell allows
1368 # us to export.
1368 # us to export.
1369 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1369 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1370
1370
1371 # Do not restore these variables; otherwise tests would fail.
1371 # Do not restore these variables; otherwise tests would fail.
1372 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1372 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1373
1373
1374 with open(scriptpath, 'w') as envf:
1374 with open(scriptpath, 'w') as envf:
1375 for name, value in origenviron.items():
1375 for name, value in origenviron.items():
1376 if not name_regex.match(name):
1376 if not name_regex.match(name):
1377 # Skip environment variables with unusual names not
1377 # Skip environment variables with unusual names not
1378 # allowed by most shells.
1378 # allowed by most shells.
1379 continue
1379 continue
1380 if name in reqnames:
1380 if name in reqnames:
1381 continue
1381 continue
1382 envf.write('%s=%s\n' % (name, shellquote(value)))
1382 envf.write('%s=%s\n' % (name, shellquote(value)))
1383
1383
1384 for name in testenv:
1384 for name in testenv:
1385 if name in origenviron or name in reqnames:
1385 if name in origenviron or name in reqnames:
1386 continue
1386 continue
1387 envf.write('unset %s\n' % (name,))
1387 envf.write('unset %s\n' % (name,))
1388
1388
1389 def _getenv(self):
1389 def _getenv(self):
1390 """Obtain environment variables to use during test execution."""
1390 """Obtain environment variables to use during test execution."""
1391
1391
1392 def defineport(i):
1392 def defineport(i):
1393 offset = '' if i == 0 else '%s' % i
1393 offset = '' if i == 0 else '%s' % i
1394 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1394 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1395
1395
1396 env = os.environ.copy()
1396 env = os.environ.copy()
1397 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1397 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1398 env['HGEMITWARNINGS'] = '1'
1398 env['HGEMITWARNINGS'] = '1'
1399 env['TESTTMP'] = _bytes2sys(self._testtmp)
1399 env['TESTTMP'] = _bytes2sys(self._testtmp)
1400 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1400 uid_file = os.path.join(_bytes2sys(self._testtmp), 'UID')
1401 env['HGTEST_UUIDFILE'] = uid_file
1401 env['HGTEST_UUIDFILE'] = uid_file
1402 env['TESTNAME'] = self.name
1402 env['TESTNAME'] = self.name
1403 env['HOME'] = _bytes2sys(self._testtmp)
1403 env['HOME'] = _bytes2sys(self._testtmp)
1404 if WINDOWS:
1404 if WINDOWS:
1405 env['REALUSERPROFILE'] = env['USERPROFILE']
1405 env['REALUSERPROFILE'] = env['USERPROFILE']
1406 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1406 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1407 env['USERPROFILE'] = env['HOME']
1407 env['USERPROFILE'] = env['HOME']
1408 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1408 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1409 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1409 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1410 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1410 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1411 # This number should match portneeded in _getport
1411 # This number should match portneeded in _getport
1412 for port in xrange(3):
1412 for port in xrange(3):
1413 # This list should be parallel to _portmap in _getreplacements
1413 # This list should be parallel to _portmap in _getreplacements
1414 defineport(port)
1414 defineport(port)
1415 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1415 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1416 env["DAEMON_PIDS"] = _bytes2sys(
1416 env["DAEMON_PIDS"] = _bytes2sys(
1417 os.path.join(self._threadtmp, b'daemon.pids')
1417 os.path.join(self._threadtmp, b'daemon.pids')
1418 )
1418 )
1419 env["HGEDITOR"] = (
1419 env["HGEDITOR"] = (
1420 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1420 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1421 )
1421 )
1422 env["HGUSER"] = "test"
1422 env["HGUSER"] = "test"
1423 env["HGENCODING"] = "ascii"
1423 env["HGENCODING"] = "ascii"
1424 env["HGENCODINGMODE"] = "strict"
1424 env["HGENCODINGMODE"] = "strict"
1425 env["HGHOSTNAME"] = "test-hostname"
1425 env["HGHOSTNAME"] = "test-hostname"
1426 env['HGIPV6'] = str(int(self._useipv6))
1426 env['HGIPV6'] = str(int(self._useipv6))
1427 # See contrib/catapipe.py for how to use this functionality.
1427 # See contrib/catapipe.py for how to use this functionality.
1428 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1428 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1429 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1429 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1430 # non-test one in as a default, otherwise set to devnull
1430 # non-test one in as a default, otherwise set to devnull
1431 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1431 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1432 'HGCATAPULTSERVERPIPE', os.devnull
1432 'HGCATAPULTSERVERPIPE', os.devnull
1433 )
1433 )
1434
1434
1435 extraextensions = []
1435 extraextensions = []
1436 for opt in self._extraconfigopts:
1436 for opt in self._extraconfigopts:
1437 section, key = opt.split('.', 1)
1437 section, key = opt.split('.', 1)
1438 if section != 'extensions':
1438 if section != 'extensions':
1439 continue
1439 continue
1440 name = key.split('=', 1)[0]
1440 name = key.split('=', 1)[0]
1441 extraextensions.append(name)
1441 extraextensions.append(name)
1442
1442
1443 if extraextensions:
1443 if extraextensions:
1444 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1444 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1445
1445
1446 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1446 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1447 # IP addresses.
1447 # IP addresses.
1448 env['LOCALIP'] = _bytes2sys(self._localip())
1448 env['LOCALIP'] = _bytes2sys(self._localip())
1449
1449
1450 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1450 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1451 # but this is needed for testing python instances like dummyssh,
1451 # but this is needed for testing python instances like dummyssh,
1452 # dummysmtpd.py, and dumbhttp.py.
1452 # dummysmtpd.py, and dumbhttp.py.
1453 if PYTHON3 and WINDOWS:
1453 if PYTHON3 and WINDOWS:
1454 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1454 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1455
1455
1456 # Modified HOME in test environment can confuse Rust tools. So set
1456 # Modified HOME in test environment can confuse Rust tools. So set
1457 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1457 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1458 # present and these variables aren't already defined.
1458 # present and these variables aren't already defined.
1459 cargo_home_path = os.path.expanduser('~/.cargo')
1459 cargo_home_path = os.path.expanduser('~/.cargo')
1460 rustup_home_path = os.path.expanduser('~/.rustup')
1460 rustup_home_path = os.path.expanduser('~/.rustup')
1461
1461
1462 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1462 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1463 env['CARGO_HOME'] = cargo_home_path
1463 env['CARGO_HOME'] = cargo_home_path
1464 if (
1464 if (
1465 os.path.exists(rustup_home_path)
1465 os.path.exists(rustup_home_path)
1466 and b'RUSTUP_HOME' not in osenvironb
1466 and b'RUSTUP_HOME' not in osenvironb
1467 ):
1467 ):
1468 env['RUSTUP_HOME'] = rustup_home_path
1468 env['RUSTUP_HOME'] = rustup_home_path
1469
1469
1470 # Reset some environment variables to well-known values so that
1470 # Reset some environment variables to well-known values so that
1471 # the tests produce repeatable output.
1471 # the tests produce repeatable output.
1472 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1472 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1473 env['TZ'] = 'GMT'
1473 env['TZ'] = 'GMT'
1474 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1474 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1475 env['COLUMNS'] = '80'
1475 env['COLUMNS'] = '80'
1476 env['TERM'] = 'xterm'
1476 env['TERM'] = 'xterm'
1477
1477
1478 dropped = [
1478 dropped = [
1479 'CDPATH',
1479 'CDPATH',
1480 'CHGDEBUG',
1480 'CHGDEBUG',
1481 'EDITOR',
1481 'EDITOR',
1482 'GREP_OPTIONS',
1482 'GREP_OPTIONS',
1483 'HG',
1483 'HG',
1484 'HGMERGE',
1484 'HGMERGE',
1485 'HGPLAIN',
1485 'HGPLAIN',
1486 'HGPLAINEXCEPT',
1486 'HGPLAINEXCEPT',
1487 'HGPROF',
1487 'HGPROF',
1488 'http_proxy',
1488 'http_proxy',
1489 'no_proxy',
1489 'no_proxy',
1490 'NO_PROXY',
1490 'NO_PROXY',
1491 'PAGER',
1491 'PAGER',
1492 'VISUAL',
1492 'VISUAL',
1493 ]
1493 ]
1494
1494
1495 for k in dropped:
1495 for k in dropped:
1496 if k in env:
1496 if k in env:
1497 del env[k]
1497 del env[k]
1498
1498
1499 # unset env related to hooks
1499 # unset env related to hooks
1500 for k in list(env):
1500 for k in list(env):
1501 if k.startswith('HG_'):
1501 if k.startswith('HG_'):
1502 del env[k]
1502 del env[k]
1503
1503
1504 if self._usechg:
1504 if self._usechg:
1505 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1505 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1506 if self._chgdebug:
1506 if self._chgdebug:
1507 env['CHGDEBUG'] = 'true'
1507 env['CHGDEBUG'] = 'true'
1508
1508
1509 return env
1509 return env
1510
1510
1511 def _createhgrc(self, path):
1511 def _createhgrc(self, path):
1512 """Create an hgrc file for this test."""
1512 """Create an hgrc file for this test."""
1513 with open(path, 'wb') as hgrc:
1513 with open(path, 'wb') as hgrc:
1514 hgrc.write(b'[ui]\n')
1514 hgrc.write(b'[ui]\n')
1515 hgrc.write(b'slash = True\n')
1515 hgrc.write(b'slash = True\n')
1516 hgrc.write(b'interactive = False\n')
1516 hgrc.write(b'interactive = False\n')
1517 hgrc.write(b'detailed-exit-code = True\n')
1517 hgrc.write(b'detailed-exit-code = True\n')
1518 hgrc.write(b'merge = internal:merge\n')
1518 hgrc.write(b'merge = internal:merge\n')
1519 hgrc.write(b'mergemarkers = detailed\n')
1519 hgrc.write(b'mergemarkers = detailed\n')
1520 hgrc.write(b'promptecho = True\n')
1520 hgrc.write(b'promptecho = True\n')
1521 hgrc.write(b'timeout.warn=15\n')
1521 hgrc.write(b'timeout.warn=15\n')
1522 hgrc.write(b'[defaults]\n')
1522 hgrc.write(b'[defaults]\n')
1523 hgrc.write(b'[devel]\n')
1523 hgrc.write(b'[devel]\n')
1524 hgrc.write(b'all-warnings = true\n')
1524 hgrc.write(b'all-warnings = true\n')
1525 hgrc.write(b'default-date = 0 0\n')
1525 hgrc.write(b'default-date = 0 0\n')
1526 hgrc.write(b'[largefiles]\n')
1526 hgrc.write(b'[largefiles]\n')
1527 hgrc.write(
1527 hgrc.write(
1528 b'usercache = %s\n'
1528 b'usercache = %s\n'
1529 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1529 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1530 )
1530 )
1531 hgrc.write(b'[lfs]\n')
1531 hgrc.write(b'[lfs]\n')
1532 hgrc.write(
1532 hgrc.write(
1533 b'usercache = %s\n'
1533 b'usercache = %s\n'
1534 % (os.path.join(self._testtmp, b'.cache/lfs'))
1534 % (os.path.join(self._testtmp, b'.cache/lfs'))
1535 )
1535 )
1536 hgrc.write(b'[web]\n')
1536 hgrc.write(b'[web]\n')
1537 hgrc.write(b'address = localhost\n')
1537 hgrc.write(b'address = localhost\n')
1538 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1538 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1539 hgrc.write(b'server-header = testing stub value\n')
1539 hgrc.write(b'server-header = testing stub value\n')
1540
1540
1541 for opt in self._extraconfigopts:
1541 for opt in self._extraconfigopts:
1542 section, key = _sys2bytes(opt).split(b'.', 1)
1542 section, key = _sys2bytes(opt).split(b'.', 1)
1543 assert b'=' in key, (
1543 assert b'=' in key, (
1544 'extra config opt %s must ' 'have an = for assignment' % opt
1544 'extra config opt %s must ' 'have an = for assignment' % opt
1545 )
1545 )
1546 hgrc.write(b'[%s]\n%s\n' % (section, key))
1546 hgrc.write(b'[%s]\n%s\n' % (section, key))
1547
1547
1548 def fail(self, msg):
1548 def fail(self, msg):
1549 # unittest differentiates between errored and failed.
1549 # unittest differentiates between errored and failed.
1550 # Failed is denoted by AssertionError (by default at least).
1550 # Failed is denoted by AssertionError (by default at least).
1551 raise AssertionError(msg)
1551 raise AssertionError(msg)
1552
1552
1553 def _runcommand(self, cmd, env, normalizenewlines=False):
1553 def _runcommand(self, cmd, env, normalizenewlines=False):
1554 """Run command in a sub-process, capturing the output (stdout and
1554 """Run command in a sub-process, capturing the output (stdout and
1555 stderr).
1555 stderr).
1556
1556
1557 Return a tuple (exitcode, output). output is None in debug mode.
1557 Return a tuple (exitcode, output). output is None in debug mode.
1558 """
1558 """
1559 if self._debug:
1559 if self._debug:
1560 proc = subprocess.Popen(
1560 proc = subprocess.Popen(
1561 _bytes2sys(cmd),
1561 _bytes2sys(cmd),
1562 shell=True,
1562 shell=True,
1563 cwd=_bytes2sys(self._testtmp),
1563 cwd=_bytes2sys(self._testtmp),
1564 env=env,
1564 env=env,
1565 )
1565 )
1566 ret = proc.wait()
1566 ret = proc.wait()
1567 return (ret, None)
1567 return (ret, None)
1568
1568
1569 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1569 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1570
1570
1571 def cleanup():
1571 def cleanup():
1572 terminate(proc)
1572 terminate(proc)
1573 ret = proc.wait()
1573 ret = proc.wait()
1574 if ret == 0:
1574 if ret == 0:
1575 ret = signal.SIGTERM << 8
1575 ret = signal.SIGTERM << 8
1576 killdaemons(env['DAEMON_PIDS'])
1576 killdaemons(env['DAEMON_PIDS'])
1577 return ret
1577 return ret
1578
1578
1579 proc.tochild.close()
1579 proc.tochild.close()
1580
1580
1581 try:
1581 try:
1582 output = proc.fromchild.read()
1582 output = proc.fromchild.read()
1583 except KeyboardInterrupt:
1583 except KeyboardInterrupt:
1584 vlog('# Handling keyboard interrupt')
1584 vlog('# Handling keyboard interrupt')
1585 cleanup()
1585 cleanup()
1586 raise
1586 raise
1587
1587
1588 ret = proc.wait()
1588 ret = proc.wait()
1589 if wifexited(ret):
1589 if wifexited(ret):
1590 ret = os.WEXITSTATUS(ret)
1590 ret = os.WEXITSTATUS(ret)
1591
1591
1592 if proc.timeout:
1592 if proc.timeout:
1593 ret = 'timeout'
1593 ret = 'timeout'
1594
1594
1595 if ret:
1595 if ret:
1596 killdaemons(env['DAEMON_PIDS'])
1596 killdaemons(env['DAEMON_PIDS'])
1597
1597
1598 for s, r in self._getreplacements():
1598 for s, r in self._getreplacements():
1599 output = re.sub(s, r, output)
1599 output = re.sub(s, r, output)
1600
1600
1601 if normalizenewlines:
1601 if normalizenewlines:
1602 output = output.replace(b'\r\n', b'\n')
1602 output = output.replace(b'\r\n', b'\n')
1603
1603
1604 return ret, output.splitlines(True)
1604 return ret, output.splitlines(True)
1605
1605
1606
1606
1607 class PythonTest(Test):
1607 class PythonTest(Test):
1608 """A Python-based test."""
1608 """A Python-based test."""
1609
1609
1610 @property
1610 @property
1611 def refpath(self):
1611 def refpath(self):
1612 return os.path.join(self._testdir, b'%s.out' % self.bname)
1612 return os.path.join(self._testdir, b'%s.out' % self.bname)
1613
1613
1614 def _run(self, env):
1614 def _run(self, env):
1615 # Quote the python(3) executable for Windows
1615 # Quote the python(3) executable for Windows
1616 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1616 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1617 vlog("# Running", cmd.decode("utf-8"))
1617 vlog("# Running", cmd.decode("utf-8"))
1618 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1618 result = self._runcommand(cmd, env, normalizenewlines=WINDOWS)
1619 if self._aborted:
1619 if self._aborted:
1620 raise KeyboardInterrupt()
1620 raise KeyboardInterrupt()
1621
1621
1622 return result
1622 return result
1623
1623
1624
1624
1625 # Some glob patterns apply only in some circumstances, so the script
1625 # Some glob patterns apply only in some circumstances, so the script
1626 # might want to remove (glob) annotations that otherwise should be
1626 # might want to remove (glob) annotations that otherwise should be
1627 # retained.
1627 # retained.
1628 checkcodeglobpats = [
1628 checkcodeglobpats = [
1629 # On Windows it looks like \ doesn't require a (glob), but we know
1629 # On Windows it looks like \ doesn't require a (glob), but we know
1630 # better.
1630 # better.
1631 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1631 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1632 re.compile(br'^moving \S+/.*[^)]$'),
1632 re.compile(br'^moving \S+/.*[^)]$'),
1633 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1633 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1634 # Not all platforms have 127.0.0.1 as loopback (though most do),
1634 # Not all platforms have 127.0.0.1 as loopback (though most do),
1635 # so we always glob that too.
1635 # so we always glob that too.
1636 re.compile(br'.*\$LOCALIP.*$'),
1636 re.compile(br'.*\$LOCALIP.*$'),
1637 ]
1637 ]
1638
1638
1639 bchr = chr
1639 bchr = chr
1640 if PYTHON3:
1640 if PYTHON3:
1641 bchr = lambda x: bytes([x])
1641 bchr = lambda x: bytes([x])
1642
1642
1643 WARN_UNDEFINED = 1
1643 WARN_UNDEFINED = 1
1644 WARN_YES = 2
1644 WARN_YES = 2
1645 WARN_NO = 3
1645 WARN_NO = 3
1646
1646
1647 MARK_OPTIONAL = b" (?)\n"
1647 MARK_OPTIONAL = b" (?)\n"
1648
1648
1649
1649
1650 def isoptional(line):
1650 def isoptional(line):
1651 return line.endswith(MARK_OPTIONAL)
1651 return line.endswith(MARK_OPTIONAL)
1652
1652
1653
1653
1654 class TTest(Test):
1654 class TTest(Test):
1655 """A "t test" is a test backed by a .t file."""
1655 """A "t test" is a test backed by a .t file."""
1656
1656
1657 SKIPPED_PREFIX = b'skipped: '
1657 SKIPPED_PREFIX = b'skipped: '
1658 FAILED_PREFIX = b'hghave check failed: '
1658 FAILED_PREFIX = b'hghave check failed: '
1659 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1659 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1660
1660
1661 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1661 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1662 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1662 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1663 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1663 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1664
1664
1665 def __init__(self, path, *args, **kwds):
1665 def __init__(self, path, *args, **kwds):
1666 # accept an extra "case" parameter
1666 # accept an extra "case" parameter
1667 case = kwds.pop('case', [])
1667 case = kwds.pop('case', [])
1668 self._case = case
1668 self._case = case
1669 self._allcases = {x for y in parsettestcases(path) for x in y}
1669 self._allcases = {x for y in parsettestcases(path) for x in y}
1670 super(TTest, self).__init__(path, *args, **kwds)
1670 super(TTest, self).__init__(path, *args, **kwds)
1671 if case:
1671 if case:
1672 casepath = b'#'.join(case)
1672 casepath = b'#'.join(case)
1673 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1673 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1674 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1674 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1675 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1675 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1676 self._have = {}
1676 self._have = {}
1677
1677
1678 @property
1678 @property
1679 def refpath(self):
1679 def refpath(self):
1680 return os.path.join(self._testdir, self.bname)
1680 return os.path.join(self._testdir, self.bname)
1681
1681
1682 def _run(self, env):
1682 def _run(self, env):
1683 with open(self.path, 'rb') as f:
1683 with open(self.path, 'rb') as f:
1684 lines = f.readlines()
1684 lines = f.readlines()
1685
1685
1686 # .t file is both reference output and the test input, keep reference
1686 # .t file is both reference output and the test input, keep reference
1687 # output updated with the the test input. This avoids some race
1687 # output updated with the the test input. This avoids some race
1688 # conditions where the reference output does not match the actual test.
1688 # conditions where the reference output does not match the actual test.
1689 if self._refout is not None:
1689 if self._refout is not None:
1690 self._refout = lines
1690 self._refout = lines
1691
1691
1692 salt, script, after, expected = self._parsetest(lines)
1692 salt, script, after, expected = self._parsetest(lines)
1693
1693
1694 # Write out the generated script.
1694 # Write out the generated script.
1695 fname = b'%s.sh' % self._testtmp
1695 fname = b'%s.sh' % self._testtmp
1696 with open(fname, 'wb') as f:
1696 with open(fname, 'wb') as f:
1697 for l in script:
1697 for l in script:
1698 f.write(l)
1698 f.write(l)
1699
1699
1700 cmd = b'%s "%s"' % (self._shell, fname)
1700 cmd = b'%s "%s"' % (self._shell, fname)
1701 vlog("# Running", cmd.decode("utf-8"))
1701 vlog("# Running", cmd.decode("utf-8"))
1702
1702
1703 exitcode, output = self._runcommand(cmd, env)
1703 exitcode, output = self._runcommand(cmd, env)
1704
1704
1705 if self._aborted:
1705 if self._aborted:
1706 raise KeyboardInterrupt()
1706 raise KeyboardInterrupt()
1707
1707
1708 # Do not merge output if skipped. Return hghave message instead.
1708 # Do not merge output if skipped. Return hghave message instead.
1709 # Similarly, with --debug, output is None.
1709 # Similarly, with --debug, output is None.
1710 if exitcode == self.SKIPPED_STATUS or output is None:
1710 if exitcode == self.SKIPPED_STATUS or output is None:
1711 return exitcode, output
1711 return exitcode, output
1712
1712
1713 return self._processoutput(exitcode, output, salt, after, expected)
1713 return self._processoutput(exitcode, output, salt, after, expected)
1714
1714
1715 def _hghave(self, reqs):
1715 def _hghave(self, reqs):
1716 allreqs = b' '.join(reqs)
1716 allreqs = b' '.join(reqs)
1717
1717
1718 self._detectslow(reqs)
1718 self._detectslow(reqs)
1719
1719
1720 if allreqs in self._have:
1720 if allreqs in self._have:
1721 return self._have.get(allreqs)
1721 return self._have.get(allreqs)
1722
1722
1723 # TODO do something smarter when all other uses of hghave are gone.
1723 # TODO do something smarter when all other uses of hghave are gone.
1724 runtestdir = osenvironb[b'RUNTESTDIR']
1724 runtestdir = osenvironb[b'RUNTESTDIR']
1725 tdir = runtestdir.replace(b'\\', b'/')
1725 tdir = runtestdir.replace(b'\\', b'/')
1726 proc = Popen4(
1726 proc = Popen4(
1727 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1727 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1728 self._testtmp,
1728 self._testtmp,
1729 0,
1729 0,
1730 self._getenv(),
1730 self._getenv(),
1731 )
1731 )
1732 stdout, stderr = proc.communicate()
1732 stdout, stderr = proc.communicate()
1733 ret = proc.wait()
1733 ret = proc.wait()
1734 if wifexited(ret):
1734 if wifexited(ret):
1735 ret = os.WEXITSTATUS(ret)
1735 ret = os.WEXITSTATUS(ret)
1736 if ret == 2:
1736 if ret == 2:
1737 print(stdout.decode('utf-8'))
1737 print(stdout.decode('utf-8'))
1738 sys.exit(1)
1738 sys.exit(1)
1739
1739
1740 if ret != 0:
1740 if ret != 0:
1741 self._have[allreqs] = (False, stdout)
1741 self._have[allreqs] = (False, stdout)
1742 return False, stdout
1742 return False, stdout
1743
1743
1744 self._have[allreqs] = (True, None)
1744 self._have[allreqs] = (True, None)
1745 return True, None
1745 return True, None
1746
1746
1747 def _detectslow(self, reqs):
1747 def _detectslow(self, reqs):
1748 """update the timeout of slow test when appropriate"""
1748 """update the timeout of slow test when appropriate"""
1749 if b'slow' in reqs:
1749 if b'slow' in reqs:
1750 self._timeout = self._slowtimeout
1750 self._timeout = self._slowtimeout
1751
1751
1752 def _iftest(self, args):
1752 def _iftest(self, args):
1753 # implements "#if"
1753 # implements "#if"
1754 reqs = []
1754 reqs = []
1755 for arg in args:
1755 for arg in args:
1756 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1756 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1757 if arg[3:] in self._case:
1757 if arg[3:] in self._case:
1758 return False
1758 return False
1759 elif arg in self._allcases:
1759 elif arg in self._allcases:
1760 if arg not in self._case:
1760 if arg not in self._case:
1761 return False
1761 return False
1762 else:
1762 else:
1763 reqs.append(arg)
1763 reqs.append(arg)
1764 self._detectslow(reqs)
1764 self._detectslow(reqs)
1765 return self._hghave(reqs)[0]
1765 return self._hghave(reqs)[0]
1766
1766
1767 def _parsetest(self, lines):
1767 def _parsetest(self, lines):
1768 # We generate a shell script which outputs unique markers to line
1768 # We generate a shell script which outputs unique markers to line
1769 # up script results with our source. These markers include input
1769 # up script results with our source. These markers include input
1770 # line number and the last return code.
1770 # line number and the last return code.
1771 salt = b"SALT%d" % time.time()
1771 salt = b"SALT%d" % time.time()
1772
1772
1773 def addsalt(line, inpython):
1773 def addsalt(line, inpython):
1774 if inpython:
1774 if inpython:
1775 script.append(b'%s %d 0\n' % (salt, line))
1775 script.append(b'%s %d 0\n' % (salt, line))
1776 else:
1776 else:
1777 script.append(b'echo %s %d $?\n' % (salt, line))
1777 script.append(b'echo %s %d $?\n' % (salt, line))
1778
1778
1779 activetrace = []
1779 activetrace = []
1780 session = str(uuid.uuid4())
1780 session = str(uuid.uuid4())
1781 if PYTHON3:
1781 if PYTHON3:
1782 session = session.encode('ascii')
1782 session = session.encode('ascii')
1783 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1783 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1784 'HGCATAPULTSERVERPIPE'
1784 'HGCATAPULTSERVERPIPE'
1785 )
1785 )
1786
1786
1787 def toggletrace(cmd=None):
1787 def toggletrace(cmd=None):
1788 if not hgcatapult or hgcatapult == os.devnull:
1788 if not hgcatapult or hgcatapult == os.devnull:
1789 return
1789 return
1790
1790
1791 if activetrace:
1791 if activetrace:
1792 script.append(
1792 script.append(
1793 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1793 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1794 % (session, activetrace[0])
1794 % (session, activetrace[0])
1795 )
1795 )
1796 if cmd is None:
1796 if cmd is None:
1797 return
1797 return
1798
1798
1799 if isinstance(cmd, str):
1799 if isinstance(cmd, str):
1800 quoted = shellquote(cmd.strip())
1800 quoted = shellquote(cmd.strip())
1801 else:
1801 else:
1802 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1802 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1803 quoted = quoted.replace(b'\\', b'\\\\')
1803 quoted = quoted.replace(b'\\', b'\\\\')
1804 script.append(
1804 script.append(
1805 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1805 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1806 % (session, quoted)
1806 % (session, quoted)
1807 )
1807 )
1808 activetrace[0:] = [quoted]
1808 activetrace[0:] = [quoted]
1809
1809
1810 script = []
1810 script = []
1811
1811
1812 # After we run the shell script, we re-unify the script output
1812 # After we run the shell script, we re-unify the script output
1813 # with non-active parts of the source, with synchronization by our
1813 # with non-active parts of the source, with synchronization by our
1814 # SALT line number markers. The after table contains the non-active
1814 # SALT line number markers. The after table contains the non-active
1815 # components, ordered by line number.
1815 # components, ordered by line number.
1816 after = {}
1816 after = {}
1817
1817
1818 # Expected shell script output.
1818 # Expected shell script output.
1819 expected = {}
1819 expected = {}
1820
1820
1821 pos = prepos = -1
1821 pos = prepos = -1
1822
1822
1823 # True or False when in a true or false conditional section
1823 # True or False when in a true or false conditional section
1824 skipping = None
1824 skipping = None
1825
1825
1826 # We keep track of whether or not we're in a Python block so we
1826 # We keep track of whether or not we're in a Python block so we
1827 # can generate the surrounding doctest magic.
1827 # can generate the surrounding doctest magic.
1828 inpython = False
1828 inpython = False
1829
1829
1830 if self._debug:
1830 if self._debug:
1831 script.append(b'set -x\n')
1831 script.append(b'set -x\n')
1832 if self._hgcommand != b'hg':
1832 if self._hgcommand != b'hg':
1833 script.append(b'alias hg="%s"\n' % self._hgcommand)
1833 script.append(b'alias hg="%s"\n' % self._hgcommand)
1834 if os.getenv('MSYSTEM'):
1834 if os.getenv('MSYSTEM'):
1835 script.append(b'alias pwd="pwd -W"\n')
1835 script.append(b'alias pwd="pwd -W"\n')
1836
1836
1837 if hgcatapult and hgcatapult != os.devnull:
1837 if hgcatapult and hgcatapult != os.devnull:
1838 if PYTHON3:
1838 if PYTHON3:
1839 hgcatapult = hgcatapult.encode('utf8')
1839 hgcatapult = hgcatapult.encode('utf8')
1840 cataname = self.name.encode('utf8')
1840 cataname = self.name.encode('utf8')
1841 else:
1841 else:
1842 cataname = self.name
1842 cataname = self.name
1843
1843
1844 # Kludge: use a while loop to keep the pipe from getting
1844 # Kludge: use a while loop to keep the pipe from getting
1845 # closed by our echo commands. The still-running file gets
1845 # closed by our echo commands. The still-running file gets
1846 # reaped at the end of the script, which causes the while
1846 # reaped at the end of the script, which causes the while
1847 # loop to exit and closes the pipe. Sigh.
1847 # loop to exit and closes the pipe. Sigh.
1848 script.append(
1848 script.append(
1849 b'rtendtracing() {\n'
1849 b'rtendtracing() {\n'
1850 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1850 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1851 b' rm -f "$TESTTMP/.still-running"\n'
1851 b' rm -f "$TESTTMP/.still-running"\n'
1852 b'}\n'
1852 b'}\n'
1853 b'trap "rtendtracing" 0\n'
1853 b'trap "rtendtracing" 0\n'
1854 b'touch "$TESTTMP/.still-running"\n'
1854 b'touch "$TESTTMP/.still-running"\n'
1855 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1855 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1856 b'> %(catapult)s &\n'
1856 b'> %(catapult)s &\n'
1857 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1857 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1858 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1858 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1859 % {
1859 % {
1860 b'name': cataname,
1860 b'name': cataname,
1861 b'session': session,
1861 b'session': session,
1862 b'catapult': hgcatapult,
1862 b'catapult': hgcatapult,
1863 }
1863 }
1864 )
1864 )
1865
1865
1866 if self._case:
1866 if self._case:
1867 casestr = b'#'.join(self._case)
1867 casestr = b'#'.join(self._case)
1868 if isinstance(casestr, str):
1868 if isinstance(casestr, str):
1869 quoted = shellquote(casestr)
1869 quoted = shellquote(casestr)
1870 else:
1870 else:
1871 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1871 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1872 script.append(b'TESTCASE=%s\n' % quoted)
1872 script.append(b'TESTCASE=%s\n' % quoted)
1873 script.append(b'export TESTCASE\n')
1873 script.append(b'export TESTCASE\n')
1874
1874
1875 n = 0
1875 n = 0
1876 for n, l in enumerate(lines):
1876 for n, l in enumerate(lines):
1877 if not l.endswith(b'\n'):
1877 if not l.endswith(b'\n'):
1878 l += b'\n'
1878 l += b'\n'
1879 if l.startswith(b'#require'):
1879 if l.startswith(b'#require'):
1880 lsplit = l.split()
1880 lsplit = l.split()
1881 if len(lsplit) < 2 or lsplit[0] != b'#require':
1881 if len(lsplit) < 2 or lsplit[0] != b'#require':
1882 after.setdefault(pos, []).append(
1882 after.setdefault(pos, []).append(
1883 b' !!! invalid #require\n'
1883 b' !!! invalid #require\n'
1884 )
1884 )
1885 if not skipping:
1885 if not skipping:
1886 haveresult, message = self._hghave(lsplit[1:])
1886 haveresult, message = self._hghave(lsplit[1:])
1887 if not haveresult:
1887 if not haveresult:
1888 script = [b'echo "%s"\nexit 80\n' % message]
1888 script = [b'echo "%s"\nexit 80\n' % message]
1889 break
1889 break
1890 after.setdefault(pos, []).append(l)
1890 after.setdefault(pos, []).append(l)
1891 elif l.startswith(b'#if'):
1891 elif l.startswith(b'#if'):
1892 lsplit = l.split()
1892 lsplit = l.split()
1893 if len(lsplit) < 2 or lsplit[0] != b'#if':
1893 if len(lsplit) < 2 or lsplit[0] != b'#if':
1894 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1894 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1895 if skipping is not None:
1895 if skipping is not None:
1896 after.setdefault(pos, []).append(b' !!! nested #if\n')
1896 after.setdefault(pos, []).append(b' !!! nested #if\n')
1897 skipping = not self._iftest(lsplit[1:])
1897 skipping = not self._iftest(lsplit[1:])
1898 after.setdefault(pos, []).append(l)
1898 after.setdefault(pos, []).append(l)
1899 elif l.startswith(b'#else'):
1899 elif l.startswith(b'#else'):
1900 if skipping is None:
1900 if skipping is None:
1901 after.setdefault(pos, []).append(b' !!! missing #if\n')
1901 after.setdefault(pos, []).append(b' !!! missing #if\n')
1902 skipping = not skipping
1902 skipping = not skipping
1903 after.setdefault(pos, []).append(l)
1903 after.setdefault(pos, []).append(l)
1904 elif l.startswith(b'#endif'):
1904 elif l.startswith(b'#endif'):
1905 if skipping is None:
1905 if skipping is None:
1906 after.setdefault(pos, []).append(b' !!! missing #if\n')
1906 after.setdefault(pos, []).append(b' !!! missing #if\n')
1907 skipping = None
1907 skipping = None
1908 after.setdefault(pos, []).append(l)
1908 after.setdefault(pos, []).append(l)
1909 elif skipping:
1909 elif skipping:
1910 after.setdefault(pos, []).append(l)
1910 after.setdefault(pos, []).append(l)
1911 elif l.startswith(b' >>> '): # python inlines
1911 elif l.startswith(b' >>> '): # python inlines
1912 after.setdefault(pos, []).append(l)
1912 after.setdefault(pos, []).append(l)
1913 prepos = pos
1913 prepos = pos
1914 pos = n
1914 pos = n
1915 if not inpython:
1915 if not inpython:
1916 # We've just entered a Python block. Add the header.
1916 # We've just entered a Python block. Add the header.
1917 inpython = True
1917 inpython = True
1918 addsalt(prepos, False) # Make sure we report the exit code.
1918 addsalt(prepos, False) # Make sure we report the exit code.
1919 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1919 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1920 addsalt(n, True)
1920 addsalt(n, True)
1921 script.append(l[2:])
1921 script.append(l[2:])
1922 elif l.startswith(b' ... '): # python inlines
1922 elif l.startswith(b' ... '): # python inlines
1923 after.setdefault(prepos, []).append(l)
1923 after.setdefault(prepos, []).append(l)
1924 script.append(l[2:])
1924 script.append(l[2:])
1925 elif l.startswith(b' $ '): # commands
1925 elif l.startswith(b' $ '): # commands
1926 if inpython:
1926 if inpython:
1927 script.append(b'EOF\n')
1927 script.append(b'EOF\n')
1928 inpython = False
1928 inpython = False
1929 after.setdefault(pos, []).append(l)
1929 after.setdefault(pos, []).append(l)
1930 prepos = pos
1930 prepos = pos
1931 pos = n
1931 pos = n
1932 addsalt(n, False)
1932 addsalt(n, False)
1933 rawcmd = l[4:]
1933 rawcmd = l[4:]
1934 cmd = rawcmd.split()
1934 cmd = rawcmd.split()
1935 toggletrace(rawcmd)
1935 toggletrace(rawcmd)
1936 if len(cmd) == 2 and cmd[0] == b'cd':
1936 if len(cmd) == 2 and cmd[0] == b'cd':
1937 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1937 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1938 script.append(rawcmd)
1938 script.append(rawcmd)
1939 elif l.startswith(b' > '): # continuations
1939 elif l.startswith(b' > '): # continuations
1940 after.setdefault(prepos, []).append(l)
1940 after.setdefault(prepos, []).append(l)
1941 script.append(l[4:])
1941 script.append(l[4:])
1942 elif l.startswith(b' '): # results
1942 elif l.startswith(b' '): # results
1943 # Queue up a list of expected results.
1943 # Queue up a list of expected results.
1944 expected.setdefault(pos, []).append(l[2:])
1944 expected.setdefault(pos, []).append(l[2:])
1945 else:
1945 else:
1946 if inpython:
1946 if inpython:
1947 script.append(b'EOF\n')
1947 script.append(b'EOF\n')
1948 inpython = False
1948 inpython = False
1949 # Non-command/result. Queue up for merged output.
1949 # Non-command/result. Queue up for merged output.
1950 after.setdefault(pos, []).append(l)
1950 after.setdefault(pos, []).append(l)
1951
1951
1952 if inpython:
1952 if inpython:
1953 script.append(b'EOF\n')
1953 script.append(b'EOF\n')
1954 if skipping is not None:
1954 if skipping is not None:
1955 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1955 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1956 addsalt(n + 1, False)
1956 addsalt(n + 1, False)
1957 # Need to end any current per-command trace
1957 # Need to end any current per-command trace
1958 if activetrace:
1958 if activetrace:
1959 toggletrace()
1959 toggletrace()
1960 return salt, script, after, expected
1960 return salt, script, after, expected
1961
1961
1962 def _processoutput(self, exitcode, output, salt, after, expected):
1962 def _processoutput(self, exitcode, output, salt, after, expected):
1963 # Merge the script output back into a unified test.
1963 # Merge the script output back into a unified test.
1964 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1964 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1965 if exitcode != 0:
1965 if exitcode != 0:
1966 warnonly = WARN_NO
1966 warnonly = WARN_NO
1967
1967
1968 pos = -1
1968 pos = -1
1969 postout = []
1969 postout = []
1970 for out_rawline in output:
1970 for out_rawline in output:
1971 out_line, cmd_line = out_rawline, None
1971 out_line, cmd_line = out_rawline, None
1972 if salt in out_rawline:
1972 if salt in out_rawline:
1973 out_line, cmd_line = out_rawline.split(salt, 1)
1973 out_line, cmd_line = out_rawline.split(salt, 1)
1974
1974
1975 pos, postout, warnonly = self._process_out_line(
1975 pos, postout, warnonly = self._process_out_line(
1976 out_line, pos, postout, expected, warnonly
1976 out_line, pos, postout, expected, warnonly
1977 )
1977 )
1978 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1978 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1979
1979
1980 if pos in after:
1980 if pos in after:
1981 postout += after.pop(pos)
1981 postout += after.pop(pos)
1982
1982
1983 if warnonly == WARN_YES:
1983 if warnonly == WARN_YES:
1984 exitcode = False # Set exitcode to warned.
1984 exitcode = False # Set exitcode to warned.
1985
1985
1986 return exitcode, postout
1986 return exitcode, postout
1987
1987
1988 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1988 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1989 while out_line:
1989 while out_line:
1990 if not out_line.endswith(b'\n'):
1990 if not out_line.endswith(b'\n'):
1991 out_line += b' (no-eol)\n'
1991 out_line += b' (no-eol)\n'
1992
1992
1993 # Find the expected output at the current position.
1993 # Find the expected output at the current position.
1994 els = [None]
1994 els = [None]
1995 if expected.get(pos, None):
1995 if expected.get(pos, None):
1996 els = expected[pos]
1996 els = expected[pos]
1997
1997
1998 optional = []
1998 optional = []
1999 for i, el in enumerate(els):
1999 for i, el in enumerate(els):
2000 r = False
2000 r = False
2001 if el:
2001 if el:
2002 r, exact = self.linematch(el, out_line)
2002 r, exact = self.linematch(el, out_line)
2003 if isinstance(r, str):
2003 if isinstance(r, str):
2004 if r == '-glob':
2004 if r == '-glob':
2005 out_line = ''.join(el.rsplit(' (glob)', 1))
2005 out_line = ''.join(el.rsplit(' (glob)', 1))
2006 r = '' # Warn only this line.
2006 r = '' # Warn only this line.
2007 elif r == "retry":
2007 elif r == "retry":
2008 postout.append(b' ' + el)
2008 postout.append(b' ' + el)
2009 else:
2009 else:
2010 log('\ninfo, unknown linematch result: %r\n' % r)
2010 log('\ninfo, unknown linematch result: %r\n' % r)
2011 r = False
2011 r = False
2012 if r:
2012 if r:
2013 els.pop(i)
2013 els.pop(i)
2014 break
2014 break
2015 if el:
2015 if el:
2016 if isoptional(el):
2016 if isoptional(el):
2017 optional.append(i)
2017 optional.append(i)
2018 else:
2018 else:
2019 m = optline.match(el)
2019 m = optline.match(el)
2020 if m:
2020 if m:
2021 conditions = [c for c in m.group(2).split(b' ')]
2021 conditions = [c for c in m.group(2).split(b' ')]
2022
2022
2023 if not self._iftest(conditions):
2023 if not self._iftest(conditions):
2024 optional.append(i)
2024 optional.append(i)
2025 if exact:
2025 if exact:
2026 # Don't allow line to be matches against a later
2026 # Don't allow line to be matches against a later
2027 # line in the output
2027 # line in the output
2028 els.pop(i)
2028 els.pop(i)
2029 break
2029 break
2030
2030
2031 if r:
2031 if r:
2032 if r == "retry":
2032 if r == "retry":
2033 continue
2033 continue
2034 # clean up any optional leftovers
2034 # clean up any optional leftovers
2035 for i in optional:
2035 for i in optional:
2036 postout.append(b' ' + els[i])
2036 postout.append(b' ' + els[i])
2037 for i in reversed(optional):
2037 for i in reversed(optional):
2038 del els[i]
2038 del els[i]
2039 postout.append(b' ' + el)
2039 postout.append(b' ' + el)
2040 else:
2040 else:
2041 if self.NEEDESCAPE(out_line):
2041 if self.NEEDESCAPE(out_line):
2042 out_line = TTest._stringescape(
2042 out_line = TTest._stringescape(
2043 b'%s (esc)\n' % out_line.rstrip(b'\n')
2043 b'%s (esc)\n' % out_line.rstrip(b'\n')
2044 )
2044 )
2045 postout.append(b' ' + out_line) # Let diff deal with it.
2045 postout.append(b' ' + out_line) # Let diff deal with it.
2046 if r != '': # If line failed.
2046 if r != '': # If line failed.
2047 warnonly = WARN_NO
2047 warnonly = WARN_NO
2048 elif warnonly == WARN_UNDEFINED:
2048 elif warnonly == WARN_UNDEFINED:
2049 warnonly = WARN_YES
2049 warnonly = WARN_YES
2050 break
2050 break
2051 else:
2051 else:
2052 # clean up any optional leftovers
2052 # clean up any optional leftovers
2053 while expected.get(pos, None):
2053 while expected.get(pos, None):
2054 el = expected[pos].pop(0)
2054 el = expected[pos].pop(0)
2055 if el:
2055 if el:
2056 if not isoptional(el):
2056 if not isoptional(el):
2057 m = optline.match(el)
2057 m = optline.match(el)
2058 if m:
2058 if m:
2059 conditions = [c for c in m.group(2).split(b' ')]
2059 conditions = [c for c in m.group(2).split(b' ')]
2060
2060
2061 if self._iftest(conditions):
2061 if self._iftest(conditions):
2062 # Don't append as optional line
2062 # Don't append as optional line
2063 continue
2063 continue
2064 else:
2064 else:
2065 continue
2065 continue
2066 postout.append(b' ' + el)
2066 postout.append(b' ' + el)
2067 return pos, postout, warnonly
2067 return pos, postout, warnonly
2068
2068
2069 def _process_cmd_line(self, cmd_line, pos, postout, after):
2069 def _process_cmd_line(self, cmd_line, pos, postout, after):
2070 """process a "command" part of a line from unified test output"""
2070 """process a "command" part of a line from unified test output"""
2071 if cmd_line:
2071 if cmd_line:
2072 # Add on last return code.
2072 # Add on last return code.
2073 ret = int(cmd_line.split()[1])
2073 ret = int(cmd_line.split()[1])
2074 if ret != 0:
2074 if ret != 0:
2075 postout.append(b' [%d]\n' % ret)
2075 postout.append(b' [%d]\n' % ret)
2076 if pos in after:
2076 if pos in after:
2077 # Merge in non-active test bits.
2077 # Merge in non-active test bits.
2078 postout += after.pop(pos)
2078 postout += after.pop(pos)
2079 pos = int(cmd_line.split()[0])
2079 pos = int(cmd_line.split()[0])
2080 return pos, postout
2080 return pos, postout
2081
2081
2082 @staticmethod
2082 @staticmethod
2083 def rematch(el, l):
2083 def rematch(el, l):
2084 try:
2084 try:
2085 # parse any flags at the beginning of the regex. Only 'i' is
2085 # parse any flags at the beginning of the regex. Only 'i' is
2086 # supported right now, but this should be easy to extend.
2086 # supported right now, but this should be easy to extend.
2087 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2087 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2088 flags = flags or b''
2088 flags = flags or b''
2089 el = flags + b'(?:' + el + b')'
2089 el = flags + b'(?:' + el + b')'
2090 # use \Z to ensure that the regex matches to the end of the string
2090 # use \Z to ensure that the regex matches to the end of the string
2091 if WINDOWS:
2091 if WINDOWS:
2092 return re.match(el + br'\r?\n\Z', l)
2092 return re.match(el + br'\r?\n\Z', l)
2093 return re.match(el + br'\n\Z', l)
2093 return re.match(el + br'\n\Z', l)
2094 except re.error:
2094 except re.error:
2095 # el is an invalid regex
2095 # el is an invalid regex
2096 return False
2096 return False
2097
2097
2098 @staticmethod
2098 @staticmethod
2099 def globmatch(el, l):
2099 def globmatch(el, l):
2100 # The only supported special characters are * and ? plus / which also
2100 # The only supported special characters are * and ? plus / which also
2101 # matches \ on windows. Escaping of these characters is supported.
2101 # matches \ on windows. Escaping of these characters is supported.
2102 if el + b'\n' == l:
2102 if el + b'\n' == l:
2103 if os.altsep:
2103 if os.altsep:
2104 # matching on "/" is not needed for this line
2104 # matching on "/" is not needed for this line
2105 for pat in checkcodeglobpats:
2105 for pat in checkcodeglobpats:
2106 if pat.match(el):
2106 if pat.match(el):
2107 return True
2107 return True
2108 return b'-glob'
2108 return b'-glob'
2109 return True
2109 return True
2110 el = el.replace(b'$LOCALIP', b'*')
2110 el = el.replace(b'$LOCALIP', b'*')
2111 i, n = 0, len(el)
2111 i, n = 0, len(el)
2112 res = b''
2112 res = b''
2113 while i < n:
2113 while i < n:
2114 c = el[i : i + 1]
2114 c = el[i : i + 1]
2115 i += 1
2115 i += 1
2116 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2116 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2117 res += el[i - 1 : i + 1]
2117 res += el[i - 1 : i + 1]
2118 i += 1
2118 i += 1
2119 elif c == b'*':
2119 elif c == b'*':
2120 res += b'.*'
2120 res += b'.*'
2121 elif c == b'?':
2121 elif c == b'?':
2122 res += b'.'
2122 res += b'.'
2123 elif c == b'/' and os.altsep:
2123 elif c == b'/' and os.altsep:
2124 res += b'[/\\\\]'
2124 res += b'[/\\\\]'
2125 else:
2125 else:
2126 res += re.escape(c)
2126 res += re.escape(c)
2127 return TTest.rematch(res, l)
2127 return TTest.rematch(res, l)
2128
2128
2129 def linematch(self, el, l):
2129 def linematch(self, el, l):
2130 if el == l: # perfect match (fast)
2130 if el == l: # perfect match (fast)
2131 return True, True
2131 return True, True
2132 retry = False
2132 retry = False
2133 if isoptional(el):
2133 if isoptional(el):
2134 retry = "retry"
2134 retry = "retry"
2135 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2135 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2136 else:
2136 else:
2137 m = optline.match(el)
2137 m = optline.match(el)
2138 if m:
2138 if m:
2139 conditions = [c for c in m.group(2).split(b' ')]
2139 conditions = [c for c in m.group(2).split(b' ')]
2140
2140
2141 el = m.group(1) + b"\n"
2141 el = m.group(1) + b"\n"
2142 if not self._iftest(conditions):
2142 if not self._iftest(conditions):
2143 # listed feature missing, should not match
2143 # listed feature missing, should not match
2144 return "retry", False
2144 return "retry", False
2145
2145
2146 if el.endswith(b" (esc)\n"):
2146 if el.endswith(b" (esc)\n"):
2147 if PYTHON3:
2147 if PYTHON3:
2148 el = el[:-7].decode('unicode_escape') + '\n'
2148 el = el[:-7].decode('unicode_escape') + '\n'
2149 el = el.encode('latin-1')
2149 el = el.encode('latin-1')
2150 else:
2150 else:
2151 el = el[:-7].decode('string-escape') + '\n'
2151 el = el[:-7].decode('string-escape') + '\n'
2152 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2152 if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
2153 return True, True
2153 return True, True
2154 if el.endswith(b" (re)\n"):
2154 if el.endswith(b" (re)\n"):
2155 return (TTest.rematch(el[:-6], l) or retry), False
2155 return (TTest.rematch(el[:-6], l) or retry), False
2156 if el.endswith(b" (glob)\n"):
2156 if el.endswith(b" (glob)\n"):
2157 # ignore '(glob)' added to l by 'replacements'
2157 # ignore '(glob)' added to l by 'replacements'
2158 if l.endswith(b" (glob)\n"):
2158 if l.endswith(b" (glob)\n"):
2159 l = l[:-8] + b"\n"
2159 l = l[:-8] + b"\n"
2160 return (TTest.globmatch(el[:-8], l) or retry), False
2160 return (TTest.globmatch(el[:-8], l) or retry), False
2161 if os.altsep:
2161 if os.altsep:
2162 _l = l.replace(b'\\', b'/')
2162 _l = l.replace(b'\\', b'/')
2163 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2163 if el == _l or WINDOWS and el[:-1] + b'\r\n' == _l:
2164 return True, True
2164 return True, True
2165 return retry, True
2165 return retry, True
2166
2166
2167 @staticmethod
2167 @staticmethod
2168 def parsehghaveoutput(lines):
2168 def parsehghaveoutput(lines):
2169 """Parse hghave log lines.
2169 """Parse hghave log lines.
2170
2170
2171 Return tuple of lists (missing, failed):
2171 Return tuple of lists (missing, failed):
2172 * the missing/unknown features
2172 * the missing/unknown features
2173 * the features for which existence check failed"""
2173 * the features for which existence check failed"""
2174 missing = []
2174 missing = []
2175 failed = []
2175 failed = []
2176 for line in lines:
2176 for line in lines:
2177 if line.startswith(TTest.SKIPPED_PREFIX):
2177 if line.startswith(TTest.SKIPPED_PREFIX):
2178 line = line.splitlines()[0]
2178 line = line.splitlines()[0]
2179 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2179 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2180 elif line.startswith(TTest.FAILED_PREFIX):
2180 elif line.startswith(TTest.FAILED_PREFIX):
2181 line = line.splitlines()[0]
2181 line = line.splitlines()[0]
2182 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2182 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2183
2183
2184 return missing, failed
2184 return missing, failed
2185
2185
2186 @staticmethod
2186 @staticmethod
2187 def _escapef(m):
2187 def _escapef(m):
2188 return TTest.ESCAPEMAP[m.group(0)]
2188 return TTest.ESCAPEMAP[m.group(0)]
2189
2189
2190 @staticmethod
2190 @staticmethod
2191 def _stringescape(s):
2191 def _stringescape(s):
2192 return TTest.ESCAPESUB(TTest._escapef, s)
2192 return TTest.ESCAPESUB(TTest._escapef, s)
2193
2193
2194
2194
2195 iolock = threading.RLock()
2195 iolock = threading.RLock()
2196 firstlock = threading.RLock()
2196 firstlock = threading.RLock()
2197 firsterror = False
2197 firsterror = False
2198
2198
2199
2199
2200 class TestResult(unittest._TextTestResult):
2200 class TestResult(unittest._TextTestResult):
2201 """Holds results when executing via unittest."""
2201 """Holds results when executing via unittest."""
2202
2202
2203 # Don't worry too much about accessing the non-public _TextTestResult.
2203 # Don't worry too much about accessing the non-public _TextTestResult.
2204 # It is relatively common in Python testing tools.
2204 # It is relatively common in Python testing tools.
2205 def __init__(self, options, *args, **kwargs):
2205 def __init__(self, options, *args, **kwargs):
2206 super(TestResult, self).__init__(*args, **kwargs)
2206 super(TestResult, self).__init__(*args, **kwargs)
2207
2207
2208 self._options = options
2208 self._options = options
2209
2209
2210 # unittest.TestResult didn't have skipped until 2.7. We need to
2210 # unittest.TestResult didn't have skipped until 2.7. We need to
2211 # polyfill it.
2211 # polyfill it.
2212 self.skipped = []
2212 self.skipped = []
2213
2213
2214 # We have a custom "ignored" result that isn't present in any Python
2214 # We have a custom "ignored" result that isn't present in any Python
2215 # unittest implementation. It is very similar to skipped. It may make
2215 # unittest implementation. It is very similar to skipped. It may make
2216 # sense to map it into skip some day.
2216 # sense to map it into skip some day.
2217 self.ignored = []
2217 self.ignored = []
2218
2218
2219 self.times = []
2219 self.times = []
2220 self._firststarttime = None
2220 self._firststarttime = None
2221 # Data stored for the benefit of generating xunit reports.
2221 # Data stored for the benefit of generating xunit reports.
2222 self.successes = []
2222 self.successes = []
2223 self.faildata = {}
2223 self.faildata = {}
2224
2224
2225 if options.color == 'auto':
2225 if options.color == 'auto':
2226 isatty = self.stream.isatty()
2226 isatty = self.stream.isatty()
2227 # For some reason, redirecting stdout on Windows disables the ANSI
2227 # For some reason, redirecting stdout on Windows disables the ANSI
2228 # color processing of stderr, which is what is used to print the
2228 # color processing of stderr, which is what is used to print the
2229 # output. Therefore, both must be tty on Windows to enable color.
2229 # output. Therefore, both must be tty on Windows to enable color.
2230 if WINDOWS:
2230 if WINDOWS:
2231 isatty = isatty and sys.stdout.isatty()
2231 isatty = isatty and sys.stdout.isatty()
2232 self.color = pygmentspresent and isatty
2232 self.color = pygmentspresent and isatty
2233 elif options.color == 'never':
2233 elif options.color == 'never':
2234 self.color = False
2234 self.color = False
2235 else: # 'always', for testing purposes
2235 else: # 'always', for testing purposes
2236 self.color = pygmentspresent
2236 self.color = pygmentspresent
2237
2237
2238 def onStart(self, test):
2238 def onStart(self, test):
2239 """Can be overriden by custom TestResult"""
2239 """Can be overriden by custom TestResult"""
2240
2240
2241 def onEnd(self):
2241 def onEnd(self):
2242 """Can be overriden by custom TestResult"""
2242 """Can be overriden by custom TestResult"""
2243
2243
2244 def addFailure(self, test, reason):
2244 def addFailure(self, test, reason):
2245 self.failures.append((test, reason))
2245 self.failures.append((test, reason))
2246
2246
2247 if self._options.first:
2247 if self._options.first:
2248 self.stop()
2248 self.stop()
2249 else:
2249 else:
2250 with iolock:
2250 with iolock:
2251 if reason == "timed out":
2251 if reason == "timed out":
2252 self.stream.write('t')
2252 self.stream.write('t')
2253 else:
2253 else:
2254 if not self._options.nodiff:
2254 if not self._options.nodiff:
2255 self.stream.write('\n')
2255 self.stream.write('\n')
2256 # Exclude the '\n' from highlighting to lex correctly
2256 # Exclude the '\n' from highlighting to lex correctly
2257 formatted = 'ERROR: %s output changed\n' % test
2257 formatted = 'ERROR: %s output changed\n' % test
2258 self.stream.write(highlightmsg(formatted, self.color))
2258 self.stream.write(highlightmsg(formatted, self.color))
2259 self.stream.write('!')
2259 self.stream.write('!')
2260
2260
2261 self.stream.flush()
2261 self.stream.flush()
2262
2262
2263 def addSuccess(self, test):
2263 def addSuccess(self, test):
2264 with iolock:
2264 with iolock:
2265 super(TestResult, self).addSuccess(test)
2265 super(TestResult, self).addSuccess(test)
2266 self.successes.append(test)
2266 self.successes.append(test)
2267
2267
2268 def addError(self, test, err):
2268 def addError(self, test, err):
2269 super(TestResult, self).addError(test, err)
2269 super(TestResult, self).addError(test, err)
2270 if self._options.first:
2270 if self._options.first:
2271 self.stop()
2271 self.stop()
2272
2272
2273 # Polyfill.
2273 # Polyfill.
2274 def addSkip(self, test, reason):
2274 def addSkip(self, test, reason):
2275 self.skipped.append((test, reason))
2275 self.skipped.append((test, reason))
2276 with iolock:
2276 with iolock:
2277 if self.showAll:
2277 if self.showAll:
2278 self.stream.writeln('skipped %s' % reason)
2278 self.stream.writeln('skipped %s' % reason)
2279 else:
2279 else:
2280 self.stream.write('s')
2280 self.stream.write('s')
2281 self.stream.flush()
2281 self.stream.flush()
2282
2282
2283 def addIgnore(self, test, reason):
2283 def addIgnore(self, test, reason):
2284 self.ignored.append((test, reason))
2284 self.ignored.append((test, reason))
2285 with iolock:
2285 with iolock:
2286 if self.showAll:
2286 if self.showAll:
2287 self.stream.writeln('ignored %s' % reason)
2287 self.stream.writeln('ignored %s' % reason)
2288 else:
2288 else:
2289 if reason not in ('not retesting', "doesn't match keyword"):
2289 if reason not in ('not retesting', "doesn't match keyword"):
2290 self.stream.write('i')
2290 self.stream.write('i')
2291 else:
2291 else:
2292 self.testsRun += 1
2292 self.testsRun += 1
2293 self.stream.flush()
2293 self.stream.flush()
2294
2294
2295 def addOutputMismatch(self, test, ret, got, expected):
2295 def addOutputMismatch(self, test, ret, got, expected):
2296 """Record a mismatch in test output for a particular test."""
2296 """Record a mismatch in test output for a particular test."""
2297 if self.shouldStop or firsterror:
2297 if self.shouldStop or firsterror:
2298 # don't print, some other test case already failed and
2298 # don't print, some other test case already failed and
2299 # printed, we're just stale and probably failed due to our
2299 # printed, we're just stale and probably failed due to our
2300 # temp dir getting cleaned up.
2300 # temp dir getting cleaned up.
2301 return
2301 return
2302
2302
2303 accepted = False
2303 accepted = False
2304 lines = []
2304 lines = []
2305
2305
2306 with iolock:
2306 with iolock:
2307 if self._options.nodiff:
2307 if self._options.nodiff:
2308 pass
2308 pass
2309 elif self._options.view:
2309 elif self._options.view:
2310 v = self._options.view
2310 v = self._options.view
2311 subprocess.call(
2311 subprocess.call(
2312 r'"%s" "%s" "%s"'
2312 r'"%s" "%s" "%s"'
2313 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2313 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2314 shell=True,
2314 shell=True,
2315 )
2315 )
2316 else:
2316 else:
2317 servefail, lines = getdiff(
2317 servefail, lines = getdiff(
2318 expected, got, test.refpath, test.errpath
2318 expected, got, test.refpath, test.errpath
2319 )
2319 )
2320 self.stream.write('\n')
2320 self.stream.write('\n')
2321 for line in lines:
2321 for line in lines:
2322 line = highlightdiff(line, self.color)
2322 line = highlightdiff(line, self.color)
2323 if PYTHON3:
2323 if PYTHON3:
2324 self.stream.flush()
2324 self.stream.flush()
2325 self.stream.buffer.write(line)
2325 self.stream.buffer.write(line)
2326 self.stream.buffer.flush()
2326 self.stream.buffer.flush()
2327 else:
2327 else:
2328 self.stream.write(line)
2328 self.stream.write(line)
2329 self.stream.flush()
2329 self.stream.flush()
2330
2330
2331 if servefail:
2331 if servefail:
2332 raise test.failureException(
2332 raise test.failureException(
2333 'server failed to start (HGPORT=%s)' % test._startport
2333 'server failed to start (HGPORT=%s)' % test._startport
2334 )
2334 )
2335
2335
2336 # handle interactive prompt without releasing iolock
2336 # handle interactive prompt without releasing iolock
2337 if self._options.interactive:
2337 if self._options.interactive:
2338 if test.readrefout() != expected:
2338 if test.readrefout() != expected:
2339 self.stream.write(
2339 self.stream.write(
2340 'Reference output has changed (run again to prompt '
2340 'Reference output has changed (run again to prompt '
2341 'changes)'
2341 'changes)'
2342 )
2342 )
2343 else:
2343 else:
2344 self.stream.write('Accept this change? [y/N] ')
2344 self.stream.write('Accept this change? [y/N] ')
2345 self.stream.flush()
2345 self.stream.flush()
2346 answer = sys.stdin.readline().strip()
2346 answer = sys.stdin.readline().strip()
2347 if answer.lower() in ('y', 'yes'):
2347 if answer.lower() in ('y', 'yes'):
2348 if test.path.endswith(b'.t'):
2348 if test.path.endswith(b'.t'):
2349 rename(test.errpath, test.path)
2349 rename(test.errpath, test.path)
2350 else:
2350 else:
2351 rename(test.errpath, b'%s.out' % test.path)
2351 rename(test.errpath, b'%s.out' % test.path)
2352 accepted = True
2352 accepted = True
2353 if not accepted:
2353 if not accepted:
2354 self.faildata[test.name] = b''.join(lines)
2354 self.faildata[test.name] = b''.join(lines)
2355
2355
2356 return accepted
2356 return accepted
2357
2357
2358 def startTest(self, test):
2358 def startTest(self, test):
2359 super(TestResult, self).startTest(test)
2359 super(TestResult, self).startTest(test)
2360
2360
2361 # os.times module computes the user time and system time spent by
2361 # os.times module computes the user time and system time spent by
2362 # child's processes along with real elapsed time taken by a process.
2362 # child's processes along with real elapsed time taken by a process.
2363 # This module has one limitation. It can only work for Linux user
2363 # This module has one limitation. It can only work for Linux user
2364 # and not for Windows. Hence why we fall back to another function
2364 # and not for Windows. Hence why we fall back to another function
2365 # for wall time calculations.
2365 # for wall time calculations.
2366 test.started_times = os.times()
2366 test.started_times = os.times()
2367 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2367 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2368 test.started_time = time.time()
2368 test.started_time = time.time()
2369 if self._firststarttime is None: # thread racy but irrelevant
2369 if self._firststarttime is None: # thread racy but irrelevant
2370 self._firststarttime = test.started_time
2370 self._firststarttime = test.started_time
2371
2371
2372 def stopTest(self, test, interrupted=False):
2372 def stopTest(self, test, interrupted=False):
2373 super(TestResult, self).stopTest(test)
2373 super(TestResult, self).stopTest(test)
2374
2374
2375 test.stopped_times = os.times()
2375 test.stopped_times = os.times()
2376 stopped_time = time.time()
2376 stopped_time = time.time()
2377
2377
2378 starttime = test.started_times
2378 starttime = test.started_times
2379 endtime = test.stopped_times
2379 endtime = test.stopped_times
2380 origin = self._firststarttime
2380 origin = self._firststarttime
2381 self.times.append(
2381 self.times.append(
2382 (
2382 (
2383 test.name,
2383 test.name,
2384 endtime[2] - starttime[2], # user space CPU time
2384 endtime[2] - starttime[2], # user space CPU time
2385 endtime[3] - starttime[3], # sys space CPU time
2385 endtime[3] - starttime[3], # sys space CPU time
2386 stopped_time - test.started_time, # real time
2386 stopped_time - test.started_time, # real time
2387 test.started_time - origin, # start date in run context
2387 test.started_time - origin, # start date in run context
2388 stopped_time - origin, # end date in run context
2388 stopped_time - origin, # end date in run context
2389 )
2389 )
2390 )
2390 )
2391
2391
2392 if interrupted:
2392 if interrupted:
2393 with iolock:
2393 with iolock:
2394 self.stream.writeln(
2394 self.stream.writeln(
2395 'INTERRUPTED: %s (after %d seconds)'
2395 'INTERRUPTED: %s (after %d seconds)'
2396 % (test.name, self.times[-1][3])
2396 % (test.name, self.times[-1][3])
2397 )
2397 )
2398
2398
2399
2399
2400 def getTestResult():
2400 def getTestResult():
2401 """
2401 """
2402 Returns the relevant test result
2402 Returns the relevant test result
2403 """
2403 """
2404 if "CUSTOM_TEST_RESULT" in os.environ:
2404 if "CUSTOM_TEST_RESULT" in os.environ:
2405 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2405 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2406 return testresultmodule.TestResult
2406 return testresultmodule.TestResult
2407 else:
2407 else:
2408 return TestResult
2408 return TestResult
2409
2409
2410
2410
2411 class TestSuite(unittest.TestSuite):
2411 class TestSuite(unittest.TestSuite):
2412 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2412 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2413
2413
2414 def __init__(
2414 def __init__(
2415 self,
2415 self,
2416 testdir,
2416 testdir,
2417 jobs=1,
2417 jobs=1,
2418 whitelist=None,
2418 whitelist=None,
2419 blacklist=None,
2419 blacklist=None,
2420 keywords=None,
2420 keywords=None,
2421 loop=False,
2421 loop=False,
2422 runs_per_test=1,
2422 runs_per_test=1,
2423 loadtest=None,
2423 loadtest=None,
2424 showchannels=False,
2424 showchannels=False,
2425 *args,
2425 *args,
2426 **kwargs
2426 **kwargs
2427 ):
2427 ):
2428 """Create a new instance that can run tests with a configuration.
2428 """Create a new instance that can run tests with a configuration.
2429
2429
2430 testdir specifies the directory where tests are executed from. This
2430 testdir specifies the directory where tests are executed from. This
2431 is typically the ``tests`` directory from Mercurial's source
2431 is typically the ``tests`` directory from Mercurial's source
2432 repository.
2432 repository.
2433
2433
2434 jobs specifies the number of jobs to run concurrently. Each test
2434 jobs specifies the number of jobs to run concurrently. Each test
2435 executes on its own thread. Tests actually spawn new processes, so
2435 executes on its own thread. Tests actually spawn new processes, so
2436 state mutation should not be an issue.
2436 state mutation should not be an issue.
2437
2437
2438 If there is only one job, it will use the main thread.
2438 If there is only one job, it will use the main thread.
2439
2439
2440 whitelist and blacklist denote tests that have been whitelisted and
2440 whitelist and blacklist denote tests that have been whitelisted and
2441 blacklisted, respectively. These arguments don't belong in TestSuite.
2441 blacklisted, respectively. These arguments don't belong in TestSuite.
2442 Instead, whitelist and blacklist should be handled by the thing that
2442 Instead, whitelist and blacklist should be handled by the thing that
2443 populates the TestSuite with tests. They are present to preserve
2443 populates the TestSuite with tests. They are present to preserve
2444 backwards compatible behavior which reports skipped tests as part
2444 backwards compatible behavior which reports skipped tests as part
2445 of the results.
2445 of the results.
2446
2446
2447 keywords denotes key words that will be used to filter which tests
2447 keywords denotes key words that will be used to filter which tests
2448 to execute. This arguably belongs outside of TestSuite.
2448 to execute. This arguably belongs outside of TestSuite.
2449
2449
2450 loop denotes whether to loop over tests forever.
2450 loop denotes whether to loop over tests forever.
2451 """
2451 """
2452 super(TestSuite, self).__init__(*args, **kwargs)
2452 super(TestSuite, self).__init__(*args, **kwargs)
2453
2453
2454 self._jobs = jobs
2454 self._jobs = jobs
2455 self._whitelist = whitelist
2455 self._whitelist = whitelist
2456 self._blacklist = blacklist
2456 self._blacklist = blacklist
2457 self._keywords = keywords
2457 self._keywords = keywords
2458 self._loop = loop
2458 self._loop = loop
2459 self._runs_per_test = runs_per_test
2459 self._runs_per_test = runs_per_test
2460 self._loadtest = loadtest
2460 self._loadtest = loadtest
2461 self._showchannels = showchannels
2461 self._showchannels = showchannels
2462
2462
2463 def run(self, result):
2463 def run(self, result):
2464 # We have a number of filters that need to be applied. We do this
2464 # We have a number of filters that need to be applied. We do this
2465 # here instead of inside Test because it makes the running logic for
2465 # here instead of inside Test because it makes the running logic for
2466 # Test simpler.
2466 # Test simpler.
2467 tests = []
2467 tests = []
2468 num_tests = [0]
2468 num_tests = [0]
2469 for test in self._tests:
2469 for test in self._tests:
2470
2470
2471 def get():
2471 def get():
2472 num_tests[0] += 1
2472 num_tests[0] += 1
2473 if getattr(test, 'should_reload', False):
2473 if getattr(test, 'should_reload', False):
2474 return self._loadtest(test, num_tests[0])
2474 return self._loadtest(test, num_tests[0])
2475 return test
2475 return test
2476
2476
2477 if not os.path.exists(test.path):
2477 if not os.path.exists(test.path):
2478 result.addSkip(test, "Doesn't exist")
2478 result.addSkip(test, "Doesn't exist")
2479 continue
2479 continue
2480
2480
2481 is_whitelisted = self._whitelist and (
2481 is_whitelisted = self._whitelist and (
2482 test.relpath in self._whitelist or test.bname in self._whitelist
2482 test.relpath in self._whitelist or test.bname in self._whitelist
2483 )
2483 )
2484 if not is_whitelisted:
2484 if not is_whitelisted:
2485 is_blacklisted = self._blacklist and (
2485 is_blacklisted = self._blacklist and (
2486 test.relpath in self._blacklist
2486 test.relpath in self._blacklist
2487 or test.bname in self._blacklist
2487 or test.bname in self._blacklist
2488 )
2488 )
2489 if is_blacklisted:
2489 if is_blacklisted:
2490 result.addSkip(test, 'blacklisted')
2490 result.addSkip(test, 'blacklisted')
2491 continue
2491 continue
2492 if self._keywords:
2492 if self._keywords:
2493 with open(test.path, 'rb') as f:
2493 with open(test.path, 'rb') as f:
2494 t = f.read().lower() + test.bname.lower()
2494 t = f.read().lower() + test.bname.lower()
2495 ignored = False
2495 ignored = False
2496 for k in self._keywords.lower().split():
2496 for k in self._keywords.lower().split():
2497 if k not in t:
2497 if k not in t:
2498 result.addIgnore(test, "doesn't match keyword")
2498 result.addIgnore(test, "doesn't match keyword")
2499 ignored = True
2499 ignored = True
2500 break
2500 break
2501
2501
2502 if ignored:
2502 if ignored:
2503 continue
2503 continue
2504 for _ in xrange(self._runs_per_test):
2504 for _ in xrange(self._runs_per_test):
2505 tests.append(get())
2505 tests.append(get())
2506
2506
2507 runtests = list(tests)
2507 runtests = list(tests)
2508 done = queue.Queue()
2508 done = queue.Queue()
2509 running = 0
2509 running = 0
2510
2510
2511 channels = [""] * self._jobs
2511 channels = [""] * self._jobs
2512
2512
2513 def job(test, result):
2513 def job(test, result):
2514 for n, v in enumerate(channels):
2514 for n, v in enumerate(channels):
2515 if not v:
2515 if not v:
2516 channel = n
2516 channel = n
2517 break
2517 break
2518 else:
2518 else:
2519 raise ValueError('Could not find output channel')
2519 raise ValueError('Could not find output channel')
2520 channels[channel] = "=" + test.name[5:].split(".")[0]
2520 channels[channel] = "=" + test.name[5:].split(".")[0]
2521 try:
2521 try:
2522 test(result)
2522 test(result)
2523 done.put(None)
2523 done.put(None)
2524 except KeyboardInterrupt:
2524 except KeyboardInterrupt:
2525 pass
2525 pass
2526 except: # re-raises
2526 except: # re-raises
2527 done.put(('!', test, 'run-test raised an error, see traceback'))
2527 done.put(('!', test, 'run-test raised an error, see traceback'))
2528 raise
2528 raise
2529 finally:
2529 finally:
2530 try:
2530 try:
2531 channels[channel] = ''
2531 channels[channel] = ''
2532 except IndexError:
2532 except IndexError:
2533 pass
2533 pass
2534
2534
2535 def stat():
2535 def stat():
2536 count = 0
2536 count = 0
2537 while channels:
2537 while channels:
2538 d = '\n%03s ' % count
2538 d = '\n%03s ' % count
2539 for n, v in enumerate(channels):
2539 for n, v in enumerate(channels):
2540 if v:
2540 if v:
2541 d += v[0]
2541 d += v[0]
2542 channels[n] = v[1:] or '.'
2542 channels[n] = v[1:] or '.'
2543 else:
2543 else:
2544 d += ' '
2544 d += ' '
2545 d += ' '
2545 d += ' '
2546 with iolock:
2546 with iolock:
2547 sys.stdout.write(d + ' ')
2547 sys.stdout.write(d + ' ')
2548 sys.stdout.flush()
2548 sys.stdout.flush()
2549 for x in xrange(10):
2549 for x in xrange(10):
2550 if channels:
2550 if channels:
2551 time.sleep(0.1)
2551 time.sleep(0.1)
2552 count += 1
2552 count += 1
2553
2553
2554 stoppedearly = False
2554 stoppedearly = False
2555
2555
2556 if self._showchannels:
2556 if self._showchannels:
2557 statthread = threading.Thread(target=stat, name="stat")
2557 statthread = threading.Thread(target=stat, name="stat")
2558 statthread.start()
2558 statthread.start()
2559
2559
2560 try:
2560 try:
2561 while tests or running:
2561 while tests or running:
2562 if not done.empty() or running == self._jobs or not tests:
2562 if not done.empty() or running == self._jobs or not tests:
2563 try:
2563 try:
2564 done.get(True, 1)
2564 done.get(True, 1)
2565 running -= 1
2565 running -= 1
2566 if result and result.shouldStop:
2566 if result and result.shouldStop:
2567 stoppedearly = True
2567 stoppedearly = True
2568 break
2568 break
2569 except queue.Empty:
2569 except queue.Empty:
2570 continue
2570 continue
2571 if tests and not running == self._jobs:
2571 if tests and not running == self._jobs:
2572 test = tests.pop(0)
2572 test = tests.pop(0)
2573 if self._loop:
2573 if self._loop:
2574 if getattr(test, 'should_reload', False):
2574 if getattr(test, 'should_reload', False):
2575 num_tests[0] += 1
2575 num_tests[0] += 1
2576 tests.append(self._loadtest(test, num_tests[0]))
2576 tests.append(self._loadtest(test, num_tests[0]))
2577 else:
2577 else:
2578 tests.append(test)
2578 tests.append(test)
2579 if self._jobs == 1:
2579 if self._jobs == 1:
2580 job(test, result)
2580 job(test, result)
2581 else:
2581 else:
2582 t = threading.Thread(
2582 t = threading.Thread(
2583 target=job, name=test.name, args=(test, result)
2583 target=job, name=test.name, args=(test, result)
2584 )
2584 )
2585 t.start()
2585 t.start()
2586 running += 1
2586 running += 1
2587
2587
2588 # If we stop early we still need to wait on started tests to
2588 # If we stop early we still need to wait on started tests to
2589 # finish. Otherwise, there is a race between the test completing
2589 # finish. Otherwise, there is a race between the test completing
2590 # and the test's cleanup code running. This could result in the
2590 # and the test's cleanup code running. This could result in the
2591 # test reporting incorrect.
2591 # test reporting incorrect.
2592 if stoppedearly:
2592 if stoppedearly:
2593 while running:
2593 while running:
2594 try:
2594 try:
2595 done.get(True, 1)
2595 done.get(True, 1)
2596 running -= 1
2596 running -= 1
2597 except queue.Empty:
2597 except queue.Empty:
2598 continue
2598 continue
2599 except KeyboardInterrupt:
2599 except KeyboardInterrupt:
2600 for test in runtests:
2600 for test in runtests:
2601 test.abort()
2601 test.abort()
2602
2602
2603 channels = []
2603 channels = []
2604
2604
2605 return result
2605 return result
2606
2606
2607
2607
2608 # Save the most recent 5 wall-clock runtimes of each test to a
2608 # Save the most recent 5 wall-clock runtimes of each test to a
2609 # human-readable text file named .testtimes. Tests are sorted
2609 # human-readable text file named .testtimes. Tests are sorted
2610 # alphabetically, while times for each test are listed from oldest to
2610 # alphabetically, while times for each test are listed from oldest to
2611 # newest.
2611 # newest.
2612
2612
2613
2613
2614 def loadtimes(outputdir):
2614 def loadtimes(outputdir):
2615 times = []
2615 times = []
2616 try:
2616 try:
2617 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2617 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2618 for line in fp:
2618 for line in fp:
2619 m = re.match('(.*?) ([0-9. ]+)', line)
2619 m = re.match('(.*?) ([0-9. ]+)', line)
2620 times.append(
2620 times.append(
2621 (m.group(1), [float(t) for t in m.group(2).split()])
2621 (m.group(1), [float(t) for t in m.group(2).split()])
2622 )
2622 )
2623 except IOError as err:
2623 except IOError as err:
2624 if err.errno != errno.ENOENT:
2624 if err.errno != errno.ENOENT:
2625 raise
2625 raise
2626 return times
2626 return times
2627
2627
2628
2628
2629 def savetimes(outputdir, result):
2629 def savetimes(outputdir, result):
2630 saved = dict(loadtimes(outputdir))
2630 saved = dict(loadtimes(outputdir))
2631 maxruns = 5
2631 maxruns = 5
2632 skipped = {str(t[0]) for t in result.skipped}
2632 skipped = {str(t[0]) for t in result.skipped}
2633 for tdata in result.times:
2633 for tdata in result.times:
2634 test, real = tdata[0], tdata[3]
2634 test, real = tdata[0], tdata[3]
2635 if test not in skipped:
2635 if test not in skipped:
2636 ts = saved.setdefault(test, [])
2636 ts = saved.setdefault(test, [])
2637 ts.append(real)
2637 ts.append(real)
2638 ts[:] = ts[-maxruns:]
2638 ts[:] = ts[-maxruns:]
2639
2639
2640 fd, tmpname = tempfile.mkstemp(
2640 fd, tmpname = tempfile.mkstemp(
2641 prefix=b'.testtimes', dir=outputdir, text=True
2641 prefix=b'.testtimes', dir=outputdir, text=True
2642 )
2642 )
2643 with os.fdopen(fd, 'w') as fp:
2643 with os.fdopen(fd, 'w') as fp:
2644 for name, ts in sorted(saved.items()):
2644 for name, ts in sorted(saved.items()):
2645 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2645 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2646 timepath = os.path.join(outputdir, b'.testtimes')
2646 timepath = os.path.join(outputdir, b'.testtimes')
2647 try:
2647 try:
2648 os.unlink(timepath)
2648 os.unlink(timepath)
2649 except OSError:
2649 except OSError:
2650 pass
2650 pass
2651 try:
2651 try:
2652 os.rename(tmpname, timepath)
2652 os.rename(tmpname, timepath)
2653 except OSError:
2653 except OSError:
2654 pass
2654 pass
2655
2655
2656
2656
2657 class TextTestRunner(unittest.TextTestRunner):
2657 class TextTestRunner(unittest.TextTestRunner):
2658 """Custom unittest test runner that uses appropriate settings."""
2658 """Custom unittest test runner that uses appropriate settings."""
2659
2659
2660 def __init__(self, runner, *args, **kwargs):
2660 def __init__(self, runner, *args, **kwargs):
2661 super(TextTestRunner, self).__init__(*args, **kwargs)
2661 super(TextTestRunner, self).__init__(*args, **kwargs)
2662
2662
2663 self._runner = runner
2663 self._runner = runner
2664
2664
2665 self._result = getTestResult()(
2665 self._result = getTestResult()(
2666 self._runner.options, self.stream, self.descriptions, self.verbosity
2666 self._runner.options, self.stream, self.descriptions, self.verbosity
2667 )
2667 )
2668
2668
2669 def listtests(self, test):
2669 def listtests(self, test):
2670 test = sorted(test, key=lambda t: t.name)
2670 test = sorted(test, key=lambda t: t.name)
2671
2671
2672 self._result.onStart(test)
2672 self._result.onStart(test)
2673
2673
2674 for t in test:
2674 for t in test:
2675 print(t.name)
2675 print(t.name)
2676 self._result.addSuccess(t)
2676 self._result.addSuccess(t)
2677
2677
2678 if self._runner.options.xunit:
2678 if self._runner.options.xunit:
2679 with open(self._runner.options.xunit, "wb") as xuf:
2679 with open(self._runner.options.xunit, "wb") as xuf:
2680 self._writexunit(self._result, xuf)
2680 self._writexunit(self._result, xuf)
2681
2681
2682 if self._runner.options.json:
2682 if self._runner.options.json:
2683 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2683 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2684 with open(jsonpath, 'w') as fp:
2684 with open(jsonpath, 'w') as fp:
2685 self._writejson(self._result, fp)
2685 self._writejson(self._result, fp)
2686
2686
2687 return self._result
2687 return self._result
2688
2688
2689 def run(self, test):
2689 def run(self, test):
2690 self._result.onStart(test)
2690 self._result.onStart(test)
2691 test(self._result)
2691 test(self._result)
2692
2692
2693 failed = len(self._result.failures)
2693 failed = len(self._result.failures)
2694 skipped = len(self._result.skipped)
2694 skipped = len(self._result.skipped)
2695 ignored = len(self._result.ignored)
2695 ignored = len(self._result.ignored)
2696
2696
2697 with iolock:
2697 with iolock:
2698 self.stream.writeln('')
2698 self.stream.writeln('')
2699
2699
2700 if not self._runner.options.noskips:
2700 if not self._runner.options.noskips:
2701 for test, msg in sorted(
2701 for test, msg in sorted(
2702 self._result.skipped, key=lambda s: s[0].name
2702 self._result.skipped, key=lambda s: s[0].name
2703 ):
2703 ):
2704 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2704 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2705 msg = highlightmsg(formatted, self._result.color)
2705 msg = highlightmsg(formatted, self._result.color)
2706 self.stream.write(msg)
2706 self.stream.write(msg)
2707 for test, msg in sorted(
2707 for test, msg in sorted(
2708 self._result.failures, key=lambda f: f[0].name
2708 self._result.failures, key=lambda f: f[0].name
2709 ):
2709 ):
2710 formatted = 'Failed %s: %s\n' % (test.name, msg)
2710 formatted = 'Failed %s: %s\n' % (test.name, msg)
2711 self.stream.write(highlightmsg(formatted, self._result.color))
2711 self.stream.write(highlightmsg(formatted, self._result.color))
2712 for test, msg in sorted(
2712 for test, msg in sorted(
2713 self._result.errors, key=lambda e: e[0].name
2713 self._result.errors, key=lambda e: e[0].name
2714 ):
2714 ):
2715 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2715 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2716
2716
2717 if self._runner.options.xunit:
2717 if self._runner.options.xunit:
2718 with open(self._runner.options.xunit, "wb") as xuf:
2718 with open(self._runner.options.xunit, "wb") as xuf:
2719 self._writexunit(self._result, xuf)
2719 self._writexunit(self._result, xuf)
2720
2720
2721 if self._runner.options.json:
2721 if self._runner.options.json:
2722 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2722 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2723 with open(jsonpath, 'w') as fp:
2723 with open(jsonpath, 'w') as fp:
2724 self._writejson(self._result, fp)
2724 self._writejson(self._result, fp)
2725
2725
2726 self._runner._checkhglib('Tested')
2726 self._runner._checkhglib('Tested')
2727
2727
2728 savetimes(self._runner._outputdir, self._result)
2728 savetimes(self._runner._outputdir, self._result)
2729
2729
2730 if failed and self._runner.options.known_good_rev:
2730 if failed and self._runner.options.known_good_rev:
2731 self._bisecttests(t for t, m in self._result.failures)
2731 self._bisecttests(t for t, m in self._result.failures)
2732 self.stream.writeln(
2732 self.stream.writeln(
2733 '# Ran %d tests, %d skipped, %d failed.'
2733 '# Ran %d tests, %d skipped, %d failed.'
2734 % (self._result.testsRun, skipped + ignored, failed)
2734 % (self._result.testsRun, skipped + ignored, failed)
2735 )
2735 )
2736 if failed:
2736 if failed:
2737 self.stream.writeln(
2737 self.stream.writeln(
2738 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2738 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2739 )
2739 )
2740 if self._runner.options.time:
2740 if self._runner.options.time:
2741 self.printtimes(self._result.times)
2741 self.printtimes(self._result.times)
2742
2742
2743 if self._runner.options.exceptions:
2743 if self._runner.options.exceptions:
2744 exceptions = aggregateexceptions(
2744 exceptions = aggregateexceptions(
2745 os.path.join(self._runner._outputdir, b'exceptions')
2745 os.path.join(self._runner._outputdir, b'exceptions')
2746 )
2746 )
2747
2747
2748 self.stream.writeln('Exceptions Report:')
2748 self.stream.writeln('Exceptions Report:')
2749 self.stream.writeln(
2749 self.stream.writeln(
2750 '%d total from %d frames'
2750 '%d total from %d frames'
2751 % (exceptions['total'], len(exceptions['exceptioncounts']))
2751 % (exceptions['total'], len(exceptions['exceptioncounts']))
2752 )
2752 )
2753 combined = exceptions['combined']
2753 combined = exceptions['combined']
2754 for key in sorted(combined, key=combined.get, reverse=True):
2754 for key in sorted(combined, key=combined.get, reverse=True):
2755 frame, line, exc = key
2755 frame, line, exc = key
2756 totalcount, testcount, leastcount, leasttest = combined[key]
2756 totalcount, testcount, leastcount, leasttest = combined[key]
2757
2757
2758 self.stream.writeln(
2758 self.stream.writeln(
2759 '%d (%d tests)\t%s: %s (%s - %d total)'
2759 '%d (%d tests)\t%s: %s (%s - %d total)'
2760 % (
2760 % (
2761 totalcount,
2761 totalcount,
2762 testcount,
2762 testcount,
2763 frame,
2763 frame,
2764 exc,
2764 exc,
2765 leasttest,
2765 leasttest,
2766 leastcount,
2766 leastcount,
2767 )
2767 )
2768 )
2768 )
2769
2769
2770 self.stream.flush()
2770 self.stream.flush()
2771
2771
2772 return self._result
2772 return self._result
2773
2773
2774 def _bisecttests(self, tests):
2774 def _bisecttests(self, tests):
2775 bisectcmd = ['hg', 'bisect']
2775 bisectcmd = ['hg', 'bisect']
2776 bisectrepo = self._runner.options.bisect_repo
2776 bisectrepo = self._runner.options.bisect_repo
2777 if bisectrepo:
2777 if bisectrepo:
2778 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2778 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2779
2779
2780 def pread(args):
2780 def pread(args):
2781 env = os.environ.copy()
2781 env = os.environ.copy()
2782 env['HGPLAIN'] = '1'
2782 env['HGPLAIN'] = '1'
2783 p = subprocess.Popen(
2783 p = subprocess.Popen(
2784 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2784 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2785 )
2785 )
2786 data = p.stdout.read()
2786 data = p.stdout.read()
2787 p.wait()
2787 p.wait()
2788 return data
2788 return data
2789
2789
2790 for test in tests:
2790 for test in tests:
2791 pread(bisectcmd + ['--reset']),
2791 pread(bisectcmd + ['--reset']),
2792 pread(bisectcmd + ['--bad', '.'])
2792 pread(bisectcmd + ['--bad', '.'])
2793 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2793 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2794 # TODO: we probably need to forward more options
2794 # TODO: we probably need to forward more options
2795 # that alter hg's behavior inside the tests.
2795 # that alter hg's behavior inside the tests.
2796 opts = ''
2796 opts = ''
2797 withhg = self._runner.options.with_hg
2797 withhg = self._runner.options.with_hg
2798 if withhg:
2798 if withhg:
2799 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2799 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2800 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2800 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2801 data = pread(bisectcmd + ['--command', rtc])
2801 data = pread(bisectcmd + ['--command', rtc])
2802 m = re.search(
2802 m = re.search(
2803 (
2803 (
2804 br'\nThe first (?P<goodbad>bad|good) revision '
2804 br'\nThe first (?P<goodbad>bad|good) revision '
2805 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2805 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2806 br'summary: +(?P<summary>[^\n]+)\n'
2806 br'summary: +(?P<summary>[^\n]+)\n'
2807 ),
2807 ),
2808 data,
2808 data,
2809 (re.MULTILINE | re.DOTALL),
2809 (re.MULTILINE | re.DOTALL),
2810 )
2810 )
2811 if m is None:
2811 if m is None:
2812 self.stream.writeln(
2812 self.stream.writeln(
2813 'Failed to identify failure point for %s' % test
2813 'Failed to identify failure point for %s' % test
2814 )
2814 )
2815 continue
2815 continue
2816 dat = m.groupdict()
2816 dat = m.groupdict()
2817 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2817 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2818 self.stream.writeln(
2818 self.stream.writeln(
2819 '%s %s by %s (%s)'
2819 '%s %s by %s (%s)'
2820 % (
2820 % (
2821 test,
2821 test,
2822 verb,
2822 verb,
2823 dat['node'].decode('ascii'),
2823 dat['node'].decode('ascii'),
2824 dat['summary'].decode('utf8', 'ignore'),
2824 dat['summary'].decode('utf8', 'ignore'),
2825 )
2825 )
2826 )
2826 )
2827
2827
2828 def printtimes(self, times):
2828 def printtimes(self, times):
2829 # iolock held by run
2829 # iolock held by run
2830 self.stream.writeln('# Producing time report')
2830 self.stream.writeln('# Producing time report')
2831 times.sort(key=lambda t: (t[3]))
2831 times.sort(key=lambda t: (t[3]))
2832 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2832 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2833 self.stream.writeln(
2833 self.stream.writeln(
2834 '%-7s %-7s %-7s %-7s %-7s %s'
2834 '%-7s %-7s %-7s %-7s %-7s %s'
2835 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2835 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2836 )
2836 )
2837 for tdata in times:
2837 for tdata in times:
2838 test = tdata[0]
2838 test = tdata[0]
2839 cuser, csys, real, start, end = tdata[1:6]
2839 cuser, csys, real, start, end = tdata[1:6]
2840 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2840 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2841
2841
2842 @staticmethod
2842 @staticmethod
2843 def _writexunit(result, outf):
2843 def _writexunit(result, outf):
2844 # See http://llg.cubic.org/docs/junit/ for a reference.
2844 # See http://llg.cubic.org/docs/junit/ for a reference.
2845 timesd = {t[0]: t[3] for t in result.times}
2845 timesd = {t[0]: t[3] for t in result.times}
2846 doc = minidom.Document()
2846 doc = minidom.Document()
2847 s = doc.createElement('testsuite')
2847 s = doc.createElement('testsuite')
2848 s.setAttribute('errors', "0") # TODO
2848 s.setAttribute('errors', "0") # TODO
2849 s.setAttribute('failures', str(len(result.failures)))
2849 s.setAttribute('failures', str(len(result.failures)))
2850 s.setAttribute('name', 'run-tests')
2850 s.setAttribute('name', 'run-tests')
2851 s.setAttribute(
2851 s.setAttribute(
2852 'skipped', str(len(result.skipped) + len(result.ignored))
2852 'skipped', str(len(result.skipped) + len(result.ignored))
2853 )
2853 )
2854 s.setAttribute('tests', str(result.testsRun))
2854 s.setAttribute('tests', str(result.testsRun))
2855 doc.appendChild(s)
2855 doc.appendChild(s)
2856 for tc in result.successes:
2856 for tc in result.successes:
2857 t = doc.createElement('testcase')
2857 t = doc.createElement('testcase')
2858 t.setAttribute('name', tc.name)
2858 t.setAttribute('name', tc.name)
2859 tctime = timesd.get(tc.name)
2859 tctime = timesd.get(tc.name)
2860 if tctime is not None:
2860 if tctime is not None:
2861 t.setAttribute('time', '%.3f' % tctime)
2861 t.setAttribute('time', '%.3f' % tctime)
2862 s.appendChild(t)
2862 s.appendChild(t)
2863 for tc, err in sorted(result.faildata.items()):
2863 for tc, err in sorted(result.faildata.items()):
2864 t = doc.createElement('testcase')
2864 t = doc.createElement('testcase')
2865 t.setAttribute('name', tc)
2865 t.setAttribute('name', tc)
2866 tctime = timesd.get(tc)
2866 tctime = timesd.get(tc)
2867 if tctime is not None:
2867 if tctime is not None:
2868 t.setAttribute('time', '%.3f' % tctime)
2868 t.setAttribute('time', '%.3f' % tctime)
2869 # createCDATASection expects a unicode or it will
2869 # createCDATASection expects a unicode or it will
2870 # convert using default conversion rules, which will
2870 # convert using default conversion rules, which will
2871 # fail if string isn't ASCII.
2871 # fail if string isn't ASCII.
2872 err = cdatasafe(err).decode('utf-8', 'replace')
2872 err = cdatasafe(err).decode('utf-8', 'replace')
2873 cd = doc.createCDATASection(err)
2873 cd = doc.createCDATASection(err)
2874 # Use 'failure' here instead of 'error' to match errors = 0,
2874 # Use 'failure' here instead of 'error' to match errors = 0,
2875 # failures = len(result.failures) in the testsuite element.
2875 # failures = len(result.failures) in the testsuite element.
2876 failelem = doc.createElement('failure')
2876 failelem = doc.createElement('failure')
2877 failelem.setAttribute('message', 'output changed')
2877 failelem.setAttribute('message', 'output changed')
2878 failelem.setAttribute('type', 'output-mismatch')
2878 failelem.setAttribute('type', 'output-mismatch')
2879 failelem.appendChild(cd)
2879 failelem.appendChild(cd)
2880 t.appendChild(failelem)
2880 t.appendChild(failelem)
2881 s.appendChild(t)
2881 s.appendChild(t)
2882 for tc, message in result.skipped:
2882 for tc, message in result.skipped:
2883 # According to the schema, 'skipped' has no attributes. So store
2883 # According to the schema, 'skipped' has no attributes. So store
2884 # the skip message as a text node instead.
2884 # the skip message as a text node instead.
2885 t = doc.createElement('testcase')
2885 t = doc.createElement('testcase')
2886 t.setAttribute('name', tc.name)
2886 t.setAttribute('name', tc.name)
2887 binmessage = message.encode('utf-8')
2887 binmessage = message.encode('utf-8')
2888 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2888 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2889 cd = doc.createCDATASection(message)
2889 cd = doc.createCDATASection(message)
2890 skipelem = doc.createElement('skipped')
2890 skipelem = doc.createElement('skipped')
2891 skipelem.appendChild(cd)
2891 skipelem.appendChild(cd)
2892 t.appendChild(skipelem)
2892 t.appendChild(skipelem)
2893 s.appendChild(t)
2893 s.appendChild(t)
2894 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2894 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2895
2895
2896 @staticmethod
2896 @staticmethod
2897 def _writejson(result, outf):
2897 def _writejson(result, outf):
2898 timesd = {}
2898 timesd = {}
2899 for tdata in result.times:
2899 for tdata in result.times:
2900 test = tdata[0]
2900 test = tdata[0]
2901 timesd[test] = tdata[1:]
2901 timesd[test] = tdata[1:]
2902
2902
2903 outcome = {}
2903 outcome = {}
2904 groups = [
2904 groups = [
2905 ('success', ((tc, None) for tc in result.successes)),
2905 ('success', ((tc, None) for tc in result.successes)),
2906 ('failure', result.failures),
2906 ('failure', result.failures),
2907 ('skip', result.skipped),
2907 ('skip', result.skipped),
2908 ]
2908 ]
2909 for res, testcases in groups:
2909 for res, testcases in groups:
2910 for tc, __ in testcases:
2910 for tc, __ in testcases:
2911 if tc.name in timesd:
2911 if tc.name in timesd:
2912 diff = result.faildata.get(tc.name, b'')
2912 diff = result.faildata.get(tc.name, b'')
2913 try:
2913 try:
2914 diff = diff.decode('unicode_escape')
2914 diff = diff.decode('unicode_escape')
2915 except UnicodeDecodeError as e:
2915 except UnicodeDecodeError as e:
2916 diff = '%r decoding diff, sorry' % e
2916 diff = '%r decoding diff, sorry' % e
2917 tres = {
2917 tres = {
2918 'result': res,
2918 'result': res,
2919 'time': ('%0.3f' % timesd[tc.name][2]),
2919 'time': ('%0.3f' % timesd[tc.name][2]),
2920 'cuser': ('%0.3f' % timesd[tc.name][0]),
2920 'cuser': ('%0.3f' % timesd[tc.name][0]),
2921 'csys': ('%0.3f' % timesd[tc.name][1]),
2921 'csys': ('%0.3f' % timesd[tc.name][1]),
2922 'start': ('%0.3f' % timesd[tc.name][3]),
2922 'start': ('%0.3f' % timesd[tc.name][3]),
2923 'end': ('%0.3f' % timesd[tc.name][4]),
2923 'end': ('%0.3f' % timesd[tc.name][4]),
2924 'diff': diff,
2924 'diff': diff,
2925 }
2925 }
2926 else:
2926 else:
2927 # blacklisted test
2927 # blacklisted test
2928 tres = {'result': res}
2928 tres = {'result': res}
2929
2929
2930 outcome[tc.name] = tres
2930 outcome[tc.name] = tres
2931 jsonout = json.dumps(
2931 jsonout = json.dumps(
2932 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2932 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2933 )
2933 )
2934 outf.writelines(("testreport =", jsonout))
2934 outf.writelines(("testreport =", jsonout))
2935
2935
2936
2936
2937 def sorttests(testdescs, previoustimes, shuffle=False):
2937 def sorttests(testdescs, previoustimes, shuffle=False):
2938 """Do an in-place sort of tests."""
2938 """Do an in-place sort of tests."""
2939 if shuffle:
2939 if shuffle:
2940 random.shuffle(testdescs)
2940 random.shuffle(testdescs)
2941 return
2941 return
2942
2942
2943 if previoustimes:
2943 if previoustimes:
2944
2944
2945 def sortkey(f):
2945 def sortkey(f):
2946 f = f['path']
2946 f = f['path']
2947 if f in previoustimes:
2947 if f in previoustimes:
2948 # Use most recent time as estimate
2948 # Use most recent time as estimate
2949 return -(previoustimes[f][-1])
2949 return -(previoustimes[f][-1])
2950 else:
2950 else:
2951 # Default to a rather arbitrary value of 1 second for new tests
2951 # Default to a rather arbitrary value of 1 second for new tests
2952 return -1.0
2952 return -1.0
2953
2953
2954 else:
2954 else:
2955 # keywords for slow tests
2955 # keywords for slow tests
2956 slow = {
2956 slow = {
2957 b'svn': 10,
2957 b'svn': 10,
2958 b'cvs': 10,
2958 b'cvs': 10,
2959 b'hghave': 10,
2959 b'hghave': 10,
2960 b'largefiles-update': 10,
2960 b'largefiles-update': 10,
2961 b'run-tests': 10,
2961 b'run-tests': 10,
2962 b'corruption': 10,
2962 b'corruption': 10,
2963 b'race': 10,
2963 b'race': 10,
2964 b'i18n': 10,
2964 b'i18n': 10,
2965 b'check': 100,
2965 b'check': 100,
2966 b'gendoc': 100,
2966 b'gendoc': 100,
2967 b'contrib-perf': 200,
2967 b'contrib-perf': 200,
2968 b'merge-combination': 100,
2968 b'merge-combination': 100,
2969 }
2969 }
2970 perf = {}
2970 perf = {}
2971
2971
2972 def sortkey(f):
2972 def sortkey(f):
2973 # run largest tests first, as they tend to take the longest
2973 # run largest tests first, as they tend to take the longest
2974 f = f['path']
2974 f = f['path']
2975 try:
2975 try:
2976 return perf[f]
2976 return perf[f]
2977 except KeyError:
2977 except KeyError:
2978 try:
2978 try:
2979 val = -os.stat(f).st_size
2979 val = -os.stat(f).st_size
2980 except OSError as e:
2980 except OSError as e:
2981 if e.errno != errno.ENOENT:
2981 if e.errno != errno.ENOENT:
2982 raise
2982 raise
2983 perf[f] = -1e9 # file does not exist, tell early
2983 perf[f] = -1e9 # file does not exist, tell early
2984 return -1e9
2984 return -1e9
2985 for kw, mul in slow.items():
2985 for kw, mul in slow.items():
2986 if kw in f:
2986 if kw in f:
2987 val *= mul
2987 val *= mul
2988 if f.endswith(b'.py'):
2988 if f.endswith(b'.py'):
2989 val /= 10.0
2989 val /= 10.0
2990 perf[f] = val / 1000.0
2990 perf[f] = val / 1000.0
2991 return perf[f]
2991 return perf[f]
2992
2992
2993 testdescs.sort(key=sortkey)
2993 testdescs.sort(key=sortkey)
2994
2994
2995
2995
2996 class TestRunner(object):
2996 class TestRunner(object):
2997 """Holds context for executing tests.
2997 """Holds context for executing tests.
2998
2998
2999 Tests rely on a lot of state. This object holds it for them.
2999 Tests rely on a lot of state. This object holds it for them.
3000 """
3000 """
3001
3001
3002 # Programs required to run tests.
3002 # Programs required to run tests.
3003 REQUIREDTOOLS = [
3003 REQUIREDTOOLS = [
3004 b'diff',
3004 b'diff',
3005 b'grep',
3005 b'grep',
3006 b'unzip',
3006 b'unzip',
3007 b'gunzip',
3007 b'gunzip',
3008 b'bunzip2',
3008 b'bunzip2',
3009 b'sed',
3009 b'sed',
3010 ]
3010 ]
3011
3011
3012 # Maps file extensions to test class.
3012 # Maps file extensions to test class.
3013 TESTTYPES = [
3013 TESTTYPES = [
3014 (b'.py', PythonTest),
3014 (b'.py', PythonTest),
3015 (b'.t', TTest),
3015 (b'.t', TTest),
3016 ]
3016 ]
3017
3017
3018 def __init__(self):
3018 def __init__(self):
3019 self.options = None
3019 self.options = None
3020 self._hgroot = None
3020 self._hgroot = None
3021 self._testdir = None
3021 self._testdir = None
3022 self._outputdir = None
3022 self._outputdir = None
3023 self._hgtmp = None
3023 self._hgtmp = None
3024 self._installdir = None
3024 self._installdir = None
3025 self._bindir = None
3025 self._bindir = None
3026 # a place for run-tests.py to generate executable it needs
3026 # a place for run-tests.py to generate executable it needs
3027 self._custom_bin_dir = None
3027 self._custom_bin_dir = None
3028 self._pythondir = None
3028 self._pythondir = None
3029 # True if we had to infer the pythondir from --with-hg
3029 # True if we had to infer the pythondir from --with-hg
3030 self._pythondir_inferred = False
3030 self._pythondir_inferred = False
3031 self._coveragefile = None
3031 self._coveragefile = None
3032 self._createdfiles = []
3032 self._createdfiles = []
3033 self._hgcommand = None
3033 self._hgcommand = None
3034 self._hgpath = None
3034 self._hgpath = None
3035 self._portoffset = 0
3035 self._portoffset = 0
3036 self._ports = {}
3036 self._ports = {}
3037
3037
3038 def run(self, args, parser=None):
3038 def run(self, args, parser=None):
3039 """Run the test suite."""
3039 """Run the test suite."""
3040 oldmask = os.umask(0o22)
3040 oldmask = os.umask(0o22)
3041 try:
3041 try:
3042 parser = parser or getparser()
3042 parser = parser or getparser()
3043 options = parseargs(args, parser)
3043 options = parseargs(args, parser)
3044 tests = [_sys2bytes(a) for a in options.tests]
3044 tests = [_sys2bytes(a) for a in options.tests]
3045 if options.test_list is not None:
3045 if options.test_list is not None:
3046 for listfile in options.test_list:
3046 for listfile in options.test_list:
3047 with open(listfile, 'rb') as f:
3047 with open(listfile, 'rb') as f:
3048 tests.extend(t for t in f.read().splitlines() if t)
3048 tests.extend(t for t in f.read().splitlines() if t)
3049 self.options = options
3049 self.options = options
3050
3050
3051 self._checktools()
3051 self._checktools()
3052 testdescs = self.findtests(tests)
3052 testdescs = self.findtests(tests)
3053 if options.profile_runner:
3053 if options.profile_runner:
3054 import statprof
3054 import statprof
3055
3055
3056 statprof.start()
3056 statprof.start()
3057 result = self._run(testdescs)
3057 result = self._run(testdescs)
3058 if options.profile_runner:
3058 if options.profile_runner:
3059 statprof.stop()
3059 statprof.stop()
3060 statprof.display()
3060 statprof.display()
3061 return result
3061 return result
3062
3062
3063 finally:
3063 finally:
3064 os.umask(oldmask)
3064 os.umask(oldmask)
3065
3065
3066 def _run(self, testdescs):
3066 def _run(self, testdescs):
3067 testdir = getcwdb()
3067 testdir = getcwdb()
3068 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
3068 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
3069 # assume all tests in same folder for now
3069 # assume all tests in same folder for now
3070 if testdescs:
3070 if testdescs:
3071 pathname = os.path.dirname(testdescs[0]['path'])
3071 pathname = os.path.dirname(testdescs[0]['path'])
3072 if pathname:
3072 if pathname:
3073 testdir = os.path.join(testdir, pathname)
3073 testdir = os.path.join(testdir, pathname)
3074 self._testdir = osenvironb[b'TESTDIR'] = testdir
3074 self._testdir = osenvironb[b'TESTDIR'] = testdir
3075 if self.options.outputdir:
3075 if self.options.outputdir:
3076 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3076 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3077 else:
3077 else:
3078 self._outputdir = getcwdb()
3078 self._outputdir = getcwdb()
3079 if testdescs and pathname:
3079 if testdescs and pathname:
3080 self._outputdir = os.path.join(self._outputdir, pathname)
3080 self._outputdir = os.path.join(self._outputdir, pathname)
3081 previoustimes = {}
3081 previoustimes = {}
3082 if self.options.order_by_runtime:
3082 if self.options.order_by_runtime:
3083 previoustimes = dict(loadtimes(self._outputdir))
3083 previoustimes = dict(loadtimes(self._outputdir))
3084 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3084 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3085
3085
3086 if 'PYTHONHASHSEED' not in os.environ:
3086 if 'PYTHONHASHSEED' not in os.environ:
3087 # use a random python hash seed all the time
3087 # use a random python hash seed all the time
3088 # we do the randomness ourself to know what seed is used
3088 # we do the randomness ourself to know what seed is used
3089 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3089 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3090
3090
3091 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3091 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3092 # by default, causing thrashing on high-cpu-count systems.
3092 # by default, causing thrashing on high-cpu-count systems.
3093 # Setting its limit to 3 during tests should still let us uncover
3093 # Setting its limit to 3 during tests should still let us uncover
3094 # multi-threading bugs while keeping the thrashing reasonable.
3094 # multi-threading bugs while keeping the thrashing reasonable.
3095 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3095 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3096
3096
3097 if self.options.tmpdir:
3097 if self.options.tmpdir:
3098 self.options.keep_tmpdir = True
3098 self.options.keep_tmpdir = True
3099 tmpdir = _sys2bytes(self.options.tmpdir)
3099 tmpdir = _sys2bytes(self.options.tmpdir)
3100 if os.path.exists(tmpdir):
3100 if os.path.exists(tmpdir):
3101 # Meaning of tmpdir has changed since 1.3: we used to create
3101 # Meaning of tmpdir has changed since 1.3: we used to create
3102 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3102 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3103 # tmpdir already exists.
3103 # tmpdir already exists.
3104 print("error: temp dir %r already exists" % tmpdir)
3104 print("error: temp dir %r already exists" % tmpdir)
3105 return 1
3105 return 1
3106
3106
3107 os.makedirs(tmpdir)
3107 os.makedirs(tmpdir)
3108 else:
3108 else:
3109 d = None
3109 d = None
3110 if WINDOWS:
3110 if WINDOWS:
3111 # without this, we get the default temp dir location, but
3111 # without this, we get the default temp dir location, but
3112 # in all lowercase, which causes troubles with paths (issue3490)
3112 # in all lowercase, which causes troubles with paths (issue3490)
3113 d = osenvironb.get(b'TMP', None)
3113 d = osenvironb.get(b'TMP', None)
3114 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3114 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3115
3115
3116 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3116 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3117
3117
3118 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3118 self._custom_bin_dir = os.path.join(self._hgtmp, b'custom-bin')
3119 os.makedirs(self._custom_bin_dir)
3119 os.makedirs(self._custom_bin_dir)
3120
3120
3121 if self.options.with_hg:
3121 if self.options.with_hg:
3122 self._installdir = None
3122 self._installdir = None
3123 whg = self.options.with_hg
3123 whg = self.options.with_hg
3124 self._bindir = os.path.dirname(os.path.realpath(whg))
3124 self._bindir = os.path.dirname(os.path.realpath(whg))
3125 assert isinstance(self._bindir, bytes)
3125 assert isinstance(self._bindir, bytes)
3126 self._hgcommand = os.path.basename(whg)
3126 self._hgcommand = os.path.basename(whg)
3127
3127
3128 normbin = os.path.normpath(os.path.abspath(whg))
3128 normbin = os.path.normpath(os.path.abspath(whg))
3129 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3129 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3130
3130
3131 # Other Python scripts in the test harness need to
3131 # Other Python scripts in the test harness need to
3132 # `import mercurial`. If `hg` is a Python script, we assume
3132 # `import mercurial`. If `hg` is a Python script, we assume
3133 # the Mercurial modules are relative to its path and tell the tests
3133 # the Mercurial modules are relative to its path and tell the tests
3134 # to load Python modules from its directory.
3134 # to load Python modules from its directory.
3135 with open(whg, 'rb') as fh:
3135 with open(whg, 'rb') as fh:
3136 initial = fh.read(1024)
3136 initial = fh.read(1024)
3137
3137
3138 if re.match(b'#!.*python', initial):
3138 if re.match(b'#!.*python', initial):
3139 self._pythondir = self._bindir
3139 self._pythondir = self._bindir
3140 # If it looks like our in-repo Rust binary, use the source root.
3140 # If it looks like our in-repo Rust binary, use the source root.
3141 # This is a bit hacky. But rhg is still not supported outside the
3141 # This is a bit hacky. But rhg is still not supported outside the
3142 # source directory. So until it is, do the simple thing.
3142 # source directory. So until it is, do the simple thing.
3143 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3143 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3144 self._pythondir = os.path.dirname(self._testdir)
3144 self._pythondir = os.path.dirname(self._testdir)
3145 # Fall back to the legacy behavior.
3145 # Fall back to the legacy behavior.
3146 else:
3146 else:
3147 self._pythondir = self._bindir
3147 self._pythondir = self._bindir
3148 self._pythondir_inferred = True
3148 self._pythondir_inferred = True
3149
3149
3150 else:
3150 else:
3151 self._installdir = os.path.join(self._hgtmp, b"install")
3151 self._installdir = os.path.join(self._hgtmp, b"install")
3152 self._bindir = os.path.join(self._installdir, b"bin")
3152 self._bindir = os.path.join(self._installdir, b"bin")
3153 self._hgcommand = b'hg'
3153 self._hgcommand = b'hg'
3154 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3154 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3155
3155
3156 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3156 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3157 # a python script and feed it to python.exe. Legacy stdio is force
3157 # a python script and feed it to python.exe. Legacy stdio is force
3158 # enabled by hg.exe, and this is a more realistic way to launch hg
3158 # enabled by hg.exe, and this is a more realistic way to launch hg
3159 # anyway.
3159 # anyway.
3160 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3160 if WINDOWS and not self._hgcommand.endswith(b'.exe'):
3161 self._hgcommand += b'.exe'
3161 self._hgcommand += b'.exe'
3162
3162
3163 real_hg = os.path.join(self._bindir, self._hgcommand)
3164 osenvironb[b'HGTEST_REAL_HG'] = real_hg
3163 # set CHGHG, then replace "hg" command by "chg"
3165 # set CHGHG, then replace "hg" command by "chg"
3164 chgbindir = self._bindir
3166 chgbindir = self._bindir
3165 if self.options.chg or self.options.with_chg:
3167 if self.options.chg or self.options.with_chg:
3166 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3168 osenvironb[b'CHGHG'] = real_hg
3167 else:
3169 else:
3168 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3170 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3169 if self.options.chg:
3171 if self.options.chg:
3170 self._hgcommand = b'chg'
3172 self._hgcommand = b'chg'
3171 elif self.options.with_chg:
3173 elif self.options.with_chg:
3172 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3174 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3173 self._hgcommand = os.path.basename(self.options.with_chg)
3175 self._hgcommand = os.path.basename(self.options.with_chg)
3174
3176
3175 # configure fallback and replace "hg" command by "rhg"
3177 # configure fallback and replace "hg" command by "rhg"
3176 rhgbindir = self._bindir
3178 rhgbindir = self._bindir
3177 if self.options.rhg or self.options.with_rhg:
3179 if self.options.rhg or self.options.with_rhg:
3178 # Affects hghave.py
3180 # Affects hghave.py
3179 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3181 osenvironb[b'RHG_INSTALLED_AS_HG'] = b'1'
3180 # Affects configuration. Alternatives would be setting configuration through
3182 # Affects configuration. Alternatives would be setting configuration through
3181 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3183 # `$HGRCPATH` but some tests override that, or changing `_hgcommand` to include
3182 # `--config` but that disrupts tests that print command lines and check expected
3184 # `--config` but that disrupts tests that print command lines and check expected
3183 # output.
3185 # output.
3184 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3186 osenvironb[b'RHG_ON_UNSUPPORTED'] = b'fallback'
3185 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = os.path.join(
3187 osenvironb[b'RHG_FALLBACK_EXECUTABLE'] = real_hg
3186 self._bindir, self._hgcommand
3187 )
3188 if self.options.rhg:
3188 if self.options.rhg:
3189 self._hgcommand = b'rhg'
3189 self._hgcommand = b'rhg'
3190 elif self.options.with_rhg:
3190 elif self.options.with_rhg:
3191 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3191 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3192 self._hgcommand = os.path.basename(self.options.with_rhg)
3192 self._hgcommand = os.path.basename(self.options.with_rhg)
3193
3193
3194 osenvironb[b"BINDIR"] = self._bindir
3194 osenvironb[b"BINDIR"] = self._bindir
3195 osenvironb[b"PYTHON"] = PYTHON
3195 osenvironb[b"PYTHON"] = PYTHON
3196
3196
3197 fileb = _sys2bytes(__file__)
3197 fileb = _sys2bytes(__file__)
3198 runtestdir = os.path.abspath(os.path.dirname(fileb))
3198 runtestdir = os.path.abspath(os.path.dirname(fileb))
3199 osenvironb[b'RUNTESTDIR'] = runtestdir
3199 osenvironb[b'RUNTESTDIR'] = runtestdir
3200 if PYTHON3:
3200 if PYTHON3:
3201 sepb = _sys2bytes(os.pathsep)
3201 sepb = _sys2bytes(os.pathsep)
3202 else:
3202 else:
3203 sepb = os.pathsep
3203 sepb = os.pathsep
3204 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3204 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3205 if os.path.islink(__file__):
3205 if os.path.islink(__file__):
3206 # test helper will likely be at the end of the symlink
3206 # test helper will likely be at the end of the symlink
3207 realfile = os.path.realpath(fileb)
3207 realfile = os.path.realpath(fileb)
3208 realdir = os.path.abspath(os.path.dirname(realfile))
3208 realdir = os.path.abspath(os.path.dirname(realfile))
3209 path.insert(2, realdir)
3209 path.insert(2, realdir)
3210 if chgbindir != self._bindir:
3210 if chgbindir != self._bindir:
3211 path.insert(1, chgbindir)
3211 path.insert(1, chgbindir)
3212 if rhgbindir != self._bindir:
3212 if rhgbindir != self._bindir:
3213 path.insert(1, rhgbindir)
3213 path.insert(1, rhgbindir)
3214 if self._testdir != runtestdir:
3214 if self._testdir != runtestdir:
3215 path = [self._testdir] + path
3215 path = [self._testdir] + path
3216 path = [self._custom_bin_dir] + path
3216 path = [self._custom_bin_dir] + path
3217 osenvironb[b"PATH"] = sepb.join(path)
3217 osenvironb[b"PATH"] = sepb.join(path)
3218
3218
3219 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3219 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3220 # can run .../tests/run-tests.py test-foo where test-foo
3220 # can run .../tests/run-tests.py test-foo where test-foo
3221 # adds an extension to HGRC. Also include run-test.py directory to
3221 # adds an extension to HGRC. Also include run-test.py directory to
3222 # import modules like heredoctest.
3222 # import modules like heredoctest.
3223 pypath = [self._pythondir, self._testdir, runtestdir]
3223 pypath = [self._pythondir, self._testdir, runtestdir]
3224 # We have to augment PYTHONPATH, rather than simply replacing
3224 # We have to augment PYTHONPATH, rather than simply replacing
3225 # it, in case external libraries are only available via current
3225 # it, in case external libraries are only available via current
3226 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3226 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3227 # are in /opt/subversion.)
3227 # are in /opt/subversion.)
3228 oldpypath = osenvironb.get(IMPL_PATH)
3228 oldpypath = osenvironb.get(IMPL_PATH)
3229 if oldpypath:
3229 if oldpypath:
3230 pypath.append(oldpypath)
3230 pypath.append(oldpypath)
3231 osenvironb[IMPL_PATH] = sepb.join(pypath)
3231 osenvironb[IMPL_PATH] = sepb.join(pypath)
3232
3232
3233 if self.options.pure:
3233 if self.options.pure:
3234 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3234 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3235 os.environ["HGMODULEPOLICY"] = "py"
3235 os.environ["HGMODULEPOLICY"] = "py"
3236 if self.options.rust:
3236 if self.options.rust:
3237 os.environ["HGMODULEPOLICY"] = "rust+c"
3237 os.environ["HGMODULEPOLICY"] = "rust+c"
3238 if self.options.no_rust:
3238 if self.options.no_rust:
3239 current_policy = os.environ.get("HGMODULEPOLICY", "")
3239 current_policy = os.environ.get("HGMODULEPOLICY", "")
3240 if current_policy.startswith("rust+"):
3240 if current_policy.startswith("rust+"):
3241 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3241 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3242 os.environ.pop("HGWITHRUSTEXT", None)
3242 os.environ.pop("HGWITHRUSTEXT", None)
3243
3243
3244 if self.options.allow_slow_tests:
3244 if self.options.allow_slow_tests:
3245 os.environ["HGTEST_SLOW"] = "slow"
3245 os.environ["HGTEST_SLOW"] = "slow"
3246 elif 'HGTEST_SLOW' in os.environ:
3246 elif 'HGTEST_SLOW' in os.environ:
3247 del os.environ['HGTEST_SLOW']
3247 del os.environ['HGTEST_SLOW']
3248
3248
3249 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3249 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3250
3250
3251 if self.options.exceptions:
3251 if self.options.exceptions:
3252 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3252 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3253 try:
3253 try:
3254 os.makedirs(exceptionsdir)
3254 os.makedirs(exceptionsdir)
3255 except OSError as e:
3255 except OSError as e:
3256 if e.errno != errno.EEXIST:
3256 if e.errno != errno.EEXIST:
3257 raise
3257 raise
3258
3258
3259 # Remove all existing exception reports.
3259 # Remove all existing exception reports.
3260 for f in os.listdir(exceptionsdir):
3260 for f in os.listdir(exceptionsdir):
3261 os.unlink(os.path.join(exceptionsdir, f))
3261 os.unlink(os.path.join(exceptionsdir, f))
3262
3262
3263 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3263 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3264 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3264 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3265 self.options.extra_config_opt.append(
3265 self.options.extra_config_opt.append(
3266 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3266 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3267 )
3267 )
3268
3268
3269 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3269 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3270 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3270 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3271 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3271 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3272 vlog("# Using PATH", os.environ["PATH"])
3272 vlog("# Using PATH", os.environ["PATH"])
3273 vlog(
3273 vlog(
3274 "# Using",
3274 "# Using",
3275 _bytes2sys(IMPL_PATH),
3275 _bytes2sys(IMPL_PATH),
3276 _bytes2sys(osenvironb[IMPL_PATH]),
3276 _bytes2sys(osenvironb[IMPL_PATH]),
3277 )
3277 )
3278 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3278 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3279
3279
3280 try:
3280 try:
3281 return self._runtests(testdescs) or 0
3281 return self._runtests(testdescs) or 0
3282 finally:
3282 finally:
3283 time.sleep(0.1)
3283 time.sleep(0.1)
3284 self._cleanup()
3284 self._cleanup()
3285
3285
3286 def findtests(self, args):
3286 def findtests(self, args):
3287 """Finds possible test files from arguments.
3287 """Finds possible test files from arguments.
3288
3288
3289 If you wish to inject custom tests into the test harness, this would
3289 If you wish to inject custom tests into the test harness, this would
3290 be a good function to monkeypatch or override in a derived class.
3290 be a good function to monkeypatch or override in a derived class.
3291 """
3291 """
3292 if not args:
3292 if not args:
3293 if self.options.changed:
3293 if self.options.changed:
3294 proc = Popen4(
3294 proc = Popen4(
3295 b'hg st --rev "%s" -man0 .'
3295 b'hg st --rev "%s" -man0 .'
3296 % _sys2bytes(self.options.changed),
3296 % _sys2bytes(self.options.changed),
3297 None,
3297 None,
3298 0,
3298 0,
3299 )
3299 )
3300 stdout, stderr = proc.communicate()
3300 stdout, stderr = proc.communicate()
3301 args = stdout.strip(b'\0').split(b'\0')
3301 args = stdout.strip(b'\0').split(b'\0')
3302 else:
3302 else:
3303 args = os.listdir(b'.')
3303 args = os.listdir(b'.')
3304
3304
3305 expanded_args = []
3305 expanded_args = []
3306 for arg in args:
3306 for arg in args:
3307 if os.path.isdir(arg):
3307 if os.path.isdir(arg):
3308 if not arg.endswith(b'/'):
3308 if not arg.endswith(b'/'):
3309 arg += b'/'
3309 arg += b'/'
3310 expanded_args.extend([arg + a for a in os.listdir(arg)])
3310 expanded_args.extend([arg + a for a in os.listdir(arg)])
3311 else:
3311 else:
3312 expanded_args.append(arg)
3312 expanded_args.append(arg)
3313 args = expanded_args
3313 args = expanded_args
3314
3314
3315 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3315 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3316 tests = []
3316 tests = []
3317 for t in args:
3317 for t in args:
3318 case = []
3318 case = []
3319
3319
3320 if not (
3320 if not (
3321 os.path.basename(t).startswith(b'test-')
3321 os.path.basename(t).startswith(b'test-')
3322 and (t.endswith(b'.py') or t.endswith(b'.t'))
3322 and (t.endswith(b'.py') or t.endswith(b'.t'))
3323 ):
3323 ):
3324
3324
3325 m = testcasepattern.match(os.path.basename(t))
3325 m = testcasepattern.match(os.path.basename(t))
3326 if m is not None:
3326 if m is not None:
3327 t_basename, casestr = m.groups()
3327 t_basename, casestr = m.groups()
3328 t = os.path.join(os.path.dirname(t), t_basename)
3328 t = os.path.join(os.path.dirname(t), t_basename)
3329 if casestr:
3329 if casestr:
3330 case = casestr.split(b'#')
3330 case = casestr.split(b'#')
3331 else:
3331 else:
3332 continue
3332 continue
3333
3333
3334 if t.endswith(b'.t'):
3334 if t.endswith(b'.t'):
3335 # .t file may contain multiple test cases
3335 # .t file may contain multiple test cases
3336 casedimensions = parsettestcases(t)
3336 casedimensions = parsettestcases(t)
3337 if casedimensions:
3337 if casedimensions:
3338 cases = []
3338 cases = []
3339
3339
3340 def addcases(case, casedimensions):
3340 def addcases(case, casedimensions):
3341 if not casedimensions:
3341 if not casedimensions:
3342 cases.append(case)
3342 cases.append(case)
3343 else:
3343 else:
3344 for c in casedimensions[0]:
3344 for c in casedimensions[0]:
3345 addcases(case + [c], casedimensions[1:])
3345 addcases(case + [c], casedimensions[1:])
3346
3346
3347 addcases([], casedimensions)
3347 addcases([], casedimensions)
3348 if case and case in cases:
3348 if case and case in cases:
3349 cases = [case]
3349 cases = [case]
3350 elif case:
3350 elif case:
3351 # Ignore invalid cases
3351 # Ignore invalid cases
3352 cases = []
3352 cases = []
3353 else:
3353 else:
3354 pass
3354 pass
3355 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3355 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3356 else:
3356 else:
3357 tests.append({'path': t})
3357 tests.append({'path': t})
3358 else:
3358 else:
3359 tests.append({'path': t})
3359 tests.append({'path': t})
3360
3360
3361 if self.options.retest:
3361 if self.options.retest:
3362 retest_args = []
3362 retest_args = []
3363 for test in tests:
3363 for test in tests:
3364 errpath = self._geterrpath(test)
3364 errpath = self._geterrpath(test)
3365 if os.path.exists(errpath):
3365 if os.path.exists(errpath):
3366 retest_args.append(test)
3366 retest_args.append(test)
3367 tests = retest_args
3367 tests = retest_args
3368 return tests
3368 return tests
3369
3369
3370 def _runtests(self, testdescs):
3370 def _runtests(self, testdescs):
3371 def _reloadtest(test, i):
3371 def _reloadtest(test, i):
3372 # convert a test back to its description dict
3372 # convert a test back to its description dict
3373 desc = {'path': test.path}
3373 desc = {'path': test.path}
3374 case = getattr(test, '_case', [])
3374 case = getattr(test, '_case', [])
3375 if case:
3375 if case:
3376 desc['case'] = case
3376 desc['case'] = case
3377 return self._gettest(desc, i)
3377 return self._gettest(desc, i)
3378
3378
3379 try:
3379 try:
3380 if self.options.restart:
3380 if self.options.restart:
3381 orig = list(testdescs)
3381 orig = list(testdescs)
3382 while testdescs:
3382 while testdescs:
3383 desc = testdescs[0]
3383 desc = testdescs[0]
3384 errpath = self._geterrpath(desc)
3384 errpath = self._geterrpath(desc)
3385 if os.path.exists(errpath):
3385 if os.path.exists(errpath):
3386 break
3386 break
3387 testdescs.pop(0)
3387 testdescs.pop(0)
3388 if not testdescs:
3388 if not testdescs:
3389 print("running all tests")
3389 print("running all tests")
3390 testdescs = orig
3390 testdescs = orig
3391
3391
3392 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3392 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3393 num_tests = len(tests) * self.options.runs_per_test
3393 num_tests = len(tests) * self.options.runs_per_test
3394
3394
3395 jobs = min(num_tests, self.options.jobs)
3395 jobs = min(num_tests, self.options.jobs)
3396
3396
3397 failed = False
3397 failed = False
3398 kws = self.options.keywords
3398 kws = self.options.keywords
3399 if kws is not None and PYTHON3:
3399 if kws is not None and PYTHON3:
3400 kws = kws.encode('utf-8')
3400 kws = kws.encode('utf-8')
3401
3401
3402 suite = TestSuite(
3402 suite = TestSuite(
3403 self._testdir,
3403 self._testdir,
3404 jobs=jobs,
3404 jobs=jobs,
3405 whitelist=self.options.whitelisted,
3405 whitelist=self.options.whitelisted,
3406 blacklist=self.options.blacklist,
3406 blacklist=self.options.blacklist,
3407 keywords=kws,
3407 keywords=kws,
3408 loop=self.options.loop,
3408 loop=self.options.loop,
3409 runs_per_test=self.options.runs_per_test,
3409 runs_per_test=self.options.runs_per_test,
3410 showchannels=self.options.showchannels,
3410 showchannels=self.options.showchannels,
3411 tests=tests,
3411 tests=tests,
3412 loadtest=_reloadtest,
3412 loadtest=_reloadtest,
3413 )
3413 )
3414 verbosity = 1
3414 verbosity = 1
3415 if self.options.list_tests:
3415 if self.options.list_tests:
3416 verbosity = 0
3416 verbosity = 0
3417 elif self.options.verbose:
3417 elif self.options.verbose:
3418 verbosity = 2
3418 verbosity = 2
3419 runner = TextTestRunner(self, verbosity=verbosity)
3419 runner = TextTestRunner(self, verbosity=verbosity)
3420
3420
3421 if self.options.list_tests:
3421 if self.options.list_tests:
3422 result = runner.listtests(suite)
3422 result = runner.listtests(suite)
3423 else:
3423 else:
3424 self._usecorrectpython()
3424 self._usecorrectpython()
3425 if self._installdir:
3425 if self._installdir:
3426 self._installhg()
3426 self._installhg()
3427 self._checkhglib("Testing")
3427 self._checkhglib("Testing")
3428 if self.options.chg:
3428 if self.options.chg:
3429 assert self._installdir
3429 assert self._installdir
3430 self._installchg()
3430 self._installchg()
3431 if self.options.rhg:
3431 if self.options.rhg:
3432 assert self._installdir
3432 assert self._installdir
3433 self._installrhg()
3433 self._installrhg()
3434
3434
3435 log(
3435 log(
3436 'running %d tests using %d parallel processes'
3436 'running %d tests using %d parallel processes'
3437 % (num_tests, jobs)
3437 % (num_tests, jobs)
3438 )
3438 )
3439
3439
3440 result = runner.run(suite)
3440 result = runner.run(suite)
3441
3441
3442 if result.failures or result.errors:
3442 if result.failures or result.errors:
3443 failed = True
3443 failed = True
3444
3444
3445 result.onEnd()
3445 result.onEnd()
3446
3446
3447 if self.options.anycoverage:
3447 if self.options.anycoverage:
3448 self._outputcoverage()
3448 self._outputcoverage()
3449 except KeyboardInterrupt:
3449 except KeyboardInterrupt:
3450 failed = True
3450 failed = True
3451 print("\ninterrupted!")
3451 print("\ninterrupted!")
3452
3452
3453 if failed:
3453 if failed:
3454 return 1
3454 return 1
3455
3455
3456 def _geterrpath(self, test):
3456 def _geterrpath(self, test):
3457 # test['path'] is a relative path
3457 # test['path'] is a relative path
3458 if 'case' in test:
3458 if 'case' in test:
3459 # for multiple dimensions test cases
3459 # for multiple dimensions test cases
3460 casestr = b'#'.join(test['case'])
3460 casestr = b'#'.join(test['case'])
3461 errpath = b'%s#%s.err' % (test['path'], casestr)
3461 errpath = b'%s#%s.err' % (test['path'], casestr)
3462 else:
3462 else:
3463 errpath = b'%s.err' % test['path']
3463 errpath = b'%s.err' % test['path']
3464 if self.options.outputdir:
3464 if self.options.outputdir:
3465 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3465 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3466 errpath = os.path.join(self._outputdir, errpath)
3466 errpath = os.path.join(self._outputdir, errpath)
3467 return errpath
3467 return errpath
3468
3468
3469 def _getport(self, count):
3469 def _getport(self, count):
3470 port = self._ports.get(count) # do we have a cached entry?
3470 port = self._ports.get(count) # do we have a cached entry?
3471 if port is None:
3471 if port is None:
3472 portneeded = 3
3472 portneeded = 3
3473 # above 100 tries we just give up and let test reports failure
3473 # above 100 tries we just give up and let test reports failure
3474 for tries in xrange(100):
3474 for tries in xrange(100):
3475 allfree = True
3475 allfree = True
3476 port = self.options.port + self._portoffset
3476 port = self.options.port + self._portoffset
3477 for idx in xrange(portneeded):
3477 for idx in xrange(portneeded):
3478 if not checkportisavailable(port + idx):
3478 if not checkportisavailable(port + idx):
3479 allfree = False
3479 allfree = False
3480 break
3480 break
3481 self._portoffset += portneeded
3481 self._portoffset += portneeded
3482 if allfree:
3482 if allfree:
3483 break
3483 break
3484 self._ports[count] = port
3484 self._ports[count] = port
3485 return port
3485 return port
3486
3486
3487 def _gettest(self, testdesc, count):
3487 def _gettest(self, testdesc, count):
3488 """Obtain a Test by looking at its filename.
3488 """Obtain a Test by looking at its filename.
3489
3489
3490 Returns a Test instance. The Test may not be runnable if it doesn't
3490 Returns a Test instance. The Test may not be runnable if it doesn't
3491 map to a known type.
3491 map to a known type.
3492 """
3492 """
3493 path = testdesc['path']
3493 path = testdesc['path']
3494 lctest = path.lower()
3494 lctest = path.lower()
3495 testcls = Test
3495 testcls = Test
3496
3496
3497 for ext, cls in self.TESTTYPES:
3497 for ext, cls in self.TESTTYPES:
3498 if lctest.endswith(ext):
3498 if lctest.endswith(ext):
3499 testcls = cls
3499 testcls = cls
3500 break
3500 break
3501
3501
3502 refpath = os.path.join(getcwdb(), path)
3502 refpath = os.path.join(getcwdb(), path)
3503 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3503 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3504
3504
3505 # extra keyword parameters. 'case' is used by .t tests
3505 # extra keyword parameters. 'case' is used by .t tests
3506 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3506 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3507
3507
3508 t = testcls(
3508 t = testcls(
3509 refpath,
3509 refpath,
3510 self._outputdir,
3510 self._outputdir,
3511 tmpdir,
3511 tmpdir,
3512 keeptmpdir=self.options.keep_tmpdir,
3512 keeptmpdir=self.options.keep_tmpdir,
3513 debug=self.options.debug,
3513 debug=self.options.debug,
3514 first=self.options.first,
3514 first=self.options.first,
3515 timeout=self.options.timeout,
3515 timeout=self.options.timeout,
3516 startport=self._getport(count),
3516 startport=self._getport(count),
3517 extraconfigopts=self.options.extra_config_opt,
3517 extraconfigopts=self.options.extra_config_opt,
3518 shell=self.options.shell,
3518 shell=self.options.shell,
3519 hgcommand=self._hgcommand,
3519 hgcommand=self._hgcommand,
3520 usechg=bool(self.options.with_chg or self.options.chg),
3520 usechg=bool(self.options.with_chg or self.options.chg),
3521 chgdebug=self.options.chg_debug,
3521 chgdebug=self.options.chg_debug,
3522 useipv6=useipv6,
3522 useipv6=useipv6,
3523 **kwds
3523 **kwds
3524 )
3524 )
3525 t.should_reload = True
3525 t.should_reload = True
3526 return t
3526 return t
3527
3527
3528 def _cleanup(self):
3528 def _cleanup(self):
3529 """Clean up state from this test invocation."""
3529 """Clean up state from this test invocation."""
3530 if self.options.keep_tmpdir:
3530 if self.options.keep_tmpdir:
3531 return
3531 return
3532
3532
3533 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3533 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3534 shutil.rmtree(self._hgtmp, True)
3534 shutil.rmtree(self._hgtmp, True)
3535 for f in self._createdfiles:
3535 for f in self._createdfiles:
3536 try:
3536 try:
3537 os.remove(f)
3537 os.remove(f)
3538 except OSError:
3538 except OSError:
3539 pass
3539 pass
3540
3540
3541 def _usecorrectpython(self):
3541 def _usecorrectpython(self):
3542 """Configure the environment to use the appropriate Python in tests."""
3542 """Configure the environment to use the appropriate Python in tests."""
3543 # Tests must use the same interpreter as us or bad things will happen.
3543 # Tests must use the same interpreter as us or bad things will happen.
3544 if sys.platform == 'win32':
3544 if sys.platform == 'win32':
3545 pyexe_names = [b'python', b'python.exe']
3545 pyexe_names = [b'python', b'python.exe']
3546 elif sys.version_info[0] < 3:
3546 elif sys.version_info[0] < 3:
3547 pyexe_names = [b'python', b'python2']
3547 pyexe_names = [b'python', b'python2']
3548 else:
3548 else:
3549 pyexe_names = [b'python', b'python3']
3549 pyexe_names = [b'python', b'python3']
3550
3550
3551 # os.symlink() is a thing with py3 on Windows, but it requires
3551 # os.symlink() is a thing with py3 on Windows, but it requires
3552 # Administrator rights.
3552 # Administrator rights.
3553 if getattr(os, 'symlink', None) and not WINDOWS:
3553 if getattr(os, 'symlink', None) and not WINDOWS:
3554 msg = "# Making python executable in test path a symlink to '%s'"
3554 msg = "# Making python executable in test path a symlink to '%s'"
3555 msg %= sysexecutable
3555 msg %= sysexecutable
3556 vlog(msg)
3556 vlog(msg)
3557 for pyexename in pyexe_names:
3557 for pyexename in pyexe_names:
3558 mypython = os.path.join(self._custom_bin_dir, pyexename)
3558 mypython = os.path.join(self._custom_bin_dir, pyexename)
3559 try:
3559 try:
3560 if os.readlink(mypython) == sysexecutable:
3560 if os.readlink(mypython) == sysexecutable:
3561 continue
3561 continue
3562 os.unlink(mypython)
3562 os.unlink(mypython)
3563 except OSError as err:
3563 except OSError as err:
3564 if err.errno != errno.ENOENT:
3564 if err.errno != errno.ENOENT:
3565 raise
3565 raise
3566 if self._findprogram(pyexename) != sysexecutable:
3566 if self._findprogram(pyexename) != sysexecutable:
3567 try:
3567 try:
3568 os.symlink(sysexecutable, mypython)
3568 os.symlink(sysexecutable, mypython)
3569 self._createdfiles.append(mypython)
3569 self._createdfiles.append(mypython)
3570 except OSError as err:
3570 except OSError as err:
3571 # child processes may race, which is harmless
3571 # child processes may race, which is harmless
3572 if err.errno != errno.EEXIST:
3572 if err.errno != errno.EEXIST:
3573 raise
3573 raise
3574 else:
3574 else:
3575 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3575 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3576 # reparse point with that name provided by Microsoft. Create a
3576 # reparse point with that name provided by Microsoft. Create a
3577 # simple script on PATH with that name that delegates to the py3
3577 # simple script on PATH with that name that delegates to the py3
3578 # launcher so the shebang lines work.
3578 # launcher so the shebang lines work.
3579 if os.getenv('MSYSTEM'):
3579 if os.getenv('MSYSTEM'):
3580 py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
3580 py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
3581 with open(py3exe_name, 'wb') as f:
3581 with open(py3exe_name, 'wb') as f:
3582 f.write(b'#!/bin/sh\n')
3582 f.write(b'#!/bin/sh\n')
3583 f.write(b'py -3.%d "$@"\n' % sys.version_info[1])
3583 f.write(b'py -3.%d "$@"\n' % sys.version_info[1])
3584
3584
3585 pyexe_name = os.path.join(self._custom_bin_dir, b'python')
3585 pyexe_name = os.path.join(self._custom_bin_dir, b'python')
3586 with open(pyexe_name, 'wb') as f:
3586 with open(pyexe_name, 'wb') as f:
3587 f.write(b'#!/bin/sh\n')
3587 f.write(b'#!/bin/sh\n')
3588 f.write(b'py -%d.%d "$@"\n' % sys.version_info[0:2])
3588 f.write(b'py -%d.%d "$@"\n' % sys.version_info[0:2])
3589
3589
3590 exedir, exename = os.path.split(sysexecutable)
3590 exedir, exename = os.path.split(sysexecutable)
3591 for pyexename in pyexe_names:
3591 for pyexename in pyexe_names:
3592 msg = "# Modifying search path to find %s as %s in '%s'"
3592 msg = "# Modifying search path to find %s as %s in '%s'"
3593 msg %= (exename, pyexename, exedir)
3593 msg %= (exename, pyexename, exedir)
3594 vlog(msg)
3594 vlog(msg)
3595 path = os.environ['PATH'].split(os.pathsep)
3595 path = os.environ['PATH'].split(os.pathsep)
3596 while exedir in path:
3596 while exedir in path:
3597 path.remove(exedir)
3597 path.remove(exedir)
3598
3598
3599 # Binaries installed by pip into the user area like pylint.exe may
3599 # Binaries installed by pip into the user area like pylint.exe may
3600 # not be in PATH by default.
3600 # not be in PATH by default.
3601 extra_paths = [exedir]
3601 extra_paths = [exedir]
3602 vi = sys.version_info
3602 vi = sys.version_info
3603 appdata = os.environ.get('APPDATA')
3603 appdata = os.environ.get('APPDATA')
3604 if appdata is not None:
3604 if appdata is not None:
3605 scripts_dir = os.path.join(
3605 scripts_dir = os.path.join(
3606 appdata,
3606 appdata,
3607 'Python',
3607 'Python',
3608 'Python%d%d' % (vi[0], vi[1]),
3608 'Python%d%d' % (vi[0], vi[1]),
3609 'Scripts',
3609 'Scripts',
3610 )
3610 )
3611
3611
3612 if vi.major == 2:
3612 if vi.major == 2:
3613 scripts_dir = os.path.join(
3613 scripts_dir = os.path.join(
3614 appdata,
3614 appdata,
3615 'Python',
3615 'Python',
3616 'Scripts',
3616 'Scripts',
3617 )
3617 )
3618
3618
3619 extra_paths.append(scripts_dir)
3619 extra_paths.append(scripts_dir)
3620
3620
3621 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3621 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3622 for pyexename in pyexe_names:
3622 for pyexename in pyexe_names:
3623 if not self._findprogram(pyexename):
3623 if not self._findprogram(pyexename):
3624 print("WARNING: Cannot find %s in search path" % pyexename)
3624 print("WARNING: Cannot find %s in search path" % pyexename)
3625
3625
3626 def _installhg(self):
3626 def _installhg(self):
3627 """Install hg into the test environment.
3627 """Install hg into the test environment.
3628
3628
3629 This will also configure hg with the appropriate testing settings.
3629 This will also configure hg with the appropriate testing settings.
3630 """
3630 """
3631 vlog("# Performing temporary installation of HG")
3631 vlog("# Performing temporary installation of HG")
3632 installerrs = os.path.join(self._hgtmp, b"install.err")
3632 installerrs = os.path.join(self._hgtmp, b"install.err")
3633 compiler = ''
3633 compiler = ''
3634 if self.options.compiler:
3634 if self.options.compiler:
3635 compiler = '--compiler ' + self.options.compiler
3635 compiler = '--compiler ' + self.options.compiler
3636 setup_opts = b""
3636 setup_opts = b""
3637 if self.options.pure:
3637 if self.options.pure:
3638 setup_opts = b"--pure"
3638 setup_opts = b"--pure"
3639 elif self.options.rust:
3639 elif self.options.rust:
3640 setup_opts = b"--rust"
3640 setup_opts = b"--rust"
3641 elif self.options.no_rust:
3641 elif self.options.no_rust:
3642 setup_opts = b"--no-rust"
3642 setup_opts = b"--no-rust"
3643
3643
3644 # Run installer in hg root
3644 # Run installer in hg root
3645 script = os.path.realpath(sys.argv[0])
3645 script = os.path.realpath(sys.argv[0])
3646 exe = sysexecutable
3646 exe = sysexecutable
3647 if PYTHON3:
3647 if PYTHON3:
3648 compiler = _sys2bytes(compiler)
3648 compiler = _sys2bytes(compiler)
3649 script = _sys2bytes(script)
3649 script = _sys2bytes(script)
3650 exe = _sys2bytes(exe)
3650 exe = _sys2bytes(exe)
3651 hgroot = os.path.dirname(os.path.dirname(script))
3651 hgroot = os.path.dirname(os.path.dirname(script))
3652 self._hgroot = hgroot
3652 self._hgroot = hgroot
3653 os.chdir(hgroot)
3653 os.chdir(hgroot)
3654 nohome = b'--home=""'
3654 nohome = b'--home=""'
3655 if WINDOWS:
3655 if WINDOWS:
3656 # The --home="" trick works only on OS where os.sep == '/'
3656 # The --home="" trick works only on OS where os.sep == '/'
3657 # because of a distutils convert_path() fast-path. Avoid it at
3657 # because of a distutils convert_path() fast-path. Avoid it at
3658 # least on Windows for now, deal with .pydistutils.cfg bugs
3658 # least on Windows for now, deal with .pydistutils.cfg bugs
3659 # when they happen.
3659 # when they happen.
3660 nohome = b''
3660 nohome = b''
3661 cmd = (
3661 cmd = (
3662 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3662 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3663 b' build %(compiler)s --build-base="%(base)s"'
3663 b' build %(compiler)s --build-base="%(base)s"'
3664 b' install --force --prefix="%(prefix)s"'
3664 b' install --force --prefix="%(prefix)s"'
3665 b' --install-lib="%(libdir)s"'
3665 b' --install-lib="%(libdir)s"'
3666 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3666 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3667 % {
3667 % {
3668 b'exe': exe,
3668 b'exe': exe,
3669 b'setup_opts': setup_opts,
3669 b'setup_opts': setup_opts,
3670 b'compiler': compiler,
3670 b'compiler': compiler,
3671 b'base': os.path.join(self._hgtmp, b"build"),
3671 b'base': os.path.join(self._hgtmp, b"build"),
3672 b'prefix': self._installdir,
3672 b'prefix': self._installdir,
3673 b'libdir': self._pythondir,
3673 b'libdir': self._pythondir,
3674 b'bindir': self._bindir,
3674 b'bindir': self._bindir,
3675 b'nohome': nohome,
3675 b'nohome': nohome,
3676 b'logfile': installerrs,
3676 b'logfile': installerrs,
3677 }
3677 }
3678 )
3678 )
3679
3679
3680 # setuptools requires install directories to exist.
3680 # setuptools requires install directories to exist.
3681 def makedirs(p):
3681 def makedirs(p):
3682 try:
3682 try:
3683 os.makedirs(p)
3683 os.makedirs(p)
3684 except OSError as e:
3684 except OSError as e:
3685 if e.errno != errno.EEXIST:
3685 if e.errno != errno.EEXIST:
3686 raise
3686 raise
3687
3687
3688 makedirs(self._pythondir)
3688 makedirs(self._pythondir)
3689 makedirs(self._bindir)
3689 makedirs(self._bindir)
3690
3690
3691 vlog("# Running", cmd.decode("utf-8"))
3691 vlog("# Running", cmd.decode("utf-8"))
3692 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3692 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3693 if not self.options.verbose:
3693 if not self.options.verbose:
3694 try:
3694 try:
3695 os.remove(installerrs)
3695 os.remove(installerrs)
3696 except OSError as e:
3696 except OSError as e:
3697 if e.errno != errno.ENOENT:
3697 if e.errno != errno.ENOENT:
3698 raise
3698 raise
3699 else:
3699 else:
3700 with open(installerrs, 'rb') as f:
3700 with open(installerrs, 'rb') as f:
3701 for line in f:
3701 for line in f:
3702 if PYTHON3:
3702 if PYTHON3:
3703 sys.stdout.buffer.write(line)
3703 sys.stdout.buffer.write(line)
3704 else:
3704 else:
3705 sys.stdout.write(line)
3705 sys.stdout.write(line)
3706 sys.exit(1)
3706 sys.exit(1)
3707 os.chdir(self._testdir)
3707 os.chdir(self._testdir)
3708
3708
3709 hgbat = os.path.join(self._bindir, b'hg.bat')
3709 hgbat = os.path.join(self._bindir, b'hg.bat')
3710 if os.path.isfile(hgbat):
3710 if os.path.isfile(hgbat):
3711 # hg.bat expects to be put in bin/scripts while run-tests.py
3711 # hg.bat expects to be put in bin/scripts while run-tests.py
3712 # installation layout put it in bin/ directly. Fix it
3712 # installation layout put it in bin/ directly. Fix it
3713 with open(hgbat, 'rb') as f:
3713 with open(hgbat, 'rb') as f:
3714 data = f.read()
3714 data = f.read()
3715 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3715 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3716 data = data.replace(
3716 data = data.replace(
3717 br'"%~dp0..\python" "%~dp0hg" %*',
3717 br'"%~dp0..\python" "%~dp0hg" %*',
3718 b'"%~dp0python" "%~dp0hg" %*',
3718 b'"%~dp0python" "%~dp0hg" %*',
3719 )
3719 )
3720 with open(hgbat, 'wb') as f:
3720 with open(hgbat, 'wb') as f:
3721 f.write(data)
3721 f.write(data)
3722 else:
3722 else:
3723 print('WARNING: cannot fix hg.bat reference to python.exe')
3723 print('WARNING: cannot fix hg.bat reference to python.exe')
3724
3724
3725 if self.options.anycoverage:
3725 if self.options.anycoverage:
3726 custom = os.path.join(
3726 custom = os.path.join(
3727 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3727 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3728 )
3728 )
3729 target = os.path.join(self._pythondir, b'sitecustomize.py')
3729 target = os.path.join(self._pythondir, b'sitecustomize.py')
3730 vlog('# Installing coverage trigger to %s' % target)
3730 vlog('# Installing coverage trigger to %s' % target)
3731 shutil.copyfile(custom, target)
3731 shutil.copyfile(custom, target)
3732 rc = os.path.join(self._testdir, b'.coveragerc')
3732 rc = os.path.join(self._testdir, b'.coveragerc')
3733 vlog('# Installing coverage rc to %s' % rc)
3733 vlog('# Installing coverage rc to %s' % rc)
3734 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3734 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3735 covdir = os.path.join(self._installdir, b'..', b'coverage')
3735 covdir = os.path.join(self._installdir, b'..', b'coverage')
3736 try:
3736 try:
3737 os.mkdir(covdir)
3737 os.mkdir(covdir)
3738 except OSError as e:
3738 except OSError as e:
3739 if e.errno != errno.EEXIST:
3739 if e.errno != errno.EEXIST:
3740 raise
3740 raise
3741
3741
3742 osenvironb[b'COVERAGE_DIR'] = covdir
3742 osenvironb[b'COVERAGE_DIR'] = covdir
3743
3743
3744 def _checkhglib(self, verb):
3744 def _checkhglib(self, verb):
3745 """Ensure that the 'mercurial' package imported by python is
3745 """Ensure that the 'mercurial' package imported by python is
3746 the one we expect it to be. If not, print a warning to stderr."""
3746 the one we expect it to be. If not, print a warning to stderr."""
3747 if self._pythondir_inferred:
3747 if self._pythondir_inferred:
3748 # The pythondir has been inferred from --with-hg flag.
3748 # The pythondir has been inferred from --with-hg flag.
3749 # We cannot expect anything sensible here.
3749 # We cannot expect anything sensible here.
3750 return
3750 return
3751 expecthg = os.path.join(self._pythondir, b'mercurial')
3751 expecthg = os.path.join(self._pythondir, b'mercurial')
3752 actualhg = self._gethgpath()
3752 actualhg = self._gethgpath()
3753 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3753 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3754 sys.stderr.write(
3754 sys.stderr.write(
3755 'warning: %s with unexpected mercurial lib: %s\n'
3755 'warning: %s with unexpected mercurial lib: %s\n'
3756 ' (expected %s)\n' % (verb, actualhg, expecthg)
3756 ' (expected %s)\n' % (verb, actualhg, expecthg)
3757 )
3757 )
3758
3758
3759 def _gethgpath(self):
3759 def _gethgpath(self):
3760 """Return the path to the mercurial package that is actually found by
3760 """Return the path to the mercurial package that is actually found by
3761 the current Python interpreter."""
3761 the current Python interpreter."""
3762 if self._hgpath is not None:
3762 if self._hgpath is not None:
3763 return self._hgpath
3763 return self._hgpath
3764
3764
3765 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3765 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3766 cmd = cmd % PYTHON
3766 cmd = cmd % PYTHON
3767 if PYTHON3:
3767 if PYTHON3:
3768 cmd = _bytes2sys(cmd)
3768 cmd = _bytes2sys(cmd)
3769
3769
3770 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3770 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3771 out, err = p.communicate()
3771 out, err = p.communicate()
3772
3772
3773 self._hgpath = out.strip()
3773 self._hgpath = out.strip()
3774
3774
3775 return self._hgpath
3775 return self._hgpath
3776
3776
3777 def _installchg(self):
3777 def _installchg(self):
3778 """Install chg into the test environment"""
3778 """Install chg into the test environment"""
3779 vlog('# Performing temporary installation of CHG')
3779 vlog('# Performing temporary installation of CHG')
3780 assert os.path.dirname(self._bindir) == self._installdir
3780 assert os.path.dirname(self._bindir) == self._installdir
3781 assert self._hgroot, 'must be called after _installhg()'
3781 assert self._hgroot, 'must be called after _installhg()'
3782 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3782 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3783 b'make': b'make', # TODO: switch by option or environment?
3783 b'make': b'make', # TODO: switch by option or environment?
3784 b'prefix': self._installdir,
3784 b'prefix': self._installdir,
3785 }
3785 }
3786 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3786 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3787 vlog("# Running", cmd)
3787 vlog("# Running", cmd)
3788 proc = subprocess.Popen(
3788 proc = subprocess.Popen(
3789 cmd,
3789 cmd,
3790 shell=True,
3790 shell=True,
3791 cwd=cwd,
3791 cwd=cwd,
3792 stdin=subprocess.PIPE,
3792 stdin=subprocess.PIPE,
3793 stdout=subprocess.PIPE,
3793 stdout=subprocess.PIPE,
3794 stderr=subprocess.STDOUT,
3794 stderr=subprocess.STDOUT,
3795 )
3795 )
3796 out, _err = proc.communicate()
3796 out, _err = proc.communicate()
3797 if proc.returncode != 0:
3797 if proc.returncode != 0:
3798 if PYTHON3:
3798 if PYTHON3:
3799 sys.stdout.buffer.write(out)
3799 sys.stdout.buffer.write(out)
3800 else:
3800 else:
3801 sys.stdout.write(out)
3801 sys.stdout.write(out)
3802 sys.exit(1)
3802 sys.exit(1)
3803
3803
3804 def _installrhg(self):
3804 def _installrhg(self):
3805 """Install rhg into the test environment"""
3805 """Install rhg into the test environment"""
3806 vlog('# Performing temporary installation of rhg')
3806 vlog('# Performing temporary installation of rhg')
3807 assert os.path.dirname(self._bindir) == self._installdir
3807 assert os.path.dirname(self._bindir) == self._installdir
3808 assert self._hgroot, 'must be called after _installhg()'
3808 assert self._hgroot, 'must be called after _installhg()'
3809 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3809 cmd = b'"%(make)s" install-rhg PREFIX="%(prefix)s"' % {
3810 b'make': b'make', # TODO: switch by option or environment?
3810 b'make': b'make', # TODO: switch by option or environment?
3811 b'prefix': self._installdir,
3811 b'prefix': self._installdir,
3812 }
3812 }
3813 cwd = self._hgroot
3813 cwd = self._hgroot
3814 vlog("# Running", cmd)
3814 vlog("# Running", cmd)
3815 proc = subprocess.Popen(
3815 proc = subprocess.Popen(
3816 cmd,
3816 cmd,
3817 shell=True,
3817 shell=True,
3818 cwd=cwd,
3818 cwd=cwd,
3819 stdin=subprocess.PIPE,
3819 stdin=subprocess.PIPE,
3820 stdout=subprocess.PIPE,
3820 stdout=subprocess.PIPE,
3821 stderr=subprocess.STDOUT,
3821 stderr=subprocess.STDOUT,
3822 )
3822 )
3823 out, _err = proc.communicate()
3823 out, _err = proc.communicate()
3824 if proc.returncode != 0:
3824 if proc.returncode != 0:
3825 if PYTHON3:
3825 if PYTHON3:
3826 sys.stdout.buffer.write(out)
3826 sys.stdout.buffer.write(out)
3827 else:
3827 else:
3828 sys.stdout.write(out)
3828 sys.stdout.write(out)
3829 sys.exit(1)
3829 sys.exit(1)
3830
3830
3831 def _outputcoverage(self):
3831 def _outputcoverage(self):
3832 """Produce code coverage output."""
3832 """Produce code coverage output."""
3833 import coverage
3833 import coverage
3834
3834
3835 coverage = coverage.coverage
3835 coverage = coverage.coverage
3836
3836
3837 vlog('# Producing coverage report')
3837 vlog('# Producing coverage report')
3838 # chdir is the easiest way to get short, relative paths in the
3838 # chdir is the easiest way to get short, relative paths in the
3839 # output.
3839 # output.
3840 os.chdir(self._hgroot)
3840 os.chdir(self._hgroot)
3841 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3841 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3842 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3842 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3843
3843
3844 # Map install directory paths back to source directory.
3844 # Map install directory paths back to source directory.
3845 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3845 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3846
3846
3847 cov.combine()
3847 cov.combine()
3848
3848
3849 omit = [
3849 omit = [
3850 _bytes2sys(os.path.join(x, b'*'))
3850 _bytes2sys(os.path.join(x, b'*'))
3851 for x in [self._bindir, self._testdir]
3851 for x in [self._bindir, self._testdir]
3852 ]
3852 ]
3853 cov.report(ignore_errors=True, omit=omit)
3853 cov.report(ignore_errors=True, omit=omit)
3854
3854
3855 if self.options.htmlcov:
3855 if self.options.htmlcov:
3856 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3856 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3857 cov.html_report(directory=htmldir, omit=omit)
3857 cov.html_report(directory=htmldir, omit=omit)
3858 if self.options.annotate:
3858 if self.options.annotate:
3859 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3859 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3860 if not os.path.isdir(adir):
3860 if not os.path.isdir(adir):
3861 os.mkdir(adir)
3861 os.mkdir(adir)
3862 cov.annotate(directory=adir, omit=omit)
3862 cov.annotate(directory=adir, omit=omit)
3863
3863
3864 def _findprogram(self, program):
3864 def _findprogram(self, program):
3865 """Search PATH for a executable program"""
3865 """Search PATH for a executable program"""
3866 dpb = _sys2bytes(os.defpath)
3866 dpb = _sys2bytes(os.defpath)
3867 sepb = _sys2bytes(os.pathsep)
3867 sepb = _sys2bytes(os.pathsep)
3868 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3868 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3869 name = os.path.join(p, program)
3869 name = os.path.join(p, program)
3870 if WINDOWS or os.access(name, os.X_OK):
3870 if WINDOWS or os.access(name, os.X_OK):
3871 return _bytes2sys(name)
3871 return _bytes2sys(name)
3872 return None
3872 return None
3873
3873
3874 def _checktools(self):
3874 def _checktools(self):
3875 """Ensure tools required to run tests are present."""
3875 """Ensure tools required to run tests are present."""
3876 for p in self.REQUIREDTOOLS:
3876 for p in self.REQUIREDTOOLS:
3877 if WINDOWS and not p.endswith(b'.exe'):
3877 if WINDOWS and not p.endswith(b'.exe'):
3878 p += b'.exe'
3878 p += b'.exe'
3879 found = self._findprogram(p)
3879 found = self._findprogram(p)
3880 p = p.decode("utf-8")
3880 p = p.decode("utf-8")
3881 if found:
3881 if found:
3882 vlog("# Found prerequisite", p, "at", found)
3882 vlog("# Found prerequisite", p, "at", found)
3883 else:
3883 else:
3884 print("WARNING: Did not find prerequisite tool: %s " % p)
3884 print("WARNING: Did not find prerequisite tool: %s " % p)
3885
3885
3886
3886
3887 def aggregateexceptions(path):
3887 def aggregateexceptions(path):
3888 exceptioncounts = collections.Counter()
3888 exceptioncounts = collections.Counter()
3889 testsbyfailure = collections.defaultdict(set)
3889 testsbyfailure = collections.defaultdict(set)
3890 failuresbytest = collections.defaultdict(set)
3890 failuresbytest = collections.defaultdict(set)
3891
3891
3892 for f in os.listdir(path):
3892 for f in os.listdir(path):
3893 with open(os.path.join(path, f), 'rb') as fh:
3893 with open(os.path.join(path, f), 'rb') as fh:
3894 data = fh.read().split(b'\0')
3894 data = fh.read().split(b'\0')
3895 if len(data) != 5:
3895 if len(data) != 5:
3896 continue
3896 continue
3897
3897
3898 exc, mainframe, hgframe, hgline, testname = data
3898 exc, mainframe, hgframe, hgline, testname = data
3899 exc = exc.decode('utf-8')
3899 exc = exc.decode('utf-8')
3900 mainframe = mainframe.decode('utf-8')
3900 mainframe = mainframe.decode('utf-8')
3901 hgframe = hgframe.decode('utf-8')
3901 hgframe = hgframe.decode('utf-8')
3902 hgline = hgline.decode('utf-8')
3902 hgline = hgline.decode('utf-8')
3903 testname = testname.decode('utf-8')
3903 testname = testname.decode('utf-8')
3904
3904
3905 key = (hgframe, hgline, exc)
3905 key = (hgframe, hgline, exc)
3906 exceptioncounts[key] += 1
3906 exceptioncounts[key] += 1
3907 testsbyfailure[key].add(testname)
3907 testsbyfailure[key].add(testname)
3908 failuresbytest[testname].add(key)
3908 failuresbytest[testname].add(key)
3909
3909
3910 # Find test having fewest failures for each failure.
3910 # Find test having fewest failures for each failure.
3911 leastfailing = {}
3911 leastfailing = {}
3912 for key, tests in testsbyfailure.items():
3912 for key, tests in testsbyfailure.items():
3913 fewesttest = None
3913 fewesttest = None
3914 fewestcount = 99999999
3914 fewestcount = 99999999
3915 for test in sorted(tests):
3915 for test in sorted(tests):
3916 if len(failuresbytest[test]) < fewestcount:
3916 if len(failuresbytest[test]) < fewestcount:
3917 fewesttest = test
3917 fewesttest = test
3918 fewestcount = len(failuresbytest[test])
3918 fewestcount = len(failuresbytest[test])
3919
3919
3920 leastfailing[key] = (fewestcount, fewesttest)
3920 leastfailing[key] = (fewestcount, fewesttest)
3921
3921
3922 # Create a combined counter so we can sort by total occurrences and
3922 # Create a combined counter so we can sort by total occurrences and
3923 # impacted tests.
3923 # impacted tests.
3924 combined = {}
3924 combined = {}
3925 for key in exceptioncounts:
3925 for key in exceptioncounts:
3926 combined[key] = (
3926 combined[key] = (
3927 exceptioncounts[key],
3927 exceptioncounts[key],
3928 len(testsbyfailure[key]),
3928 len(testsbyfailure[key]),
3929 leastfailing[key][0],
3929 leastfailing[key][0],
3930 leastfailing[key][1],
3930 leastfailing[key][1],
3931 )
3931 )
3932
3932
3933 return {
3933 return {
3934 'exceptioncounts': exceptioncounts,
3934 'exceptioncounts': exceptioncounts,
3935 'total': sum(exceptioncounts.values()),
3935 'total': sum(exceptioncounts.values()),
3936 'combined': combined,
3936 'combined': combined,
3937 'leastfailing': leastfailing,
3937 'leastfailing': leastfailing,
3938 'byfailure': testsbyfailure,
3938 'byfailure': testsbyfailure,
3939 'bytest': failuresbytest,
3939 'bytest': failuresbytest,
3940 }
3940 }
3941
3941
3942
3942
3943 if __name__ == '__main__':
3943 if __name__ == '__main__':
3944 runner = TestRunner()
3944 runner = TestRunner()
3945
3945
3946 try:
3946 try:
3947 import msvcrt
3947 import msvcrt
3948
3948
3949 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3949 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3950 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3950 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3951 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3951 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3952 except ImportError:
3952 except ImportError:
3953 pass
3953 pass
3954
3954
3955 sys.exit(runner.run(sys.argv[1:]))
3955 sys.exit(runner.run(sys.argv[1:]))
@@ -1,46 +1,46
1 $ . "$TESTDIR/helpers-testrepo.sh"
1 $ . "$TESTDIR/helpers-testrepo.sh"
2
2
3 Testing that hghave does not crash when checking features
3 Testing that hghave does not crash when checking features
4
4
5 $ hghave --test-features 2>/dev/null
5 $ hghave --test-features 2>/dev/null
6
6
7 Testing hghave extensibility for third party tools
7 Testing hghave extensibility for third party tools
8
8
9 $ cat > hghaveaddon.py <<EOF
9 $ cat > hghaveaddon.py <<EOF
10 > import hghave
10 > import hghave
11 > @hghave.check("custom", "custom hghave feature")
11 > @hghave.check("custom", "custom hghave feature")
12 > def has_custom():
12 > def has_custom():
13 > return True
13 > return True
14 > EOF
14 > EOF
15
15
16 (invocation via run-tests.py)
16 (invocation via run-tests.py)
17
17
18 $ cat > test-hghaveaddon.t <<EOF
18 $ cat > test-hghaveaddon.t <<EOF
19 > #require custom
19 > #require custom
20 > $ echo foo
20 > $ echo foo
21 > foo
21 > foo
22 > EOF
22 > EOF
23 $ ( \
23 $ ( \
24 > testrepohgenv; \
24 > testrepohgenv; \
25 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j 1 \
25 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j 1 \
26 > $HGTEST_RUN_TESTS_PURE test-hghaveaddon.t \
26 > $HGTEST_RUN_TESTS_PURE test-hghaveaddon.t \
27 > )
27 > )
28 running 1 tests using 1 parallel processes
28 running 1 tests using 1 parallel processes
29 .
29 .
30 # Ran 1 tests, 0 skipped, 0 failed.
30 # Ran 1 tests, 0 skipped, 0 failed.
31
31
32 (invocation via command line)
32 (invocation via command line)
33
33
34 $ unset TESTDIR
34 $ unset TESTDIR
35 $ hghave custom
35 $ hghave custom
36
36
37 (terminate with exit code 2 at failure of importing hghaveaddon.py)
37 (terminate with exit code 2 at failure of importing hghaveaddon.py)
38
38
39 $ rm hghaveaddon.*
39 $ rm hghaveaddon.*
40 $ cat > hghaveaddon.py <<NO_CHECK_EOF
40 $ cat > hghaveaddon.py <<NO_CHECK_EOF
41 > importing this file should cause syntax error
41 > importing this file should cause syntax error
42 > NO_CHECK_EOF
42 > NO_CHECK_EOF
43
43
44 $ hghave custom
44 $ hghave custom
45 failed to import hghaveaddon.py from '.': invalid syntax (hghaveaddon.py, line 1)
45 failed to import hghaveaddon.py from '.': invalid syntax (hghaveaddon.py, line 1)
46 [2]
46 [2]
@@ -1,2087 +1,2087
1 This file tests the behavior of run-tests.py itself.
1 This file tests the behavior of run-tests.py itself.
2
2
3 Avoid interference from actual test env:
3 Avoid interference from actual test env:
4
4
5 $ . "$TESTDIR/helper-runtests.sh"
5 $ . "$TESTDIR/helper-runtests.sh"
6
6
7 Smoke test with install
7 Smoke test with install
8 ============
8 ============
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
10 running 0 tests using 0 parallel processes
10 running 0 tests using 0 parallel processes
11
11
12 # Ran 0 tests, 0 skipped, 0 failed.
12 # Ran 0 tests, 0 skipped, 0 failed.
13
13
14 Define a helper to avoid the install step
14 Define a helper to avoid the install step
15 =============
15 =============
16 $ rt()
16 $ rt()
17 > {
17 > {
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@"
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=$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 `which true` hg
24 $ ln -s `which true` hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
26 warning: --with-hg should specify an hg script, not: true
26 warning: --with-hg should specify an hg script, not: true
27 running 0 tests using 0 parallel processes
27 running 0 tests using 0 parallel processes
28
28
29 # Ran 0 tests, 0 skipped, 0 failed.
29 # Ran 0 tests, 0 skipped, 0 failed.
30 $ rm hg
30 $ rm hg
31 #endif
31 #endif
32
32
33 #if execbit
33 #if execbit
34 $ touch hg
34 $ touch hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
36 usage: run-tests.py [options] [tests]
36 usage: run-tests.py [options] [tests]
37 run-tests.py: error: --with-hg must specify an executable hg script
37 run-tests.py: error: --with-hg must specify an executable hg script
38 [2]
38 [2]
39 $ rm hg
39 $ rm hg
40 #endif
40 #endif
41
41
42 Features for testing optional lines
42 Features for testing optional lines
43 ===================================
43 ===================================
44
44
45 $ cat > hghaveaddon.py <<EOF
45 $ cat > hghaveaddon.py <<EOF
46 > import hghave
46 > import hghave
47 > @hghave.check("custom", "custom hghave feature")
47 > @hghave.check("custom", "custom hghave feature")
48 > def has_custom():
48 > def has_custom():
49 > return True
49 > return True
50 > @hghave.check("missing", "missing hghave feature")
50 > @hghave.check("missing", "missing hghave feature")
51 > def has_missing():
51 > def has_missing():
52 > return False
52 > return False
53 > EOF
53 > EOF
54
54
55 an empty test
55 an empty test
56 =======================
56 =======================
57
57
58 $ touch test-empty.t
58 $ touch test-empty.t
59 $ rt
59 $ rt
60 running 1 tests using 1 parallel processes
60 running 1 tests using 1 parallel processes
61 .
61 .
62 # Ran 1 tests, 0 skipped, 0 failed.
62 # Ran 1 tests, 0 skipped, 0 failed.
63 $ rm test-empty.t
63 $ rm test-empty.t
64
64
65 a succesful test
65 a succesful test
66 =======================
66 =======================
67
67
68 $ cat > test-success.t << EOF
68 $ cat > test-success.t << EOF
69 > $ echo babar
69 > $ echo babar
70 > babar
70 > babar
71 > $ echo xyzzy
71 > $ echo xyzzy
72 > dont_print (?)
72 > dont_print (?)
73 > nothing[42]line (re) (?)
73 > nothing[42]line (re) (?)
74 > never*happens (glob) (?)
74 > never*happens (glob) (?)
75 > more_nothing (?)
75 > more_nothing (?)
76 > xyzzy
76 > xyzzy
77 > nor this (?)
77 > nor this (?)
78 > $ printf 'abc\ndef\nxyz\n'
78 > $ printf 'abc\ndef\nxyz\n'
79 > 123 (?)
79 > 123 (?)
80 > abc
80 > abc
81 > def (?)
81 > def (?)
82 > 456 (?)
82 > 456 (?)
83 > xyz
83 > xyz
84 > $ printf 'zyx\nwvu\ntsr\n'
84 > $ printf 'zyx\nwvu\ntsr\n'
85 > abc (?)
85 > abc (?)
86 > zyx (custom !)
86 > zyx (custom !)
87 > wvu
87 > wvu
88 > no_print (no-custom !)
88 > no_print (no-custom !)
89 > tsr (no-missing !)
89 > tsr (no-missing !)
90 > missing (missing !)
90 > missing (missing !)
91 > EOF
91 > EOF
92
92
93 $ rt
93 $ rt
94 running 1 tests using 1 parallel processes
94 running 1 tests using 1 parallel processes
95 .
95 .
96 # Ran 1 tests, 0 skipped, 0 failed.
96 # Ran 1 tests, 0 skipped, 0 failed.
97
97
98 failing test
98 failing test
99 ==================
99 ==================
100
100
101 test churn with globs
101 test churn with globs
102 $ cat > test-failure.t <<EOF
102 $ cat > test-failure.t <<EOF
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
104 > bar*bad (glob)
104 > bar*bad (glob)
105 > bar*baz (glob)
105 > bar*baz (glob)
106 > | fo (re)
106 > | fo (re)
107 > EOF
107 > EOF
108 $ rt test-failure.t
108 $ rt test-failure.t
109 running 1 tests using 1 parallel processes
109 running 1 tests using 1 parallel processes
110
110
111 --- $TESTTMP/test-failure.t
111 --- $TESTTMP/test-failure.t
112 +++ $TESTTMP/test-failure.t.err
112 +++ $TESTTMP/test-failure.t.err
113 @@ -1,4 +1,4 @@
113 @@ -1,4 +1,4 @@
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
115 + bar*baz (glob)
115 + bar*baz (glob)
116 bar*bad (glob)
116 bar*bad (glob)
117 - bar*baz (glob)
117 - bar*baz (glob)
118 - | fo (re)
118 - | fo (re)
119 + foo
119 + foo
120
120
121 ERROR: test-failure.t output changed
121 ERROR: test-failure.t output changed
122 !
122 !
123 Failed test-failure.t: output changed
123 Failed test-failure.t: output changed
124 # Ran 1 tests, 0 skipped, 1 failed.
124 # Ran 1 tests, 0 skipped, 1 failed.
125 python hash seed: * (glob)
125 python hash seed: * (glob)
126 [1]
126 [1]
127
127
128 test how multiple globs gets matched with lines in output
128 test how multiple globs gets matched with lines in output
129 $ cat > test-failure-globs.t <<EOF
129 $ cat > test-failure-globs.t <<EOF
130 > $ echo "context"; echo "context"; \
130 > $ echo "context"; echo "context"; \
131 > echo "key: 1"; echo "value: not a"; \
131 > echo "key: 1"; echo "value: not a"; \
132 > echo "key: 2"; echo "value: not b"; \
132 > echo "key: 2"; echo "value: not b"; \
133 > echo "key: 3"; echo "value: c"; \
133 > echo "key: 3"; echo "value: c"; \
134 > echo "key: 4"; echo "value: d"
134 > echo "key: 4"; echo "value: d"
135 > context
135 > context
136 > context
136 > context
137 > key: 1
137 > key: 1
138 > value: a
138 > value: a
139 > key: 2
139 > key: 2
140 > value: b
140 > value: b
141 > key: 3
141 > key: 3
142 > value: * (glob)
142 > value: * (glob)
143 > key: 4
143 > key: 4
144 > value: * (glob)
144 > value: * (glob)
145 > EOF
145 > EOF
146 $ rt test-failure-globs.t
146 $ rt test-failure-globs.t
147 running 1 tests using 1 parallel processes
147 running 1 tests using 1 parallel processes
148
148
149 --- $TESTTMP/test-failure-globs.t
149 --- $TESTTMP/test-failure-globs.t
150 +++ $TESTTMP/test-failure-globs.t.err
150 +++ $TESTTMP/test-failure-globs.t.err
151 @@ -2,9 +2,9 @@
151 @@ -2,9 +2,9 @@
152 context
152 context
153 context
153 context
154 key: 1
154 key: 1
155 - value: a
155 - value: a
156 + value: not a
156 + value: not a
157 key: 2
157 key: 2
158 - value: b
158 - value: b
159 + value: not b
159 + value: not b
160 key: 3
160 key: 3
161 value: * (glob)
161 value: * (glob)
162 key: 4
162 key: 4
163
163
164 ERROR: test-failure-globs.t output changed
164 ERROR: test-failure-globs.t output changed
165 !
165 !
166 Failed test-failure-globs.t: output changed
166 Failed test-failure-globs.t: output changed
167 # Ran 1 tests, 0 skipped, 1 failed.
167 # Ran 1 tests, 0 skipped, 1 failed.
168 python hash seed: * (glob)
168 python hash seed: * (glob)
169 [1]
169 [1]
170 $ rm test-failure-globs.t
170 $ rm test-failure-globs.t
171
171
172 test diff colorisation
172 test diff colorisation
173
173
174 #if no-windows pygments
174 #if no-windows pygments
175 $ rt test-failure.t --color always
175 $ rt test-failure.t --color always
176 running 1 tests using 1 parallel processes
176 running 1 tests using 1 parallel processes
177
177
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
183 bar*bad (glob)
183 bar*bad (glob)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
187
187
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
189 !
189 !
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
191 # Ran 1 tests, 0 skipped, 1 failed.
191 # Ran 1 tests, 0 skipped, 1 failed.
192 python hash seed: * (glob)
192 python hash seed: * (glob)
193 [1]
193 [1]
194
194
195 $ rt test-failure.t 2> tmp.log
195 $ rt test-failure.t 2> tmp.log
196 running 1 tests using 1 parallel processes
196 running 1 tests using 1 parallel processes
197 [1]
197 [1]
198 $ cat tmp.log
198 $ cat tmp.log
199
199
200 --- $TESTTMP/test-failure.t
200 --- $TESTTMP/test-failure.t
201 +++ $TESTTMP/test-failure.t.err
201 +++ $TESTTMP/test-failure.t.err
202 @@ -1,4 +1,4 @@
202 @@ -1,4 +1,4 @@
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
204 + bar*baz (glob)
204 + bar*baz (glob)
205 bar*bad (glob)
205 bar*bad (glob)
206 - bar*baz (glob)
206 - bar*baz (glob)
207 - | fo (re)
207 - | fo (re)
208 + foo
208 + foo
209
209
210 ERROR: test-failure.t output changed
210 ERROR: test-failure.t output changed
211 !
211 !
212 Failed test-failure.t: output changed
212 Failed test-failure.t: output changed
213 # Ran 1 tests, 0 skipped, 1 failed.
213 # Ran 1 tests, 0 skipped, 1 failed.
214 python hash seed: * (glob)
214 python hash seed: * (glob)
215 #endif
215 #endif
216
216
217 $ cat > test-failure.t << EOF
217 $ cat > test-failure.t << EOF
218 > $ true
218 > $ true
219 > should go away (true !)
219 > should go away (true !)
220 > $ true
220 > $ true
221 > should stay (false !)
221 > should stay (false !)
222 >
222 >
223 > Should remove first line, not second or third
223 > Should remove first line, not second or third
224 > $ echo 'testing'
224 > $ echo 'testing'
225 > baz*foo (glob) (true !)
225 > baz*foo (glob) (true !)
226 > foobar*foo (glob) (false !)
226 > foobar*foo (glob) (false !)
227 > te*ting (glob) (true !)
227 > te*ting (glob) (true !)
228 >
228 >
229 > Should keep first two lines, remove third and last
229 > Should keep first two lines, remove third and last
230 > $ echo 'testing'
230 > $ echo 'testing'
231 > test.ng (re) (true !)
231 > test.ng (re) (true !)
232 > foo.ar (re) (false !)
232 > foo.ar (re) (false !)
233 > b.r (re) (true !)
233 > b.r (re) (true !)
234 > missing (?)
234 > missing (?)
235 > awol (true !)
235 > awol (true !)
236 >
236 >
237 > The "missing" line should stay, even though awol is dropped
237 > The "missing" line should stay, even though awol is dropped
238 > $ echo 'testing'
238 > $ echo 'testing'
239 > test.ng (re) (true !)
239 > test.ng (re) (true !)
240 > foo.ar (?)
240 > foo.ar (?)
241 > awol
241 > awol
242 > missing (?)
242 > missing (?)
243 > EOF
243 > EOF
244 $ rt test-failure.t
244 $ rt test-failure.t
245 running 1 tests using 1 parallel processes
245 running 1 tests using 1 parallel processes
246
246
247 --- $TESTTMP/test-failure.t
247 --- $TESTTMP/test-failure.t
248 +++ $TESTTMP/test-failure.t.err
248 +++ $TESTTMP/test-failure.t.err
249 @@ -1,11 +1,9 @@
249 @@ -1,11 +1,9 @@
250 $ true
250 $ true
251 - should go away (true !)
251 - should go away (true !)
252 $ true
252 $ true
253 should stay (false !)
253 should stay (false !)
254
254
255 Should remove first line, not second or third
255 Should remove first line, not second or third
256 $ echo 'testing'
256 $ echo 'testing'
257 - baz*foo (glob) (true !)
257 - baz*foo (glob) (true !)
258 foobar*foo (glob) (false !)
258 foobar*foo (glob) (false !)
259 te*ting (glob) (true !)
259 te*ting (glob) (true !)
260
260
261 foo.ar (re) (false !)
261 foo.ar (re) (false !)
262 missing (?)
262 missing (?)
263 @@ -13,13 +11,10 @@
263 @@ -13,13 +11,10 @@
264 $ echo 'testing'
264 $ echo 'testing'
265 test.ng (re) (true !)
265 test.ng (re) (true !)
266 foo.ar (re) (false !)
266 foo.ar (re) (false !)
267 - b.r (re) (true !)
267 - b.r (re) (true !)
268 missing (?)
268 missing (?)
269 - awol (true !)
269 - awol (true !)
270
270
271 The "missing" line should stay, even though awol is dropped
271 The "missing" line should stay, even though awol is dropped
272 $ echo 'testing'
272 $ echo 'testing'
273 test.ng (re) (true !)
273 test.ng (re) (true !)
274 foo.ar (?)
274 foo.ar (?)
275 - awol
275 - awol
276 missing (?)
276 missing (?)
277
277
278 ERROR: test-failure.t output changed
278 ERROR: test-failure.t output changed
279 !
279 !
280 Failed test-failure.t: output changed
280 Failed test-failure.t: output changed
281 # Ran 1 tests, 0 skipped, 1 failed.
281 # Ran 1 tests, 0 skipped, 1 failed.
282 python hash seed: * (glob)
282 python hash seed: * (glob)
283 [1]
283 [1]
284
284
285 basic failing test
285 basic failing test
286 $ cat > test-failure.t << EOF
286 $ cat > test-failure.t << EOF
287 > $ echo babar
287 > $ echo babar
288 > rataxes
288 > rataxes
289 > This is a noop statement so that
289 > This is a noop statement so that
290 > this test is still more bytes than success.
290 > this test is still more bytes than success.
291 > pad pad pad pad............................................................
291 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
297 > EOF
297 > EOF
298
298
299 >>> fh = open('test-failure-unicode.t', 'wb')
299 >>> fh = open('test-failure-unicode.t', 'wb')
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
302
302
303 $ rt
303 $ rt
304 running 3 tests using 1 parallel processes
304 running 3 tests using 1 parallel processes
305
305
306 --- $TESTTMP/test-failure.t
306 --- $TESTTMP/test-failure.t
307 +++ $TESTTMP/test-failure.t.err
307 +++ $TESTTMP/test-failure.t.err
308 @@ -1,5 +1,5 @@
308 @@ -1,5 +1,5 @@
309 $ echo babar
309 $ echo babar
310 - rataxes
310 - rataxes
311 + babar
311 + babar
312 This is a noop statement so that
312 This is a noop statement so that
313 this test is still more bytes than success.
313 this test is still more bytes than success.
314 pad pad pad pad............................................................
314 pad pad pad pad............................................................
315
315
316 ERROR: test-failure.t output changed
316 ERROR: test-failure.t output changed
317 !.
317 !.
318 --- $TESTTMP/test-failure-unicode.t
318 --- $TESTTMP/test-failure-unicode.t
319 +++ $TESTTMP/test-failure-unicode.t.err
319 +++ $TESTTMP/test-failure-unicode.t.err
320 @@ -1,2 +1,2 @@
320 @@ -1,2 +1,2 @@
321 $ echo babar\xce\xb1 (esc)
321 $ echo babar\xce\xb1 (esc)
322 - l\xce\xb5\xce\xb5t (esc)
322 - l\xce\xb5\xce\xb5t (esc)
323 + babar\xce\xb1 (esc)
323 + babar\xce\xb1 (esc)
324
324
325 ERROR: test-failure-unicode.t output changed
325 ERROR: test-failure-unicode.t output changed
326 !
326 !
327 Failed test-failure-unicode.t: output changed
327 Failed test-failure-unicode.t: output changed
328 Failed test-failure.t: output changed
328 Failed test-failure.t: output changed
329 # Ran 3 tests, 0 skipped, 2 failed.
329 # Ran 3 tests, 0 skipped, 2 failed.
330 python hash seed: * (glob)
330 python hash seed: * (glob)
331 [1]
331 [1]
332
332
333 test --outputdir
333 test --outputdir
334 $ mkdir output
334 $ mkdir output
335 $ rt --outputdir output
335 $ rt --outputdir output
336 running 3 tests using 1 parallel processes
336 running 3 tests using 1 parallel processes
337
337
338 --- $TESTTMP/test-failure.t
338 --- $TESTTMP/test-failure.t
339 +++ $TESTTMP/output/test-failure.t.err
339 +++ $TESTTMP/output/test-failure.t.err
340 @@ -1,5 +1,5 @@
340 @@ -1,5 +1,5 @@
341 $ echo babar
341 $ echo babar
342 - rataxes
342 - rataxes
343 + babar
343 + babar
344 This is a noop statement so that
344 This is a noop statement so that
345 this test is still more bytes than success.
345 this test is still more bytes than success.
346 pad pad pad pad............................................................
346 pad pad pad pad............................................................
347
347
348 ERROR: test-failure.t output changed
348 ERROR: test-failure.t output changed
349 !.
349 !.
350 --- $TESTTMP/test-failure-unicode.t
350 --- $TESTTMP/test-failure-unicode.t
351 +++ $TESTTMP/output/test-failure-unicode.t.err
351 +++ $TESTTMP/output/test-failure-unicode.t.err
352 @@ -1,2 +1,2 @@
352 @@ -1,2 +1,2 @@
353 $ echo babar\xce\xb1 (esc)
353 $ echo babar\xce\xb1 (esc)
354 - l\xce\xb5\xce\xb5t (esc)
354 - l\xce\xb5\xce\xb5t (esc)
355 + babar\xce\xb1 (esc)
355 + babar\xce\xb1 (esc)
356
356
357 ERROR: test-failure-unicode.t output changed
357 ERROR: test-failure-unicode.t output changed
358 !
358 !
359 Failed test-failure-unicode.t: output changed
359 Failed test-failure-unicode.t: output changed
360 Failed test-failure.t: output changed
360 Failed test-failure.t: output changed
361 # Ran 3 tests, 0 skipped, 2 failed.
361 # Ran 3 tests, 0 skipped, 2 failed.
362 python hash seed: * (glob)
362 python hash seed: * (glob)
363 [1]
363 [1]
364 $ ls -a output
364 $ ls -a output
365 .
365 .
366 ..
366 ..
367 .testtimes
367 .testtimes
368 test-failure-unicode.t.err
368 test-failure-unicode.t.err
369 test-failure.t.err
369 test-failure.t.err
370
370
371 test --xunit support
371 test --xunit support
372 $ rt --xunit=xunit.xml
372 $ rt --xunit=xunit.xml
373 running 3 tests using 1 parallel processes
373 running 3 tests using 1 parallel processes
374
374
375 --- $TESTTMP/test-failure.t
375 --- $TESTTMP/test-failure.t
376 +++ $TESTTMP/test-failure.t.err
376 +++ $TESTTMP/test-failure.t.err
377 @@ -1,5 +1,5 @@
377 @@ -1,5 +1,5 @@
378 $ echo babar
378 $ echo babar
379 - rataxes
379 - rataxes
380 + babar
380 + babar
381 This is a noop statement so that
381 This is a noop statement so that
382 this test is still more bytes than success.
382 this test is still more bytes than success.
383 pad pad pad pad............................................................
383 pad pad pad pad............................................................
384
384
385 ERROR: test-failure.t output changed
385 ERROR: test-failure.t output changed
386 !.
386 !.
387 --- $TESTTMP/test-failure-unicode.t
387 --- $TESTTMP/test-failure-unicode.t
388 +++ $TESTTMP/test-failure-unicode.t.err
388 +++ $TESTTMP/test-failure-unicode.t.err
389 @@ -1,2 +1,2 @@
389 @@ -1,2 +1,2 @@
390 $ echo babar\xce\xb1 (esc)
390 $ echo babar\xce\xb1 (esc)
391 - l\xce\xb5\xce\xb5t (esc)
391 - l\xce\xb5\xce\xb5t (esc)
392 + babar\xce\xb1 (esc)
392 + babar\xce\xb1 (esc)
393
393
394 ERROR: test-failure-unicode.t output changed
394 ERROR: test-failure-unicode.t output changed
395 !
395 !
396 Failed test-failure-unicode.t: output changed
396 Failed test-failure-unicode.t: output changed
397 Failed test-failure.t: output changed
397 Failed test-failure.t: output changed
398 # Ran 3 tests, 0 skipped, 2 failed.
398 # Ran 3 tests, 0 skipped, 2 failed.
399 python hash seed: * (glob)
399 python hash seed: * (glob)
400 [1]
400 [1]
401 $ cat xunit.xml
401 $ cat xunit.xml
402 <?xml version="1.0" encoding="utf-8"?>
402 <?xml version="1.0" encoding="utf-8"?>
403 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
403 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
404 <testcase name="test-success.t" time="*"/> (glob)
404 <testcase name="test-success.t" time="*"/> (glob)
405 <testcase name="test-failure-unicode.t" time="*"> (glob)
405 <testcase name="test-failure-unicode.t" time="*"> (glob)
406 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
406 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure-unicode.t (py38 !)
407 <failure message="output changed" type="output-mismatch"> (no-py38 !)
407 <failure message="output changed" type="output-mismatch"> (no-py38 !)
408 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
408 <![CDATA[--- $TESTTMP/test-failure-unicode.t (no-py38 !)
409 +++ $TESTTMP/test-failure-unicode.t.err
409 +++ $TESTTMP/test-failure-unicode.t.err
410 @@ -1,2 +1,2 @@
410 @@ -1,2 +1,2 @@
411 $ echo babar\xce\xb1 (esc)
411 $ echo babar\xce\xb1 (esc)
412 - l\xce\xb5\xce\xb5t (esc)
412 - l\xce\xb5\xce\xb5t (esc)
413 + babar\xce\xb1 (esc)
413 + babar\xce\xb1 (esc)
414 ]]></failure> (py38 !)
414 ]]></failure> (py38 !)
415 ]]> </failure> (no-py38 !)
415 ]]> </failure> (no-py38 !)
416 </testcase>
416 </testcase>
417 <testcase name="test-failure.t" time="*"> (glob)
417 <testcase name="test-failure.t" time="*"> (glob)
418 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
418 <failure message="output changed" type="output-mismatch"><![CDATA[--- $TESTTMP/test-failure.t (py38 !)
419 <failure message="output changed" type="output-mismatch"> (no-py38 !)
419 <failure message="output changed" type="output-mismatch"> (no-py38 !)
420 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
420 <![CDATA[--- $TESTTMP/test-failure.t (no-py38 !)
421 +++ $TESTTMP/test-failure.t.err
421 +++ $TESTTMP/test-failure.t.err
422 @@ -1,5 +1,5 @@
422 @@ -1,5 +1,5 @@
423 $ echo babar
423 $ echo babar
424 - rataxes
424 - rataxes
425 + babar
425 + babar
426 This is a noop statement so that
426 This is a noop statement so that
427 this test is still more bytes than success.
427 this test is still more bytes than success.
428 pad pad pad pad............................................................
428 pad pad pad pad............................................................
429 ]]></failure> (py38 !)
429 ]]></failure> (py38 !)
430 ]]> </failure> (no-py38 !)
430 ]]> </failure> (no-py38 !)
431 </testcase>
431 </testcase>
432 </testsuite>
432 </testsuite>
433
433
434 $ cat .testtimes
434 $ cat .testtimes
435 test-empty.t * (glob)
435 test-empty.t * (glob)
436 test-failure-globs.t * (glob)
436 test-failure-globs.t * (glob)
437 test-failure-unicode.t * (glob)
437 test-failure-unicode.t * (glob)
438 test-failure.t * (glob)
438 test-failure.t * (glob)
439 test-success.t * (glob)
439 test-success.t * (glob)
440
440
441 $ rt --list-tests
441 $ rt --list-tests
442 test-failure-unicode.t
442 test-failure-unicode.t
443 test-failure.t
443 test-failure.t
444 test-success.t
444 test-success.t
445
445
446 $ rt --list-tests --json
446 $ rt --list-tests --json
447 test-failure-unicode.t
447 test-failure-unicode.t
448 test-failure.t
448 test-failure.t
449 test-success.t
449 test-success.t
450 $ cat report.json
450 $ cat report.json
451 testreport ={
451 testreport ={
452 "test-failure-unicode.t": {
452 "test-failure-unicode.t": {
453 "result": "success"
453 "result": "success"
454 },
454 },
455 "test-failure.t": {
455 "test-failure.t": {
456 "result": "success"
456 "result": "success"
457 },
457 },
458 "test-success.t": {
458 "test-success.t": {
459 "result": "success"
459 "result": "success"
460 }
460 }
461 } (no-eol)
461 } (no-eol)
462
462
463 $ rt --list-tests --xunit=xunit.xml
463 $ rt --list-tests --xunit=xunit.xml
464 test-failure-unicode.t
464 test-failure-unicode.t
465 test-failure.t
465 test-failure.t
466 test-success.t
466 test-success.t
467 $ cat xunit.xml
467 $ cat xunit.xml
468 <?xml version="1.0" encoding="utf-8"?>
468 <?xml version="1.0" encoding="utf-8"?>
469 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
469 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
470 <testcase name="test-failure-unicode.t"/>
470 <testcase name="test-failure-unicode.t"/>
471 <testcase name="test-failure.t"/>
471 <testcase name="test-failure.t"/>
472 <testcase name="test-success.t"/>
472 <testcase name="test-success.t"/>
473 </testsuite>
473 </testsuite>
474
474
475 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
475 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
476 test-failure-unicode.t
476 test-failure-unicode.t
477 test-failure.t
477 test-failure.t
478 $ cat output/report.json
478 $ cat output/report.json
479 testreport ={
479 testreport ={
480 "test-failure-unicode.t": {
480 "test-failure-unicode.t": {
481 "result": "success"
481 "result": "success"
482 },
482 },
483 "test-failure.t": {
483 "test-failure.t": {
484 "result": "success"
484 "result": "success"
485 }
485 }
486 } (no-eol)
486 } (no-eol)
487 $ cat xunit.xml
487 $ cat xunit.xml
488 <?xml version="1.0" encoding="utf-8"?>
488 <?xml version="1.0" encoding="utf-8"?>
489 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
489 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
490 <testcase name="test-failure-unicode.t"/>
490 <testcase name="test-failure-unicode.t"/>
491 <testcase name="test-failure.t"/>
491 <testcase name="test-failure.t"/>
492 </testsuite>
492 </testsuite>
493
493
494 $ rm test-failure-unicode.t
494 $ rm test-failure-unicode.t
495
495
496 test for --retest
496 test for --retest
497 ====================
497 ====================
498
498
499 $ rt --retest
499 $ rt --retest
500 running 1 tests using 1 parallel processes
500 running 1 tests using 1 parallel processes
501
501
502 --- $TESTTMP/test-failure.t
502 --- $TESTTMP/test-failure.t
503 +++ $TESTTMP/test-failure.t.err
503 +++ $TESTTMP/test-failure.t.err
504 @@ -1,5 +1,5 @@
504 @@ -1,5 +1,5 @@
505 $ echo babar
505 $ echo babar
506 - rataxes
506 - rataxes
507 + babar
507 + babar
508 This is a noop statement so that
508 This is a noop statement so that
509 this test is still more bytes than success.
509 this test is still more bytes than success.
510 pad pad pad pad............................................................
510 pad pad pad pad............................................................
511
511
512 ERROR: test-failure.t output changed
512 ERROR: test-failure.t output changed
513 !
513 !
514 Failed test-failure.t: output changed
514 Failed test-failure.t: output changed
515 # Ran 1 tests, 0 skipped, 1 failed.
515 # Ran 1 tests, 0 skipped, 1 failed.
516 python hash seed: * (glob)
516 python hash seed: * (glob)
517 [1]
517 [1]
518
518
519 --retest works with --outputdir
519 --retest works with --outputdir
520 $ rm -r output
520 $ rm -r output
521 $ mkdir output
521 $ mkdir output
522 $ mv test-failure.t.err output
522 $ mv test-failure.t.err output
523 $ rt --retest --outputdir output
523 $ rt --retest --outputdir output
524 running 1 tests using 1 parallel processes
524 running 1 tests using 1 parallel processes
525
525
526 --- $TESTTMP/test-failure.t
526 --- $TESTTMP/test-failure.t
527 +++ $TESTTMP/output/test-failure.t.err
527 +++ $TESTTMP/output/test-failure.t.err
528 @@ -1,5 +1,5 @@
528 @@ -1,5 +1,5 @@
529 $ echo babar
529 $ echo babar
530 - rataxes
530 - rataxes
531 + babar
531 + babar
532 This is a noop statement so that
532 This is a noop statement so that
533 this test is still more bytes than success.
533 this test is still more bytes than success.
534 pad pad pad pad............................................................
534 pad pad pad pad............................................................
535
535
536 ERROR: test-failure.t output changed
536 ERROR: test-failure.t output changed
537 !
537 !
538 Failed test-failure.t: output changed
538 Failed test-failure.t: output changed
539 # Ran 1 tests, 0 skipped, 1 failed.
539 # Ran 1 tests, 0 skipped, 1 failed.
540 python hash seed: * (glob)
540 python hash seed: * (glob)
541 [1]
541 [1]
542
542
543 Selecting Tests To Run
543 Selecting Tests To Run
544 ======================
544 ======================
545
545
546 successful
546 successful
547
547
548 $ rt test-success.t
548 $ rt test-success.t
549 running 1 tests using 1 parallel processes
549 running 1 tests using 1 parallel processes
550 .
550 .
551 # Ran 1 tests, 0 skipped, 0 failed.
551 # Ran 1 tests, 0 skipped, 0 failed.
552
552
553 success w/ keyword
553 success w/ keyword
554 $ rt -k xyzzy
554 $ rt -k xyzzy
555 running 2 tests using 1 parallel processes
555 running 2 tests using 1 parallel processes
556 .
556 .
557 # Ran 2 tests, 1 skipped, 0 failed.
557 # Ran 2 tests, 1 skipped, 0 failed.
558
558
559 failed
559 failed
560
560
561 $ rt test-failure.t
561 $ rt test-failure.t
562 running 1 tests using 1 parallel processes
562 running 1 tests using 1 parallel processes
563
563
564 --- $TESTTMP/test-failure.t
564 --- $TESTTMP/test-failure.t
565 +++ $TESTTMP/test-failure.t.err
565 +++ $TESTTMP/test-failure.t.err
566 @@ -1,5 +1,5 @@
566 @@ -1,5 +1,5 @@
567 $ echo babar
567 $ echo babar
568 - rataxes
568 - rataxes
569 + babar
569 + babar
570 This is a noop statement so that
570 This is a noop statement so that
571 this test is still more bytes than success.
571 this test is still more bytes than success.
572 pad pad pad pad............................................................
572 pad pad pad pad............................................................
573
573
574 ERROR: test-failure.t output changed
574 ERROR: test-failure.t output changed
575 !
575 !
576 Failed test-failure.t: output changed
576 Failed test-failure.t: output changed
577 # Ran 1 tests, 0 skipped, 1 failed.
577 # Ran 1 tests, 0 skipped, 1 failed.
578 python hash seed: * (glob)
578 python hash seed: * (glob)
579 [1]
579 [1]
580
580
581 failure w/ keyword
581 failure w/ keyword
582 $ rt -k rataxes
582 $ rt -k rataxes
583 running 2 tests using 1 parallel processes
583 running 2 tests using 1 parallel processes
584
584
585 --- $TESTTMP/test-failure.t
585 --- $TESTTMP/test-failure.t
586 +++ $TESTTMP/test-failure.t.err
586 +++ $TESTTMP/test-failure.t.err
587 @@ -1,5 +1,5 @@
587 @@ -1,5 +1,5 @@
588 $ echo babar
588 $ echo babar
589 - rataxes
589 - rataxes
590 + babar
590 + babar
591 This is a noop statement so that
591 This is a noop statement so that
592 this test is still more bytes than success.
592 this test is still more bytes than success.
593 pad pad pad pad............................................................
593 pad pad pad pad............................................................
594
594
595 ERROR: test-failure.t output changed
595 ERROR: test-failure.t output changed
596 !
596 !
597 Failed test-failure.t: output changed
597 Failed test-failure.t: output changed
598 # Ran 2 tests, 1 skipped, 1 failed.
598 # Ran 2 tests, 1 skipped, 1 failed.
599 python hash seed: * (glob)
599 python hash seed: * (glob)
600 [1]
600 [1]
601
601
602 Verify that when a process fails to start we show a useful message
602 Verify that when a process fails to start we show a useful message
603 ==================================================================
603 ==================================================================
604
604
605 $ cat > test-serve-fail.t <<EOF
605 $ cat > test-serve-fail.t <<EOF
606 > $ echo 'abort: child process failed to start blah'
606 > $ echo 'abort: child process failed to start blah'
607 > EOF
607 > EOF
608 $ rt test-serve-fail.t
608 $ rt test-serve-fail.t
609 running 1 tests using 1 parallel processes
609 running 1 tests using 1 parallel processes
610
610
611 --- $TESTTMP/test-serve-fail.t
611 --- $TESTTMP/test-serve-fail.t
612 +++ $TESTTMP/test-serve-fail.t.err
612 +++ $TESTTMP/test-serve-fail.t.err
613 @@ -1* +1,2 @@ (glob)
613 @@ -1* +1,2 @@ (glob)
614 $ echo 'abort: child process failed to start blah'
614 $ echo 'abort: child process failed to start blah'
615 + abort: child process failed to start blah
615 + abort: child process failed to start blah
616
616
617 ERROR: test-serve-fail.t output changed
617 ERROR: test-serve-fail.t output changed
618 !
618 !
619 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
619 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
620 # Ran 1 tests, 0 skipped, 1 failed.
620 # Ran 1 tests, 0 skipped, 1 failed.
621 python hash seed: * (glob)
621 python hash seed: * (glob)
622 [1]
622 [1]
623 $ rm test-serve-fail.t
623 $ rm test-serve-fail.t
624
624
625 Verify that we can try other ports
625 Verify that we can try other ports
626 ===================================
626 ===================================
627
627
628 Extensions aren't inherited by the invoked run-tests.py. An extension
628 Extensions aren't inherited by the invoked run-tests.py. An extension
629 introducing a repository requirement could cause this to fail. So we force
629 introducing a repository requirement could cause this to fail. So we force
630 HGRCPATH to get a clean environment.
630 HGRCPATH to get a clean environment.
631
631
632 $ HGRCPATH= hg init inuse
632 $ HGRCPATH= hg init inuse
633 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
633 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
634 $ cat blocks.pid >> $DAEMON_PIDS
634 $ cat blocks.pid >> $DAEMON_PIDS
635 $ cat > test-serve-inuse.t <<EOF
635 $ cat > test-serve-inuse.t <<EOF
636 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
636 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
637 > $ cat hg.pid >> \$DAEMON_PIDS
637 > $ cat hg.pid >> \$DAEMON_PIDS
638 > EOF
638 > EOF
639 $ rt test-serve-inuse.t
639 $ rt test-serve-inuse.t
640 running 1 tests using 1 parallel processes
640 running 1 tests using 1 parallel processes
641 .
641 .
642 # Ran 1 tests, 0 skipped, 0 failed.
642 # Ran 1 tests, 0 skipped, 0 failed.
643 $ rm test-serve-inuse.t
643 $ rm test-serve-inuse.t
644 $ killdaemons.py $DAEMON_PIDS
644 $ killdaemons.py $DAEMON_PIDS
645
645
646 Running In Debug Mode
646 Running In Debug Mode
647 ======================
647 ======================
648
648
649 $ rt --debug 2>&1 | grep -v pwd
649 $ rt --debug 2>&1 | grep -v pwd
650 running 2 tests using 1 parallel processes
650 running 2 tests using 1 parallel processes
651 + alias hg=hg.exe (windows !)
651 + alias hg=hg.exe (windows !)
652 + echo *SALT* 0 0 (glob)
652 + echo *SALT* 0 0 (glob)
653 *SALT* 0 0 (glob)
653 *SALT* 0 0 (glob)
654 + echo babar
654 + echo babar
655 babar
655 babar
656 + echo *SALT* 10 0 (glob)
656 + echo *SALT* 10 0 (glob)
657 *SALT* 10 0 (glob)
657 *SALT* 10 0 (glob)
658 .+ alias hg=hg.exe (windows !)
658 .+ alias hg=hg.exe (windows !)
659 *+ echo *SALT* 0 0 (glob)
659 *+ echo *SALT* 0 0 (glob)
660 *SALT* 0 0 (glob)
660 *SALT* 0 0 (glob)
661 + echo babar
661 + echo babar
662 babar
662 babar
663 + echo *SALT* 2 0 (glob)
663 + echo *SALT* 2 0 (glob)
664 *SALT* 2 0 (glob)
664 *SALT* 2 0 (glob)
665 + echo xyzzy
665 + echo xyzzy
666 xyzzy
666 xyzzy
667 + echo *SALT* 9 0 (glob)
667 + echo *SALT* 9 0 (glob)
668 *SALT* 9 0 (glob)
668 *SALT* 9 0 (glob)
669 + printf *abc\ndef\nxyz\n* (glob)
669 + printf *abc\ndef\nxyz\n* (glob)
670 abc
670 abc
671 def
671 def
672 xyz
672 xyz
673 + echo *SALT* 15 0 (glob)
673 + echo *SALT* 15 0 (glob)
674 *SALT* 15 0 (glob)
674 *SALT* 15 0 (glob)
675 + printf *zyx\nwvu\ntsr\n* (glob)
675 + printf *zyx\nwvu\ntsr\n* (glob)
676 zyx
676 zyx
677 wvu
677 wvu
678 tsr
678 tsr
679 + echo *SALT* 22 0 (glob)
679 + echo *SALT* 22 0 (glob)
680 *SALT* 22 0 (glob)
680 *SALT* 22 0 (glob)
681 .
681 .
682 # Ran 2 tests, 0 skipped, 0 failed.
682 # Ran 2 tests, 0 skipped, 0 failed.
683
683
684 Parallel runs
684 Parallel runs
685 ==============
685 ==============
686
686
687 (duplicate the failing test to get predictable output)
687 (duplicate the failing test to get predictable output)
688 $ cp test-failure.t test-failure-copy.t
688 $ cp test-failure.t test-failure-copy.t
689
689
690 $ rt --jobs 2 test-failure*.t -n
690 $ rt --jobs 2 test-failure*.t -n
691 running 2 tests using 2 parallel processes
691 running 2 tests using 2 parallel processes
692 !!
692 !!
693 Failed test-failure*.t: output changed (glob)
693 Failed test-failure*.t: output changed (glob)
694 Failed test-failure*.t: output changed (glob)
694 Failed test-failure*.t: output changed (glob)
695 # Ran 2 tests, 0 skipped, 2 failed.
695 # Ran 2 tests, 0 skipped, 2 failed.
696 python hash seed: * (glob)
696 python hash seed: * (glob)
697 [1]
697 [1]
698
698
699 failures in parallel with --first should only print one failure
699 failures in parallel with --first should only print one failure
700 $ rt --jobs 2 --first test-failure*.t
700 $ rt --jobs 2 --first test-failure*.t
701 running 2 tests using 2 parallel processes
701 running 2 tests using 2 parallel processes
702
702
703 --- $TESTTMP/test-failure*.t (glob)
703 --- $TESTTMP/test-failure*.t (glob)
704 +++ $TESTTMP/test-failure*.t.err (glob)
704 +++ $TESTTMP/test-failure*.t.err (glob)
705 @@ -1,5 +1,5 @@
705 @@ -1,5 +1,5 @@
706 $ echo babar
706 $ echo babar
707 - rataxes
707 - rataxes
708 + babar
708 + babar
709 This is a noop statement so that
709 This is a noop statement so that
710 this test is still more bytes than success.
710 this test is still more bytes than success.
711 pad pad pad pad............................................................
711 pad pad pad pad............................................................
712
712
713 Failed test-failure*.t: output changed (glob)
713 Failed test-failure*.t: output changed (glob)
714 Failed test-failure*.t: output changed (glob)
714 Failed test-failure*.t: output changed (glob)
715 # Ran 2 tests, 0 skipped, 2 failed.
715 # Ran 2 tests, 0 skipped, 2 failed.
716 python hash seed: * (glob)
716 python hash seed: * (glob)
717 [1]
717 [1]
718
718
719
719
720 (delete the duplicated test file)
720 (delete the duplicated test file)
721 $ rm test-failure-copy.t
721 $ rm test-failure-copy.t
722
722
723 multiple runs per test should be parallelized
723 multiple runs per test should be parallelized
724
724
725 $ rt --jobs 2 --runs-per-test 2 test-success.t
725 $ rt --jobs 2 --runs-per-test 2 test-success.t
726 running 2 tests using 2 parallel processes
726 running 2 tests using 2 parallel processes
727 ..
727 ..
728 # Ran 2 tests, 0 skipped, 0 failed.
728 # Ran 2 tests, 0 skipped, 0 failed.
729
729
730 Interactive run
730 Interactive run
731 ===============
731 ===============
732
732
733 (backup the failing test)
733 (backup the failing test)
734 $ cp test-failure.t backup
734 $ cp test-failure.t backup
735
735
736 Refuse the fix
736 Refuse the fix
737
737
738 $ echo 'n' | rt -i
738 $ echo 'n' | rt -i
739 running 2 tests using 1 parallel processes
739 running 2 tests using 1 parallel processes
740
740
741 --- $TESTTMP/test-failure.t
741 --- $TESTTMP/test-failure.t
742 +++ $TESTTMP/test-failure.t.err
742 +++ $TESTTMP/test-failure.t.err
743 @@ -1,5 +1,5 @@
743 @@ -1,5 +1,5 @@
744 $ echo babar
744 $ echo babar
745 - rataxes
745 - rataxes
746 + babar
746 + babar
747 This is a noop statement so that
747 This is a noop statement so that
748 this test is still more bytes than success.
748 this test is still more bytes than success.
749 pad pad pad pad............................................................
749 pad pad pad pad............................................................
750 Accept this change? [y/N]
750 Accept this change? [y/N]
751 ERROR: test-failure.t output changed
751 ERROR: test-failure.t output changed
752 !.
752 !.
753 Failed test-failure.t: output changed
753 Failed test-failure.t: output changed
754 # Ran 2 tests, 0 skipped, 1 failed.
754 # Ran 2 tests, 0 skipped, 1 failed.
755 python hash seed: * (glob)
755 python hash seed: * (glob)
756 [1]
756 [1]
757
757
758 $ cat test-failure.t
758 $ cat test-failure.t
759 $ echo babar
759 $ echo babar
760 rataxes
760 rataxes
761 This is a noop statement so that
761 This is a noop statement so that
762 this test is still more bytes than success.
762 this test is still more bytes than success.
763 pad pad pad pad............................................................
763 pad pad pad pad............................................................
764 pad pad pad pad............................................................
764 pad pad pad pad............................................................
765 pad pad pad pad............................................................
765 pad pad pad pad............................................................
766 pad pad pad pad............................................................
766 pad pad pad pad............................................................
767 pad pad pad pad............................................................
767 pad pad pad pad............................................................
768 pad pad pad pad............................................................
768 pad pad pad pad............................................................
769
769
770 Interactive with custom view
770 Interactive with custom view
771
771
772 $ echo 'n' | rt -i --view echo
772 $ echo 'n' | rt -i --view echo
773 running 2 tests using 1 parallel processes
773 running 2 tests using 1 parallel processes
774 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
774 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
775 Accept this change? [y/N]* (glob)
775 Accept this change? [y/N]* (glob)
776 ERROR: test-failure.t output changed
776 ERROR: test-failure.t output changed
777 !.
777 !.
778 Failed test-failure.t: output changed
778 Failed test-failure.t: output changed
779 # Ran 2 tests, 0 skipped, 1 failed.
779 # Ran 2 tests, 0 skipped, 1 failed.
780 python hash seed: * (glob)
780 python hash seed: * (glob)
781 [1]
781 [1]
782
782
783 View the fix
783 View the fix
784
784
785 $ echo 'y' | rt --view echo
785 $ echo 'y' | rt --view echo
786 running 2 tests using 1 parallel processes
786 running 2 tests using 1 parallel processes
787 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
787 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
788
788
789 ERROR: test-failure.t output changed
789 ERROR: test-failure.t output changed
790 !.
790 !.
791 Failed test-failure.t: output changed
791 Failed test-failure.t: output changed
792 # Ran 2 tests, 0 skipped, 1 failed.
792 # Ran 2 tests, 0 skipped, 1 failed.
793 python hash seed: * (glob)
793 python hash seed: * (glob)
794 [1]
794 [1]
795
795
796 Accept the fix
796 Accept the fix
797
797
798 $ cat >> test-failure.t <<EOF
798 $ cat >> test-failure.t <<EOF
799 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
799 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
800 > saved backup bundle to \$TESTTMP/foo.hg
800 > saved backup bundle to \$TESTTMP/foo.hg
801 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
801 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
802 > saved backup bundle to $TESTTMP\\foo.hg
802 > saved backup bundle to $TESTTMP\\foo.hg
803 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
803 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
804 > saved backup bundle to \$TESTTMP/*.hg (glob)
804 > saved backup bundle to \$TESTTMP/*.hg (glob)
805 > EOF
805 > EOF
806 $ echo 'y' | rt -i 2>&1
806 $ echo 'y' | rt -i 2>&1
807 running 2 tests using 1 parallel processes
807 running 2 tests using 1 parallel processes
808
808
809 --- $TESTTMP/test-failure.t
809 --- $TESTTMP/test-failure.t
810 +++ $TESTTMP/test-failure.t.err
810 +++ $TESTTMP/test-failure.t.err
811 @@ -1,5 +1,5 @@
811 @@ -1,5 +1,5 @@
812 $ echo babar
812 $ echo babar
813 - rataxes
813 - rataxes
814 + babar
814 + babar
815 This is a noop statement so that
815 This is a noop statement so that
816 this test is still more bytes than success.
816 this test is still more bytes than success.
817 pad pad pad pad............................................................
817 pad pad pad pad............................................................
818 @@ -11,6 +11,6 @@
818 @@ -11,6 +11,6 @@
819 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
819 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
820 saved backup bundle to $TESTTMP/foo.hg
820 saved backup bundle to $TESTTMP/foo.hg
821 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
821 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
822 - saved backup bundle to $TESTTMP\foo.hg
822 - saved backup bundle to $TESTTMP\foo.hg
823 + saved backup bundle to $TESTTMP/foo.hg
823 + saved backup bundle to $TESTTMP/foo.hg
824 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
824 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
825 saved backup bundle to $TESTTMP/*.hg (glob)
825 saved backup bundle to $TESTTMP/*.hg (glob)
826 Accept this change? [y/N] ..
826 Accept this change? [y/N] ..
827 # Ran 2 tests, 0 skipped, 0 failed.
827 # Ran 2 tests, 0 skipped, 0 failed.
828
828
829 $ sed -e 's,(glob)$,&<,g' test-failure.t
829 $ sed -e 's,(glob)$,&<,g' test-failure.t
830 $ echo babar
830 $ echo babar
831 babar
831 babar
832 This is a noop statement so that
832 This is a noop statement so that
833 this test is still more bytes than success.
833 this test is still more bytes than success.
834 pad pad pad pad............................................................
834 pad pad pad pad............................................................
835 pad pad pad pad............................................................
835 pad pad pad pad............................................................
836 pad pad pad pad............................................................
836 pad pad pad pad............................................................
837 pad pad pad pad............................................................
837 pad pad pad pad............................................................
838 pad pad pad pad............................................................
838 pad pad pad pad............................................................
839 pad pad pad pad............................................................
839 pad pad pad pad............................................................
840 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
840 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
841 saved backup bundle to $TESTTMP/foo.hg
841 saved backup bundle to $TESTTMP/foo.hg
842 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
842 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
843 saved backup bundle to $TESTTMP/foo.hg
843 saved backup bundle to $TESTTMP/foo.hg
844 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
844 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
845 saved backup bundle to $TESTTMP/*.hg (glob)<
845 saved backup bundle to $TESTTMP/*.hg (glob)<
846
846
847 $ rm test-failure.t
847 $ rm test-failure.t
848
848
849 Race condition - test file was modified when test is running
849 Race condition - test file was modified when test is running
850
850
851 $ TESTRACEDIR=`pwd`
851 $ TESTRACEDIR=`pwd`
852 $ export TESTRACEDIR
852 $ export TESTRACEDIR
853 $ cat > test-race.t <<EOF
853 $ cat > test-race.t <<EOF
854 > $ echo 1
854 > $ echo 1
855 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
855 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
856 > EOF
856 > EOF
857
857
858 $ rt -i test-race.t
858 $ rt -i test-race.t
859 running 1 tests using 1 parallel processes
859 running 1 tests using 1 parallel processes
860
860
861 --- $TESTTMP/test-race.t
861 --- $TESTTMP/test-race.t
862 +++ $TESTTMP/test-race.t.err
862 +++ $TESTTMP/test-race.t.err
863 @@ -1,2 +1,3 @@
863 @@ -1,2 +1,3 @@
864 $ echo 1
864 $ echo 1
865 + 1
865 + 1
866 $ echo "# a new line" >> $TESTTMP/test-race.t
866 $ echo "# a new line" >> $TESTTMP/test-race.t
867 Reference output has changed (run again to prompt changes)
867 Reference output has changed (run again to prompt changes)
868 ERROR: test-race.t output changed
868 ERROR: test-race.t output changed
869 !
869 !
870 Failed test-race.t: output changed
870 Failed test-race.t: output changed
871 # Ran 1 tests, 0 skipped, 1 failed.
871 # Ran 1 tests, 0 skipped, 1 failed.
872 python hash seed: * (glob)
872 python hash seed: * (glob)
873 [1]
873 [1]
874
874
875 $ rm test-race.t
875 $ rm test-race.t
876
876
877 When "#testcases" is used in .t files
877 When "#testcases" is used in .t files
878
878
879 $ cat >> test-cases.t <<EOF
879 $ cat >> test-cases.t <<EOF
880 > #testcases a b
880 > #testcases a b
881 > #if a
881 > #if a
882 > $ echo 1
882 > $ echo 1
883 > #endif
883 > #endif
884 > #if b
884 > #if b
885 > $ echo 2
885 > $ echo 2
886 > #endif
886 > #endif
887 > EOF
887 > EOF
888
888
889 $ cat <<EOF | rt -i test-cases.t 2>&1
889 $ cat <<EOF | rt -i test-cases.t 2>&1
890 > y
890 > y
891 > y
891 > y
892 > EOF
892 > EOF
893 running 2 tests using 1 parallel processes
893 running 2 tests using 1 parallel processes
894
894
895 --- $TESTTMP/test-cases.t
895 --- $TESTTMP/test-cases.t
896 +++ $TESTTMP/test-cases.t#a.err
896 +++ $TESTTMP/test-cases.t#a.err
897 @@ -1,6 +1,7 @@
897 @@ -1,6 +1,7 @@
898 #testcases a b
898 #testcases a b
899 #if a
899 #if a
900 $ echo 1
900 $ echo 1
901 + 1
901 + 1
902 #endif
902 #endif
903 #if b
903 #if b
904 $ echo 2
904 $ echo 2
905 Accept this change? [y/N] .
905 Accept this change? [y/N] .
906 --- $TESTTMP/test-cases.t
906 --- $TESTTMP/test-cases.t
907 +++ $TESTTMP/test-cases.t#b.err
907 +++ $TESTTMP/test-cases.t#b.err
908 @@ -5,4 +5,5 @@
908 @@ -5,4 +5,5 @@
909 #endif
909 #endif
910 #if b
910 #if b
911 $ echo 2
911 $ echo 2
912 + 2
912 + 2
913 #endif
913 #endif
914 Accept this change? [y/N] .
914 Accept this change? [y/N] .
915 # Ran 2 tests, 0 skipped, 0 failed.
915 # Ran 2 tests, 0 skipped, 0 failed.
916
916
917 $ cat test-cases.t
917 $ cat test-cases.t
918 #testcases a b
918 #testcases a b
919 #if a
919 #if a
920 $ echo 1
920 $ echo 1
921 1
921 1
922 #endif
922 #endif
923 #if b
923 #if b
924 $ echo 2
924 $ echo 2
925 2
925 2
926 #endif
926 #endif
927
927
928 $ cat >> test-cases.t <<'EOF'
928 $ cat >> test-cases.t <<'EOF'
929 > #if a
929 > #if a
930 > $ NAME=A
930 > $ NAME=A
931 > #else
931 > #else
932 > $ NAME=B
932 > $ NAME=B
933 > #endif
933 > #endif
934 > $ echo $NAME
934 > $ echo $NAME
935 > A (a !)
935 > A (a !)
936 > B (b !)
936 > B (b !)
937 > EOF
937 > EOF
938 $ rt test-cases.t
938 $ rt test-cases.t
939 running 2 tests using 1 parallel processes
939 running 2 tests using 1 parallel processes
940 ..
940 ..
941 # Ran 2 tests, 0 skipped, 0 failed.
941 # Ran 2 tests, 0 skipped, 0 failed.
942
942
943 When using multiple dimensions of "#testcases" in .t files
943 When using multiple dimensions of "#testcases" in .t files
944
944
945 $ cat > test-cases.t <<'EOF'
945 $ cat > test-cases.t <<'EOF'
946 > #testcases a b
946 > #testcases a b
947 > #testcases c d
947 > #testcases c d
948 > #if a d
948 > #if a d
949 > $ echo $TESTCASE
949 > $ echo $TESTCASE
950 > a#d
950 > a#d
951 > #endif
951 > #endif
952 > #if b c
952 > #if b c
953 > $ echo yes
953 > $ echo yes
954 > no
954 > no
955 > #endif
955 > #endif
956 > EOF
956 > EOF
957 $ rt test-cases.t
957 $ rt test-cases.t
958 running 4 tests using 1 parallel processes
958 running 4 tests using 1 parallel processes
959 ..
959 ..
960 --- $TESTTMP/test-cases.t
960 --- $TESTTMP/test-cases.t
961 +++ $TESTTMP/test-cases.t#b#c.err
961 +++ $TESTTMP/test-cases.t#b#c.err
962 @@ -6,5 +6,5 @@
962 @@ -6,5 +6,5 @@
963 #endif
963 #endif
964 #if b c
964 #if b c
965 $ echo yes
965 $ echo yes
966 - no
966 - no
967 + yes
967 + yes
968 #endif
968 #endif
969
969
970 ERROR: test-cases.t#b#c output changed
970 ERROR: test-cases.t#b#c output changed
971 !.
971 !.
972 Failed test-cases.t#b#c: output changed
972 Failed test-cases.t#b#c: output changed
973 # Ran 4 tests, 0 skipped, 1 failed.
973 # Ran 4 tests, 0 skipped, 1 failed.
974 python hash seed: * (glob)
974 python hash seed: * (glob)
975 [1]
975 [1]
976
976
977 $ rt --retest
977 $ rt --retest
978 running 1 tests using 1 parallel processes
978 running 1 tests using 1 parallel processes
979
979
980 --- $TESTTMP/test-cases.t
980 --- $TESTTMP/test-cases.t
981 +++ $TESTTMP/test-cases.t#b#c.err
981 +++ $TESTTMP/test-cases.t#b#c.err
982 @@ -6,5 +6,5 @@
982 @@ -6,5 +6,5 @@
983 #endif
983 #endif
984 #if b c
984 #if b c
985 $ echo yes
985 $ echo yes
986 - no
986 - no
987 + yes
987 + yes
988 #endif
988 #endif
989
989
990 ERROR: test-cases.t#b#c output changed
990 ERROR: test-cases.t#b#c output changed
991 !
991 !
992 Failed test-cases.t#b#c: output changed
992 Failed test-cases.t#b#c: output changed
993 # Ran 1 tests, 0 skipped, 1 failed.
993 # Ran 1 tests, 0 skipped, 1 failed.
994 python hash seed: * (glob)
994 python hash seed: * (glob)
995 [1]
995 [1]
996 $ rm test-cases.t#b#c.err
996 $ rm test-cases.t#b#c.err
997 $ rm test-cases.t
997 $ rm test-cases.t
998
998
999 (reinstall)
999 (reinstall)
1000 $ mv backup test-failure.t
1000 $ mv backup test-failure.t
1001
1001
1002 No Diff
1002 No Diff
1003 ===============
1003 ===============
1004
1004
1005 $ rt --nodiff
1005 $ rt --nodiff
1006 running 2 tests using 1 parallel processes
1006 running 2 tests using 1 parallel processes
1007 !.
1007 !.
1008 Failed test-failure.t: output changed
1008 Failed test-failure.t: output changed
1009 # Ran 2 tests, 0 skipped, 1 failed.
1009 # Ran 2 tests, 0 skipped, 1 failed.
1010 python hash seed: * (glob)
1010 python hash seed: * (glob)
1011 [1]
1011 [1]
1012
1012
1013 test --tmpdir support
1013 test --tmpdir support
1014 $ rt --tmpdir=$TESTTMP/keep test-success.t
1014 $ rt --tmpdir=$TESTTMP/keep test-success.t
1015 running 1 tests using 1 parallel processes
1015 running 1 tests using 1 parallel processes
1016
1016
1017 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1017 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
1018 Keeping threadtmp dir: $TESTTMP/keep/child1
1018 Keeping threadtmp dir: $TESTTMP/keep/child1
1019 .
1019 .
1020 # Ran 1 tests, 0 skipped, 0 failed.
1020 # Ran 1 tests, 0 skipped, 0 failed.
1021
1021
1022 timeouts
1022 timeouts
1023 ========
1023 ========
1024 $ cat > test-timeout.t <<EOF
1024 $ cat > test-timeout.t <<EOF
1025 > $ sleep 2
1025 > $ sleep 2
1026 > $ echo pass
1026 > $ echo pass
1027 > pass
1027 > pass
1028 > EOF
1028 > EOF
1029 > echo '#require slow' > test-slow-timeout.t
1029 > echo '#require slow' > test-slow-timeout.t
1030 > cat test-timeout.t >> test-slow-timeout.t
1030 > cat test-timeout.t >> test-slow-timeout.t
1031 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1031 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1032 running 2 tests using 1 parallel processes
1032 running 2 tests using 1 parallel processes
1033 st
1033 st
1034 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1034 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1035 Failed test-timeout.t: timed out
1035 Failed test-timeout.t: timed out
1036 # Ran 1 tests, 1 skipped, 1 failed.
1036 # Ran 1 tests, 1 skipped, 1 failed.
1037 python hash seed: * (glob)
1037 python hash seed: * (glob)
1038 [1]
1038 [1]
1039 $ rt --timeout=1 --slowtimeout=3 \
1039 $ rt --timeout=1 --slowtimeout=3 \
1040 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1040 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1041 running 2 tests using 1 parallel processes
1041 running 2 tests using 1 parallel processes
1042 .t
1042 .t
1043 Failed test-timeout.t: timed out
1043 Failed test-timeout.t: timed out
1044 # Ran 2 tests, 0 skipped, 1 failed.
1044 # Ran 2 tests, 0 skipped, 1 failed.
1045 python hash seed: * (glob)
1045 python hash seed: * (glob)
1046 [1]
1046 [1]
1047 $ rm test-timeout.t test-slow-timeout.t
1047 $ rm test-timeout.t test-slow-timeout.t
1048
1048
1049 test for --time
1049 test for --time
1050 ==================
1050 ==================
1051
1051
1052 $ rt test-success.t --time
1052 $ rt test-success.t --time
1053 running 1 tests using 1 parallel processes
1053 running 1 tests using 1 parallel processes
1054 .
1054 .
1055 # Ran 1 tests, 0 skipped, 0 failed.
1055 # Ran 1 tests, 0 skipped, 0 failed.
1056 # Producing time report
1056 # Producing time report
1057 start end cuser csys real Test
1057 start end cuser csys real Test
1058 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1058 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1059
1059
1060 test for --time with --job enabled
1060 test for --time with --job enabled
1061 ====================================
1061 ====================================
1062
1062
1063 $ rt test-success.t --time --jobs 2
1063 $ rt test-success.t --time --jobs 2
1064 running 1 tests using 1 parallel processes
1064 running 1 tests using 1 parallel processes
1065 .
1065 .
1066 # Ran 1 tests, 0 skipped, 0 failed.
1066 # Ran 1 tests, 0 skipped, 0 failed.
1067 # Producing time report
1067 # Producing time report
1068 start end cuser csys real Test
1068 start end cuser csys real Test
1069 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1069 \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} \s*[\d\.]{5,8} test-success.t (re)
1070
1070
1071 Skips
1071 Skips
1072 ================
1072 ================
1073 $ cat > test-skip.t <<EOF
1073 $ cat > test-skip.t <<EOF
1074 > $ echo xyzzy
1074 > $ echo xyzzy
1075 > #if true
1075 > #if true
1076 > #require false
1076 > #require false
1077 > #end
1077 > #end
1078 > EOF
1078 > EOF
1079 $ cat > test-noskip.t <<EOF
1079 $ cat > test-noskip.t <<EOF
1080 > #if false
1080 > #if false
1081 > #require false
1081 > #require false
1082 > #endif
1082 > #endif
1083 > EOF
1083 > EOF
1084 $ rt --nodiff
1084 $ rt --nodiff
1085 running 4 tests using 1 parallel processes
1085 running 4 tests using 1 parallel processes
1086 !.s.
1086 !.s.
1087 Skipped test-skip.t: missing feature: nail clipper
1087 Skipped test-skip.t: missing feature: nail clipper
1088 Failed test-failure.t: output changed
1088 Failed test-failure.t: output changed
1089 # Ran 3 tests, 1 skipped, 1 failed.
1089 # Ran 3 tests, 1 skipped, 1 failed.
1090 python hash seed: * (glob)
1090 python hash seed: * (glob)
1091 [1]
1091 [1]
1092
1092
1093 $ rm test-noskip.t
1093 $ rm test-noskip.t
1094 $ rt --keyword xyzzy
1094 $ rt --keyword xyzzy
1095 running 3 tests using 1 parallel processes
1095 running 3 tests using 1 parallel processes
1096 .s
1096 .s
1097 Skipped test-skip.t: missing feature: nail clipper
1097 Skipped test-skip.t: missing feature: nail clipper
1098 # Ran 2 tests, 2 skipped, 0 failed.
1098 # Ran 2 tests, 2 skipped, 0 failed.
1099
1099
1100 Skips with xml
1100 Skips with xml
1101 $ rt --keyword xyzzy \
1101 $ rt --keyword xyzzy \
1102 > --xunit=xunit.xml
1102 > --xunit=xunit.xml
1103 running 3 tests using 1 parallel processes
1103 running 3 tests using 1 parallel processes
1104 .s
1104 .s
1105 Skipped test-skip.t: missing feature: nail clipper
1105 Skipped test-skip.t: missing feature: nail clipper
1106 # Ran 2 tests, 2 skipped, 0 failed.
1106 # Ran 2 tests, 2 skipped, 0 failed.
1107 $ cat xunit.xml
1107 $ cat xunit.xml
1108 <?xml version="1.0" encoding="utf-8"?>
1108 <?xml version="1.0" encoding="utf-8"?>
1109 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1109 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1110 <testcase name="test-success.t" time="*"/> (glob)
1110 <testcase name="test-success.t" time="*"/> (glob)
1111 <testcase name="test-skip.t">
1111 <testcase name="test-skip.t">
1112 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1112 <skipped><![CDATA[missing feature: nail clipper]]></skipped> (py38 !)
1113 <skipped> (no-py38 !)
1113 <skipped> (no-py38 !)
1114 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1114 <![CDATA[missing feature: nail clipper]]> </skipped> (no-py38 !)
1115 </testcase>
1115 </testcase>
1116 </testsuite>
1116 </testsuite>
1117
1117
1118 Missing skips or blacklisted skips don't count as executed:
1118 Missing skips or blacklisted skips don't count as executed:
1119 $ mkdir tests
1119 $ mkdir tests
1120 $ echo tests/test-failure.t > blacklist
1120 $ echo tests/test-failure.t > blacklist
1121 $ cp test-failure.t tests
1121 $ cp test-failure.t tests
1122 $ rt --blacklist=blacklist --json\
1122 $ rt --blacklist=blacklist --json\
1123 > tests/test-failure.t tests/test-bogus.t
1123 > tests/test-failure.t tests/test-bogus.t
1124 running 2 tests using 1 parallel processes
1124 running 2 tests using 1 parallel processes
1125 ss
1125 ss
1126 Skipped test-bogus.t: Doesn't exist
1126 Skipped test-bogus.t: Doesn't exist
1127 Skipped test-failure.t: blacklisted
1127 Skipped test-failure.t: blacklisted
1128 # Ran 0 tests, 2 skipped, 0 failed.
1128 # Ran 0 tests, 2 skipped, 0 failed.
1129 $ cat tests/report.json
1129 $ cat tests/report.json
1130 testreport ={
1130 testreport ={
1131 "test-bogus.t": {
1131 "test-bogus.t": {
1132 "result": "skip"
1132 "result": "skip"
1133 },
1133 },
1134 "test-failure.t": {
1134 "test-failure.t": {
1135 "result": "skip"
1135 "result": "skip"
1136 }
1136 }
1137 } (no-eol)
1137 } (no-eol)
1138 $ rm -r tests
1138 $ rm -r tests
1139 $ echo test-failure.t > blacklist
1139 $ echo test-failure.t > blacklist
1140
1140
1141 Whitelist trumps blacklist
1141 Whitelist trumps blacklist
1142 $ echo test-failure.t > whitelist
1142 $ echo test-failure.t > whitelist
1143 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1143 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1144 > test-failure.t test-bogus.t
1144 > test-failure.t test-bogus.t
1145 running 2 tests using 1 parallel processes
1145 running 2 tests using 1 parallel processes
1146 s
1146 s
1147 --- $TESTTMP/test-failure.t
1147 --- $TESTTMP/test-failure.t
1148 +++ $TESTTMP/test-failure.t.err
1148 +++ $TESTTMP/test-failure.t.err
1149 @@ -1,5 +1,5 @@
1149 @@ -1,5 +1,5 @@
1150 $ echo babar
1150 $ echo babar
1151 - rataxes
1151 - rataxes
1152 + babar
1152 + babar
1153 This is a noop statement so that
1153 This is a noop statement so that
1154 this test is still more bytes than success.
1154 this test is still more bytes than success.
1155 pad pad pad pad............................................................
1155 pad pad pad pad............................................................
1156
1156
1157 ERROR: test-failure.t output changed
1157 ERROR: test-failure.t output changed
1158 !
1158 !
1159 Skipped test-bogus.t: Doesn't exist
1159 Skipped test-bogus.t: Doesn't exist
1160 Failed test-failure.t: output changed
1160 Failed test-failure.t: output changed
1161 # Ran 1 tests, 1 skipped, 1 failed.
1161 # Ran 1 tests, 1 skipped, 1 failed.
1162 python hash seed: * (glob)
1162 python hash seed: * (glob)
1163 [1]
1163 [1]
1164
1164
1165 Ensure that --test-list causes only the tests listed in that file to
1165 Ensure that --test-list causes only the tests listed in that file to
1166 be executed.
1166 be executed.
1167 $ echo test-success.t >> onlytest
1167 $ echo test-success.t >> onlytest
1168 $ rt --test-list=onlytest
1168 $ rt --test-list=onlytest
1169 running 1 tests using 1 parallel processes
1169 running 1 tests using 1 parallel processes
1170 .
1170 .
1171 # Ran 1 tests, 0 skipped, 0 failed.
1171 # Ran 1 tests, 0 skipped, 0 failed.
1172 $ echo test-bogus.t >> anothertest
1172 $ echo test-bogus.t >> anothertest
1173 $ rt --test-list=onlytest --test-list=anothertest
1173 $ rt --test-list=onlytest --test-list=anothertest
1174 running 2 tests using 1 parallel processes
1174 running 2 tests using 1 parallel processes
1175 s.
1175 s.
1176 Skipped test-bogus.t: Doesn't exist
1176 Skipped test-bogus.t: Doesn't exist
1177 # Ran 1 tests, 1 skipped, 0 failed.
1177 # Ran 1 tests, 1 skipped, 0 failed.
1178 $ rm onlytest anothertest
1178 $ rm onlytest anothertest
1179
1179
1180 test for --json
1180 test for --json
1181 ==================
1181 ==================
1182
1182
1183 $ rt --json
1183 $ rt --json
1184 running 3 tests using 1 parallel processes
1184 running 3 tests using 1 parallel processes
1185
1185
1186 --- $TESTTMP/test-failure.t
1186 --- $TESTTMP/test-failure.t
1187 +++ $TESTTMP/test-failure.t.err
1187 +++ $TESTTMP/test-failure.t.err
1188 @@ -1,5 +1,5 @@
1188 @@ -1,5 +1,5 @@
1189 $ echo babar
1189 $ echo babar
1190 - rataxes
1190 - rataxes
1191 + babar
1191 + babar
1192 This is a noop statement so that
1192 This is a noop statement so that
1193 this test is still more bytes than success.
1193 this test is still more bytes than success.
1194 pad pad pad pad............................................................
1194 pad pad pad pad............................................................
1195
1195
1196 ERROR: test-failure.t output changed
1196 ERROR: test-failure.t output changed
1197 !.s
1197 !.s
1198 Skipped test-skip.t: missing feature: nail clipper
1198 Skipped test-skip.t: missing feature: nail clipper
1199 Failed test-failure.t: output changed
1199 Failed test-failure.t: output changed
1200 # Ran 2 tests, 1 skipped, 1 failed.
1200 # Ran 2 tests, 1 skipped, 1 failed.
1201 python hash seed: * (glob)
1201 python hash seed: * (glob)
1202 [1]
1202 [1]
1203
1203
1204 $ cat report.json
1204 $ cat report.json
1205 testreport ={
1205 testreport ={
1206 "test-failure.t": [\{] (re)
1206 "test-failure.t": [\{] (re)
1207 "csys": "\s*\d+\.\d{3,4}", ? (re)
1207 "csys": "\s*\d+\.\d{3,4}", ? (re)
1208 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1208 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1209 "diff": "---.+\+\+\+.+", ? (re)
1209 "diff": "---.+\+\+\+.+", ? (re)
1210 "end": "\s*\d+\.\d{3,4}", ? (re)
1210 "end": "\s*\d+\.\d{3,4}", ? (re)
1211 "result": "failure", ? (re)
1211 "result": "failure", ? (re)
1212 "start": "\s*\d+\.\d{3,4}", ? (re)
1212 "start": "\s*\d+\.\d{3,4}", ? (re)
1213 "time": "\s*\d+\.\d{3,4}" (re)
1213 "time": "\s*\d+\.\d{3,4}" (re)
1214 }, ? (re)
1214 }, ? (re)
1215 "test-skip.t": {
1215 "test-skip.t": {
1216 "csys": "\s*\d+\.\d{3,4}", ? (re)
1216 "csys": "\s*\d+\.\d{3,4}", ? (re)
1217 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1217 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1218 "diff": "", ? (re)
1218 "diff": "", ? (re)
1219 "end": "\s*\d+\.\d{3,4}", ? (re)
1219 "end": "\s*\d+\.\d{3,4}", ? (re)
1220 "result": "skip", ? (re)
1220 "result": "skip", ? (re)
1221 "start": "\s*\d+\.\d{3,4}", ? (re)
1221 "start": "\s*\d+\.\d{3,4}", ? (re)
1222 "time": "\s*\d+\.\d{3,4}" (re)
1222 "time": "\s*\d+\.\d{3,4}" (re)
1223 }, ? (re)
1223 }, ? (re)
1224 "test-success.t": [\{] (re)
1224 "test-success.t": [\{] (re)
1225 "csys": "\s*\d+\.\d{3,4}", ? (re)
1225 "csys": "\s*\d+\.\d{3,4}", ? (re)
1226 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1226 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1227 "diff": "", ? (re)
1227 "diff": "", ? (re)
1228 "end": "\s*\d+\.\d{3,4}", ? (re)
1228 "end": "\s*\d+\.\d{3,4}", ? (re)
1229 "result": "success", ? (re)
1229 "result": "success", ? (re)
1230 "start": "\s*\d+\.\d{3,4}", ? (re)
1230 "start": "\s*\d+\.\d{3,4}", ? (re)
1231 "time": "\s*\d+\.\d{3,4}" (re)
1231 "time": "\s*\d+\.\d{3,4}" (re)
1232 }
1232 }
1233 } (no-eol)
1233 } (no-eol)
1234 --json with --outputdir
1234 --json with --outputdir
1235
1235
1236 $ rm report.json
1236 $ rm report.json
1237 $ rm -r output
1237 $ rm -r output
1238 $ mkdir output
1238 $ mkdir output
1239 $ rt --json --outputdir output
1239 $ rt --json --outputdir output
1240 running 3 tests using 1 parallel processes
1240 running 3 tests using 1 parallel processes
1241
1241
1242 --- $TESTTMP/test-failure.t
1242 --- $TESTTMP/test-failure.t
1243 +++ $TESTTMP/output/test-failure.t.err
1243 +++ $TESTTMP/output/test-failure.t.err
1244 @@ -1,5 +1,5 @@
1244 @@ -1,5 +1,5 @@
1245 $ echo babar
1245 $ echo babar
1246 - rataxes
1246 - rataxes
1247 + babar
1247 + babar
1248 This is a noop statement so that
1248 This is a noop statement so that
1249 this test is still more bytes than success.
1249 this test is still more bytes than success.
1250 pad pad pad pad............................................................
1250 pad pad pad pad............................................................
1251
1251
1252 ERROR: test-failure.t output changed
1252 ERROR: test-failure.t output changed
1253 !.s
1253 !.s
1254 Skipped test-skip.t: missing feature: nail clipper
1254 Skipped test-skip.t: missing feature: nail clipper
1255 Failed test-failure.t: output changed
1255 Failed test-failure.t: output changed
1256 # Ran 2 tests, 1 skipped, 1 failed.
1256 # Ran 2 tests, 1 skipped, 1 failed.
1257 python hash seed: * (glob)
1257 python hash seed: * (glob)
1258 [1]
1258 [1]
1259 $ f report.json
1259 $ f report.json
1260 report.json: file not found
1260 report.json: file not found
1261 $ cat output/report.json
1261 $ cat output/report.json
1262 testreport ={
1262 testreport ={
1263 "test-failure.t": [\{] (re)
1263 "test-failure.t": [\{] (re)
1264 "csys": "\s*\d+\.\d{3,4}", ? (re)
1264 "csys": "\s*\d+\.\d{3,4}", ? (re)
1265 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1265 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1266 "diff": "---.+\+\+\+.+", ? (re)
1266 "diff": "---.+\+\+\+.+", ? (re)
1267 "end": "\s*\d+\.\d{3,4}", ? (re)
1267 "end": "\s*\d+\.\d{3,4}", ? (re)
1268 "result": "failure", ? (re)
1268 "result": "failure", ? (re)
1269 "start": "\s*\d+\.\d{3,4}", ? (re)
1269 "start": "\s*\d+\.\d{3,4}", ? (re)
1270 "time": "\s*\d+\.\d{3,4}" (re)
1270 "time": "\s*\d+\.\d{3,4}" (re)
1271 }, ? (re)
1271 }, ? (re)
1272 "test-skip.t": {
1272 "test-skip.t": {
1273 "csys": "\s*\d+\.\d{3,4}", ? (re)
1273 "csys": "\s*\d+\.\d{3,4}", ? (re)
1274 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1274 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1275 "diff": "", ? (re)
1275 "diff": "", ? (re)
1276 "end": "\s*\d+\.\d{3,4}", ? (re)
1276 "end": "\s*\d+\.\d{3,4}", ? (re)
1277 "result": "skip", ? (re)
1277 "result": "skip", ? (re)
1278 "start": "\s*\d+\.\d{3,4}", ? (re)
1278 "start": "\s*\d+\.\d{3,4}", ? (re)
1279 "time": "\s*\d+\.\d{3,4}" (re)
1279 "time": "\s*\d+\.\d{3,4}" (re)
1280 }, ? (re)
1280 }, ? (re)
1281 "test-success.t": [\{] (re)
1281 "test-success.t": [\{] (re)
1282 "csys": "\s*\d+\.\d{3,4}", ? (re)
1282 "csys": "\s*\d+\.\d{3,4}", ? (re)
1283 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1283 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1284 "diff": "", ? (re)
1284 "diff": "", ? (re)
1285 "end": "\s*\d+\.\d{3,4}", ? (re)
1285 "end": "\s*\d+\.\d{3,4}", ? (re)
1286 "result": "success", ? (re)
1286 "result": "success", ? (re)
1287 "start": "\s*\d+\.\d{3,4}", ? (re)
1287 "start": "\s*\d+\.\d{3,4}", ? (re)
1288 "time": "\s*\d+\.\d{3,4}" (re)
1288 "time": "\s*\d+\.\d{3,4}" (re)
1289 }
1289 }
1290 } (no-eol)
1290 } (no-eol)
1291 $ ls -a output
1291 $ ls -a output
1292 .
1292 .
1293 ..
1293 ..
1294 .testtimes
1294 .testtimes
1295 report.json
1295 report.json
1296 test-failure.t.err
1296 test-failure.t.err
1297
1297
1298 Test that failed test accepted through interactive are properly reported:
1298 Test that failed test accepted through interactive are properly reported:
1299
1299
1300 $ cp test-failure.t backup
1300 $ cp test-failure.t backup
1301 $ echo y | rt --json -i
1301 $ echo y | rt --json -i
1302 running 3 tests using 1 parallel processes
1302 running 3 tests using 1 parallel processes
1303
1303
1304 --- $TESTTMP/test-failure.t
1304 --- $TESTTMP/test-failure.t
1305 +++ $TESTTMP/test-failure.t.err
1305 +++ $TESTTMP/test-failure.t.err
1306 @@ -1,5 +1,5 @@
1306 @@ -1,5 +1,5 @@
1307 $ echo babar
1307 $ echo babar
1308 - rataxes
1308 - rataxes
1309 + babar
1309 + babar
1310 This is a noop statement so that
1310 This is a noop statement so that
1311 this test is still more bytes than success.
1311 this test is still more bytes than success.
1312 pad pad pad pad............................................................
1312 pad pad pad pad............................................................
1313 Accept this change? [y/N] ..s
1313 Accept this change? [y/N] ..s
1314 Skipped test-skip.t: missing feature: nail clipper
1314 Skipped test-skip.t: missing feature: nail clipper
1315 # Ran 2 tests, 1 skipped, 0 failed.
1315 # Ran 2 tests, 1 skipped, 0 failed.
1316
1316
1317 $ cat report.json
1317 $ cat report.json
1318 testreport ={
1318 testreport ={
1319 "test-failure.t": [\{] (re)
1319 "test-failure.t": [\{] (re)
1320 "csys": "\s*\d+\.\d{3,4}", ? (re)
1320 "csys": "\s*\d+\.\d{3,4}", ? (re)
1321 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1321 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1322 "diff": "", ? (re)
1322 "diff": "", ? (re)
1323 "end": "\s*\d+\.\d{3,4}", ? (re)
1323 "end": "\s*\d+\.\d{3,4}", ? (re)
1324 "result": "success", ? (re)
1324 "result": "success", ? (re)
1325 "start": "\s*\d+\.\d{3,4}", ? (re)
1325 "start": "\s*\d+\.\d{3,4}", ? (re)
1326 "time": "\s*\d+\.\d{3,4}" (re)
1326 "time": "\s*\d+\.\d{3,4}" (re)
1327 }, ? (re)
1327 }, ? (re)
1328 "test-skip.t": {
1328 "test-skip.t": {
1329 "csys": "\s*\d+\.\d{3,4}", ? (re)
1329 "csys": "\s*\d+\.\d{3,4}", ? (re)
1330 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1330 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1331 "diff": "", ? (re)
1331 "diff": "", ? (re)
1332 "end": "\s*\d+\.\d{3,4}", ? (re)
1332 "end": "\s*\d+\.\d{3,4}", ? (re)
1333 "result": "skip", ? (re)
1333 "result": "skip", ? (re)
1334 "start": "\s*\d+\.\d{3,4}", ? (re)
1334 "start": "\s*\d+\.\d{3,4}", ? (re)
1335 "time": "\s*\d+\.\d{3,4}" (re)
1335 "time": "\s*\d+\.\d{3,4}" (re)
1336 }, ? (re)
1336 }, ? (re)
1337 "test-success.t": [\{] (re)
1337 "test-success.t": [\{] (re)
1338 "csys": "\s*\d+\.\d{3,4}", ? (re)
1338 "csys": "\s*\d+\.\d{3,4}", ? (re)
1339 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1339 "cuser": "\s*\d+\.\d{3,4}", ? (re)
1340 "diff": "", ? (re)
1340 "diff": "", ? (re)
1341 "end": "\s*\d+\.\d{3,4}", ? (re)
1341 "end": "\s*\d+\.\d{3,4}", ? (re)
1342 "result": "success", ? (re)
1342 "result": "success", ? (re)
1343 "start": "\s*\d+\.\d{3,4}", ? (re)
1343 "start": "\s*\d+\.\d{3,4}", ? (re)
1344 "time": "\s*\d+\.\d{3,4}" (re)
1344 "time": "\s*\d+\.\d{3,4}" (re)
1345 }
1345 }
1346 } (no-eol)
1346 } (no-eol)
1347 $ mv backup test-failure.t
1347 $ mv backup test-failure.t
1348
1348
1349 backslash on end of line with glob matching is handled properly
1349 backslash on end of line with glob matching is handled properly
1350
1350
1351 $ cat > test-glob-backslash.t << EOF
1351 $ cat > test-glob-backslash.t << EOF
1352 > $ echo 'foo bar \\'
1352 > $ echo 'foo bar \\'
1353 > foo * \ (glob)
1353 > foo * \ (glob)
1354 > EOF
1354 > EOF
1355
1355
1356 $ rt test-glob-backslash.t
1356 $ rt test-glob-backslash.t
1357 running 1 tests using 1 parallel processes
1357 running 1 tests using 1 parallel processes
1358 .
1358 .
1359 # Ran 1 tests, 0 skipped, 0 failed.
1359 # Ran 1 tests, 0 skipped, 0 failed.
1360
1360
1361 $ rm -f test-glob-backslash.t
1361 $ rm -f test-glob-backslash.t
1362
1362
1363 Test globbing of local IP addresses
1363 Test globbing of local IP addresses
1364 $ echo 172.16.18.1
1364 $ echo 172.16.18.1
1365 $LOCALIP (glob)
1365 $LOCALIP (glob)
1366 $ echo dead:beef::1
1366 $ echo dead:beef::1
1367 $LOCALIP (glob)
1367 $LOCALIP (glob)
1368
1368
1369 Add support for external test formatter
1369 Add support for external test formatter
1370 =======================================
1370 =======================================
1371
1371
1372 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@" test-success.t test-failure.t
1372 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=$HGTEST_REAL_HG -j1 "$@" test-success.t test-failure.t
1373 running 2 tests using 1 parallel processes
1373 running 2 tests using 1 parallel processes
1374
1374
1375 # Ran 2 tests, 0 skipped, 0 failed.
1375 # Ran 2 tests, 0 skipped, 0 failed.
1376 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1376 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1377 FAILURE! test-failure.t output changed
1377 FAILURE! test-failure.t output changed
1378 SUCCESS! test-success.t
1378 SUCCESS! test-success.t
1379 ON_END!
1379 ON_END!
1380
1380
1381 Test reusability for third party tools
1381 Test reusability for third party tools
1382 ======================================
1382 ======================================
1383
1383
1384 $ THISTESTDIR="$TESTDIR"
1384 $ THISTESTDIR="$TESTDIR"
1385 $ export THISTESTDIR
1385 $ export THISTESTDIR
1386 $ THISTESTTMP="$TESTTMP"
1386 $ THISTESTTMP="$TESTTMP"
1387 $ export THISTESTTMP
1387 $ export THISTESTTMP
1388
1388
1389 #if windows
1389 #if windows
1390
1390
1391 $ NEWTESTDIR="$THISTESTTMP"\\anothertests
1391 $ NEWTESTDIR="$THISTESTTMP"\\anothertests
1392
1392
1393 #else
1393 #else
1394
1394
1395 $ NEWTESTDIR="$THISTESTTMP"/anothertests
1395 $ NEWTESTDIR="$THISTESTTMP"/anothertests
1396
1396
1397 #endif
1397 #endif
1398
1398
1399 $ export NEWTESTDIR
1399 $ export NEWTESTDIR
1400
1400
1401 $ echo creating some new test in: $NEWTESTDIR
1401 $ echo creating some new test in: $NEWTESTDIR
1402 creating some new test in: $TESTTMP\anothertests (windows !)
1402 creating some new test in: $TESTTMP\anothertests (windows !)
1403 creating some new test in: $TESTTMP/anothertests (no-windows !)
1403 creating some new test in: $TESTTMP/anothertests (no-windows !)
1404 $ mkdir "$NEWTESTDIR"
1404 $ mkdir "$NEWTESTDIR"
1405 $ cd "$NEWTESTDIR"
1405 $ cd "$NEWTESTDIR"
1406
1406
1407 test that `run-tests.py` can execute hghave, even if it runs not in
1407 test that `run-tests.py` can execute hghave, even if it runs not in
1408 Mercurial source tree.
1408 Mercurial source tree.
1409
1409
1410 $ cat > test-hghave.t <<EOF
1410 $ cat > test-hghave.t <<EOF
1411 > #require true
1411 > #require true
1412 > $ echo foo
1412 > $ echo foo
1413 > foo
1413 > foo
1414 > EOF
1414 > EOF
1415 $ rt test-hghave.t
1415 $ rt test-hghave.t
1416 running 1 tests using 1 parallel processes
1416 running 1 tests using 1 parallel processes
1417 .
1417 .
1418 # Ran 1 tests, 0 skipped, 0 failed.
1418 # Ran 1 tests, 0 skipped, 0 failed.
1419
1419
1420 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1420 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1421 running is placed.
1421 running is placed.
1422
1422
1423
1423
1424 $ cat > test-runtestdir.t <<EOF
1424 $ cat > test-runtestdir.t <<EOF
1425 > # \$THISTESTDIR, in which test-run-tests.t (this test file) is placed
1425 > # \$THISTESTDIR, in which test-run-tests.t (this test file) is placed
1426 > # \$THISTESTTMP, in which test-run-tests.t (this test file) is placed
1426 > # \$THISTESTTMP, in which test-run-tests.t (this test file) is placed
1427 > # \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1427 > # \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1428 > # \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1428 > # \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1429 >
1429 >
1430 > $ test "\$TESTDIR" = "\$NEWTESTDIR"
1430 > $ test "\$TESTDIR" = "\$NEWTESTDIR"
1431 > If this prints a path, that means RUNTESTDIR didn't equal
1431 > If this prints a path, that means RUNTESTDIR didn't equal
1432 > THISTESTDIR as it should have.
1432 > THISTESTDIR as it should have.
1433 > $ test "\$RUNTESTDIR" = "\$THISTESTDIR" || echo "\$RUNTESTDIR"
1433 > $ test "\$RUNTESTDIR" = "\$THISTESTDIR" || echo "\$RUNTESTDIR"
1434 > This should print the start of check-code. If this passes but the
1434 > This should print the start of check-code. If this passes but the
1435 > previous check failed, that means we found a copy of check-code at whatever
1435 > previous check failed, that means we found a copy of check-code at whatever
1436 > RUNTESTSDIR ended up containing, even though it doesn't match THISTESTDIR.
1436 > RUNTESTSDIR ended up containing, even though it doesn't match THISTESTDIR.
1437 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1437 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python3@#!USRBINENVPY@'
1438 > #!USRBINENVPY
1438 > #!USRBINENVPY
1439 > #
1439 > #
1440 > # check-code - a style and portability checker for Mercurial
1440 > # check-code - a style and portability checker for Mercurial
1441 > EOF
1441 > EOF
1442 $ rt test-runtestdir.t
1442 $ rt test-runtestdir.t
1443 running 1 tests using 1 parallel processes
1443 running 1 tests using 1 parallel processes
1444 .
1444 .
1445 # Ran 1 tests, 0 skipped, 0 failed.
1445 # Ran 1 tests, 0 skipped, 0 failed.
1446
1446
1447 #if execbit
1447 #if execbit
1448
1448
1449 test that TESTDIR is referred in PATH
1449 test that TESTDIR is referred in PATH
1450
1450
1451 $ cat > custom-command.sh <<EOF
1451 $ cat > custom-command.sh <<EOF
1452 > #!/bin/sh
1452 > #!/bin/sh
1453 > echo "hello world"
1453 > echo "hello world"
1454 > EOF
1454 > EOF
1455 $ chmod +x custom-command.sh
1455 $ chmod +x custom-command.sh
1456 $ cat > test-testdir-path.t <<EOF
1456 $ cat > test-testdir-path.t <<EOF
1457 > $ custom-command.sh
1457 > $ custom-command.sh
1458 > hello world
1458 > hello world
1459 > EOF
1459 > EOF
1460 $ rt test-testdir-path.t
1460 $ rt test-testdir-path.t
1461 running 1 tests using 1 parallel processes
1461 running 1 tests using 1 parallel processes
1462 .
1462 .
1463 # Ran 1 tests, 0 skipped, 0 failed.
1463 # Ran 1 tests, 0 skipped, 0 failed.
1464
1464
1465 #endif
1465 #endif
1466
1466
1467 test support for --allow-slow-tests
1467 test support for --allow-slow-tests
1468 $ cat > test-very-slow-test.t <<EOF
1468 $ cat > test-very-slow-test.t <<EOF
1469 > #require slow
1469 > #require slow
1470 > $ echo pass
1470 > $ echo pass
1471 > pass
1471 > pass
1472 > EOF
1472 > EOF
1473 $ rt test-very-slow-test.t
1473 $ rt test-very-slow-test.t
1474 running 1 tests using 1 parallel processes
1474 running 1 tests using 1 parallel processes
1475 s
1475 s
1476 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1476 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1477 # Ran 0 tests, 1 skipped, 0 failed.
1477 # Ran 0 tests, 1 skipped, 0 failed.
1478 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1478 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1479 running 1 tests using 1 parallel processes
1479 running 1 tests using 1 parallel processes
1480 .
1480 .
1481 # Ran 1 tests, 0 skipped, 0 failed.
1481 # Ran 1 tests, 0 skipped, 0 failed.
1482
1482
1483 support for running a test outside the current directory
1483 support for running a test outside the current directory
1484 $ mkdir nonlocal
1484 $ mkdir nonlocal
1485 $ cat > nonlocal/test-is-not-here.t << EOF
1485 $ cat > nonlocal/test-is-not-here.t << EOF
1486 > $ echo pass
1486 > $ echo pass
1487 > pass
1487 > pass
1488 > EOF
1488 > EOF
1489 $ rt nonlocal/test-is-not-here.t
1489 $ rt nonlocal/test-is-not-here.t
1490 running 1 tests using 1 parallel processes
1490 running 1 tests using 1 parallel processes
1491 .
1491 .
1492 # Ran 1 tests, 0 skipped, 0 failed.
1492 # Ran 1 tests, 0 skipped, 0 failed.
1493
1493
1494 support for automatically discovering test if arg is a folder
1494 support for automatically discovering test if arg is a folder
1495 $ mkdir tmp && cd tmp
1495 $ mkdir tmp && cd tmp
1496
1496
1497 $ cat > test-uno.t << EOF
1497 $ cat > test-uno.t << EOF
1498 > $ echo line
1498 > $ echo line
1499 > line
1499 > line
1500 > EOF
1500 > EOF
1501
1501
1502 $ cp test-uno.t test-dos.t
1502 $ cp test-uno.t test-dos.t
1503 $ cd ..
1503 $ cd ..
1504 $ cp -R tmp tmpp
1504 $ cp -R tmp tmpp
1505 $ cp tmp/test-uno.t test-solo.t
1505 $ cp tmp/test-uno.t test-solo.t
1506
1506
1507 $ rt tmp/ test-solo.t tmpp
1507 $ rt tmp/ test-solo.t tmpp
1508 running 5 tests using 1 parallel processes
1508 running 5 tests using 1 parallel processes
1509 .....
1509 .....
1510 # Ran 5 tests, 0 skipped, 0 failed.
1510 # Ran 5 tests, 0 skipped, 0 failed.
1511 $ rm -rf tmp tmpp
1511 $ rm -rf tmp tmpp
1512
1512
1513 support for running run-tests.py from another directory
1513 support for running run-tests.py from another directory
1514 $ mkdir tmp && cd tmp
1514 $ mkdir tmp && cd tmp
1515
1515
1516 $ cat > useful-file.sh << EOF
1516 $ cat > useful-file.sh << EOF
1517 > important command
1517 > important command
1518 > EOF
1518 > EOF
1519
1519
1520 $ cat > test-folder.t << EOF
1520 $ cat > test-folder.t << EOF
1521 > $ cat \$TESTDIR/useful-file.sh
1521 > $ cat \$TESTDIR/useful-file.sh
1522 > important command
1522 > important command
1523 > EOF
1523 > EOF
1524
1524
1525 $ cat > test-folder-fail.t << EOF
1525 $ cat > test-folder-fail.t << EOF
1526 > $ cat \$TESTDIR/useful-file.sh
1526 > $ cat \$TESTDIR/useful-file.sh
1527 > important commando
1527 > important commando
1528 > EOF
1528 > EOF
1529
1529
1530 $ cd ..
1530 $ cd ..
1531 $ rt tmp/test-*.t
1531 $ rt tmp/test-*.t
1532 running 2 tests using 1 parallel processes
1532 running 2 tests using 1 parallel processes
1533
1533
1534 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1534 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1535 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1535 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1536 @@ -1,2 +1,2 @@
1536 @@ -1,2 +1,2 @@
1537 $ cat $TESTDIR/useful-file.sh
1537 $ cat $TESTDIR/useful-file.sh
1538 - important commando
1538 - important commando
1539 + important command
1539 + important command
1540
1540
1541 ERROR: test-folder-fail.t output changed
1541 ERROR: test-folder-fail.t output changed
1542 !.
1542 !.
1543 Failed test-folder-fail.t: output changed
1543 Failed test-folder-fail.t: output changed
1544 # Ran 2 tests, 0 skipped, 1 failed.
1544 # Ran 2 tests, 0 skipped, 1 failed.
1545 python hash seed: * (glob)
1545 python hash seed: * (glob)
1546 [1]
1546 [1]
1547
1547
1548 support for bisecting failed tests automatically
1548 support for bisecting failed tests automatically
1549 $ hg init bisect
1549 $ hg init bisect
1550 $ cd bisect
1550 $ cd bisect
1551 $ cat >> test-bisect.t <<EOF
1551 $ cat >> test-bisect.t <<EOF
1552 > $ echo pass
1552 > $ echo pass
1553 > pass
1553 > pass
1554 > EOF
1554 > EOF
1555 $ hg add test-bisect.t
1555 $ hg add test-bisect.t
1556 $ hg ci -m 'good'
1556 $ hg ci -m 'good'
1557 $ cat >> test-bisect.t <<EOF
1557 $ cat >> test-bisect.t <<EOF
1558 > $ echo pass
1558 > $ echo pass
1559 > fail
1559 > fail
1560 > EOF
1560 > EOF
1561 $ hg ci -m 'bad'
1561 $ hg ci -m 'bad'
1562 $ rt --known-good-rev=0 test-bisect.t
1562 $ rt --known-good-rev=0 test-bisect.t
1563 running 1 tests using 1 parallel processes
1563 running 1 tests using 1 parallel processes
1564
1564
1565 --- $TESTTMP/anothertests/bisect/test-bisect.t
1565 --- $TESTTMP/anothertests/bisect/test-bisect.t
1566 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1566 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1567 @@ -1,4 +1,4 @@
1567 @@ -1,4 +1,4 @@
1568 $ echo pass
1568 $ echo pass
1569 pass
1569 pass
1570 $ echo pass
1570 $ echo pass
1571 - fail
1571 - fail
1572 + pass
1572 + pass
1573
1573
1574 ERROR: test-bisect.t output changed
1574 ERROR: test-bisect.t output changed
1575 !
1575 !
1576 Failed test-bisect.t: output changed
1576 Failed test-bisect.t: output changed
1577 test-bisect.t broken by 72cbf122d116 (bad)
1577 test-bisect.t broken by 72cbf122d116 (bad)
1578 # Ran 1 tests, 0 skipped, 1 failed.
1578 # Ran 1 tests, 0 skipped, 1 failed.
1579 python hash seed: * (glob)
1579 python hash seed: * (glob)
1580 [1]
1580 [1]
1581
1581
1582 $ cd ..
1582 $ cd ..
1583
1583
1584 support bisecting a separate repo
1584 support bisecting a separate repo
1585
1585
1586 $ hg init bisect-dependent
1586 $ hg init bisect-dependent
1587 $ cd bisect-dependent
1587 $ cd bisect-dependent
1588 $ cat > test-bisect-dependent.t <<EOF
1588 $ cat > test-bisect-dependent.t <<EOF
1589 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1589 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1590 > pass
1590 > pass
1591 > EOF
1591 > EOF
1592 $ hg commit -Am dependent test-bisect-dependent.t
1592 $ hg commit -Am dependent test-bisect-dependent.t
1593
1593
1594 $ rt --known-good-rev=0 test-bisect-dependent.t
1594 $ rt --known-good-rev=0 test-bisect-dependent.t
1595 running 1 tests using 1 parallel processes
1595 running 1 tests using 1 parallel processes
1596
1596
1597 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1597 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1598 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1598 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1599 @@ -1,2 +1,2 @@
1599 @@ -1,2 +1,2 @@
1600 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1600 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1601 - pass
1601 - pass
1602 + fail
1602 + fail
1603
1603
1604 ERROR: test-bisect-dependent.t output changed
1604 ERROR: test-bisect-dependent.t output changed
1605 !
1605 !
1606 Failed test-bisect-dependent.t: output changed
1606 Failed test-bisect-dependent.t: output changed
1607 Failed to identify failure point for test-bisect-dependent.t
1607 Failed to identify failure point for test-bisect-dependent.t
1608 # Ran 1 tests, 0 skipped, 1 failed.
1608 # Ran 1 tests, 0 skipped, 1 failed.
1609 python hash seed: * (glob)
1609 python hash seed: * (glob)
1610 [1]
1610 [1]
1611
1611
1612 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1612 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1613 usage: run-tests.py [options] [tests]
1613 usage: run-tests.py [options] [tests]
1614 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1614 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1615 [2]
1615 [2]
1616
1616
1617 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1617 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1618 running 1 tests using 1 parallel processes
1618 running 1 tests using 1 parallel processes
1619
1619
1620 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1620 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1621 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1621 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1622 @@ -1,2 +1,2 @@
1622 @@ -1,2 +1,2 @@
1623 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1623 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1624 - pass
1624 - pass
1625 + fail
1625 + fail
1626
1626
1627 ERROR: test-bisect-dependent.t output changed
1627 ERROR: test-bisect-dependent.t output changed
1628 !
1628 !
1629 Failed test-bisect-dependent.t: output changed
1629 Failed test-bisect-dependent.t: output changed
1630 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1630 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1631 # Ran 1 tests, 0 skipped, 1 failed.
1631 # Ran 1 tests, 0 skipped, 1 failed.
1632 python hash seed: * (glob)
1632 python hash seed: * (glob)
1633 [1]
1633 [1]
1634
1634
1635 $ cd ..
1635 $ cd ..
1636
1636
1637 Test a broken #if statement doesn't break run-tests threading.
1637 Test a broken #if statement doesn't break run-tests threading.
1638 ==============================================================
1638 ==============================================================
1639 $ mkdir broken
1639 $ mkdir broken
1640 $ cd broken
1640 $ cd broken
1641 $ cat > test-broken.t <<EOF
1641 $ cat > test-broken.t <<EOF
1642 > true
1642 > true
1643 > #if notarealhghavefeature
1643 > #if notarealhghavefeature
1644 > $ false
1644 > $ false
1645 > #endif
1645 > #endif
1646 > EOF
1646 > EOF
1647 $ for f in 1 2 3 4 ; do
1647 $ for f in 1 2 3 4 ; do
1648 > cat > test-works-$f.t <<EOF
1648 > cat > test-works-$f.t <<EOF
1649 > This is test case $f
1649 > This is test case $f
1650 > $ sleep 1
1650 > $ sleep 1
1651 > EOF
1651 > EOF
1652 > done
1652 > done
1653 $ rt -j 2
1653 $ rt -j 2
1654 running 5 tests using 2 parallel processes
1654 running 5 tests using 2 parallel processes
1655 ....
1655 ....
1656 # Ran 5 tests, 0 skipped, 0 failed.
1656 # Ran 5 tests, 0 skipped, 0 failed.
1657 skipped: unknown feature: notarealhghavefeature
1657 skipped: unknown feature: notarealhghavefeature
1658
1658
1659 $ cd ..
1659 $ cd ..
1660 $ rm -rf broken
1660 $ rm -rf broken
1661
1661
1662 Test cases in .t files
1662 Test cases in .t files
1663 ======================
1663 ======================
1664 $ mkdir cases
1664 $ mkdir cases
1665 $ cd cases
1665 $ cd cases
1666 $ cat > test-cases-abc.t <<'EOF'
1666 $ cat > test-cases-abc.t <<'EOF'
1667 > #testcases A B C
1667 > #testcases A B C
1668 > $ V=B
1668 > $ V=B
1669 > #if A
1669 > #if A
1670 > $ V=A
1670 > $ V=A
1671 > #endif
1671 > #endif
1672 > #if C
1672 > #if C
1673 > $ V=C
1673 > $ V=C
1674 > #endif
1674 > #endif
1675 > $ echo $V | sed 's/A/C/'
1675 > $ echo $V | sed 's/A/C/'
1676 > C
1676 > C
1677 > #if C
1677 > #if C
1678 > $ [ $V = C ]
1678 > $ [ $V = C ]
1679 > #endif
1679 > #endif
1680 > #if A
1680 > #if A
1681 > $ [ $V = C ]
1681 > $ [ $V = C ]
1682 > [1]
1682 > [1]
1683 > #endif
1683 > #endif
1684 > #if no-C
1684 > #if no-C
1685 > $ [ $V = C ]
1685 > $ [ $V = C ]
1686 > [1]
1686 > [1]
1687 > #endif
1687 > #endif
1688 > $ [ $V = D ]
1688 > $ [ $V = D ]
1689 > [1]
1689 > [1]
1690 > EOF
1690 > EOF
1691 $ rt
1691 $ rt
1692 running 3 tests using 1 parallel processes
1692 running 3 tests using 1 parallel processes
1693 .
1693 .
1694 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1694 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1695 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1695 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1696 @@ -7,7 +7,7 @@
1696 @@ -7,7 +7,7 @@
1697 $ V=C
1697 $ V=C
1698 #endif
1698 #endif
1699 $ echo $V | sed 's/A/C/'
1699 $ echo $V | sed 's/A/C/'
1700 - C
1700 - C
1701 + B
1701 + B
1702 #if C
1702 #if C
1703 $ [ $V = C ]
1703 $ [ $V = C ]
1704 #endif
1704 #endif
1705
1705
1706 ERROR: test-cases-abc.t#B output changed
1706 ERROR: test-cases-abc.t#B output changed
1707 !.
1707 !.
1708 Failed test-cases-abc.t#B: output changed
1708 Failed test-cases-abc.t#B: output changed
1709 # Ran 3 tests, 0 skipped, 1 failed.
1709 # Ran 3 tests, 0 skipped, 1 failed.
1710 python hash seed: * (glob)
1710 python hash seed: * (glob)
1711 [1]
1711 [1]
1712
1712
1713 --restart works
1713 --restart works
1714
1714
1715 $ rt --restart
1715 $ rt --restart
1716 running 2 tests using 1 parallel processes
1716 running 2 tests using 1 parallel processes
1717
1717
1718 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1718 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1719 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1719 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1720 @@ -7,7 +7,7 @@
1720 @@ -7,7 +7,7 @@
1721 $ V=C
1721 $ V=C
1722 #endif
1722 #endif
1723 $ echo $V | sed 's/A/C/'
1723 $ echo $V | sed 's/A/C/'
1724 - C
1724 - C
1725 + B
1725 + B
1726 #if C
1726 #if C
1727 $ [ $V = C ]
1727 $ [ $V = C ]
1728 #endif
1728 #endif
1729
1729
1730 ERROR: test-cases-abc.t#B output changed
1730 ERROR: test-cases-abc.t#B output changed
1731 !.
1731 !.
1732 Failed test-cases-abc.t#B: output changed
1732 Failed test-cases-abc.t#B: output changed
1733 # Ran 2 tests, 0 skipped, 1 failed.
1733 # Ran 2 tests, 0 skipped, 1 failed.
1734 python hash seed: * (glob)
1734 python hash seed: * (glob)
1735 [1]
1735 [1]
1736
1736
1737 --restart works with outputdir
1737 --restart works with outputdir
1738
1738
1739 $ mkdir output
1739 $ mkdir output
1740 $ mv test-cases-abc.t#B.err output
1740 $ mv test-cases-abc.t#B.err output
1741 $ rt --restart --outputdir output
1741 $ rt --restart --outputdir output
1742 running 2 tests using 1 parallel processes
1742 running 2 tests using 1 parallel processes
1743
1743
1744 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1744 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1745 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1745 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1746 @@ -7,7 +7,7 @@
1746 @@ -7,7 +7,7 @@
1747 $ V=C
1747 $ V=C
1748 #endif
1748 #endif
1749 $ echo $V | sed 's/A/C/'
1749 $ echo $V | sed 's/A/C/'
1750 - C
1750 - C
1751 + B
1751 + B
1752 #if C
1752 #if C
1753 $ [ $V = C ]
1753 $ [ $V = C ]
1754 #endif
1754 #endif
1755
1755
1756 ERROR: test-cases-abc.t#B output changed
1756 ERROR: test-cases-abc.t#B output changed
1757 !.
1757 !.
1758 Failed test-cases-abc.t#B: output changed
1758 Failed test-cases-abc.t#B: output changed
1759 # Ran 2 tests, 0 skipped, 1 failed.
1759 # Ran 2 tests, 0 skipped, 1 failed.
1760 python hash seed: * (glob)
1760 python hash seed: * (glob)
1761 [1]
1761 [1]
1762
1762
1763 Test TESTCASE variable
1763 Test TESTCASE variable
1764
1764
1765 $ cat > test-cases-ab.t <<'EOF'
1765 $ cat > test-cases-ab.t <<'EOF'
1766 > $ dostuff() {
1766 > $ dostuff() {
1767 > > echo "In case $TESTCASE"
1767 > > echo "In case $TESTCASE"
1768 > > }
1768 > > }
1769 > #testcases A B
1769 > #testcases A B
1770 > #if A
1770 > #if A
1771 > $ dostuff
1771 > $ dostuff
1772 > In case A
1772 > In case A
1773 > #endif
1773 > #endif
1774 > #if B
1774 > #if B
1775 > $ dostuff
1775 > $ dostuff
1776 > In case B
1776 > In case B
1777 > #endif
1777 > #endif
1778 > EOF
1778 > EOF
1779 $ rt test-cases-ab.t
1779 $ rt test-cases-ab.t
1780 running 2 tests using 1 parallel processes
1780 running 2 tests using 1 parallel processes
1781 ..
1781 ..
1782 # Ran 2 tests, 0 skipped, 0 failed.
1782 # Ran 2 tests, 0 skipped, 0 failed.
1783
1783
1784 Support running a specific test case
1784 Support running a specific test case
1785
1785
1786 $ rt "test-cases-abc.t#B"
1786 $ rt "test-cases-abc.t#B"
1787 running 1 tests using 1 parallel processes
1787 running 1 tests using 1 parallel processes
1788
1788
1789 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1789 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1790 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1790 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1791 @@ -7,7 +7,7 @@
1791 @@ -7,7 +7,7 @@
1792 $ V=C
1792 $ V=C
1793 #endif
1793 #endif
1794 $ echo $V | sed 's/A/C/'
1794 $ echo $V | sed 's/A/C/'
1795 - C
1795 - C
1796 + B
1796 + B
1797 #if C
1797 #if C
1798 $ [ $V = C ]
1798 $ [ $V = C ]
1799 #endif
1799 #endif
1800
1800
1801 ERROR: test-cases-abc.t#B output changed
1801 ERROR: test-cases-abc.t#B output changed
1802 !
1802 !
1803 Failed test-cases-abc.t#B: output changed
1803 Failed test-cases-abc.t#B: output changed
1804 # Ran 1 tests, 0 skipped, 1 failed.
1804 # Ran 1 tests, 0 skipped, 1 failed.
1805 python hash seed: * (glob)
1805 python hash seed: * (glob)
1806 [1]
1806 [1]
1807
1807
1808 Support running multiple test cases in the same file
1808 Support running multiple test cases in the same file
1809
1809
1810 $ rt test-cases-abc.t#B test-cases-abc.t#C
1810 $ rt test-cases-abc.t#B test-cases-abc.t#C
1811 running 2 tests using 1 parallel processes
1811 running 2 tests using 1 parallel processes
1812
1812
1813 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1813 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1814 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1814 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1815 @@ -7,7 +7,7 @@
1815 @@ -7,7 +7,7 @@
1816 $ V=C
1816 $ V=C
1817 #endif
1817 #endif
1818 $ echo $V | sed 's/A/C/'
1818 $ echo $V | sed 's/A/C/'
1819 - C
1819 - C
1820 + B
1820 + B
1821 #if C
1821 #if C
1822 $ [ $V = C ]
1822 $ [ $V = C ]
1823 #endif
1823 #endif
1824
1824
1825 ERROR: test-cases-abc.t#B output changed
1825 ERROR: test-cases-abc.t#B output changed
1826 !.
1826 !.
1827 Failed test-cases-abc.t#B: output changed
1827 Failed test-cases-abc.t#B: output changed
1828 # Ran 2 tests, 0 skipped, 1 failed.
1828 # Ran 2 tests, 0 skipped, 1 failed.
1829 python hash seed: * (glob)
1829 python hash seed: * (glob)
1830 [1]
1830 [1]
1831
1831
1832 Support ignoring invalid test cases
1832 Support ignoring invalid test cases
1833
1833
1834 $ rt test-cases-abc.t#B test-cases-abc.t#D
1834 $ rt test-cases-abc.t#B test-cases-abc.t#D
1835 running 1 tests using 1 parallel processes
1835 running 1 tests using 1 parallel processes
1836
1836
1837 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1837 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1838 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1838 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1839 @@ -7,7 +7,7 @@
1839 @@ -7,7 +7,7 @@
1840 $ V=C
1840 $ V=C
1841 #endif
1841 #endif
1842 $ echo $V | sed 's/A/C/'
1842 $ echo $V | sed 's/A/C/'
1843 - C
1843 - C
1844 + B
1844 + B
1845 #if C
1845 #if C
1846 $ [ $V = C ]
1846 $ [ $V = C ]
1847 #endif
1847 #endif
1848
1848
1849 ERROR: test-cases-abc.t#B output changed
1849 ERROR: test-cases-abc.t#B output changed
1850 !
1850 !
1851 Failed test-cases-abc.t#B: output changed
1851 Failed test-cases-abc.t#B: output changed
1852 # Ran 1 tests, 0 skipped, 1 failed.
1852 # Ran 1 tests, 0 skipped, 1 failed.
1853 python hash seed: * (glob)
1853 python hash seed: * (glob)
1854 [1]
1854 [1]
1855
1855
1856 Support running complex test cases names
1856 Support running complex test cases names
1857
1857
1858 $ cat > test-cases-advanced-cases.t <<'EOF'
1858 $ cat > test-cases-advanced-cases.t <<'EOF'
1859 > #testcases simple case-with-dashes casewith_-.chars
1859 > #testcases simple case-with-dashes casewith_-.chars
1860 > $ echo $TESTCASE
1860 > $ echo $TESTCASE
1861 > simple
1861 > simple
1862 > EOF
1862 > EOF
1863
1863
1864 $ cat test-cases-advanced-cases.t
1864 $ cat test-cases-advanced-cases.t
1865 #testcases simple case-with-dashes casewith_-.chars
1865 #testcases simple case-with-dashes casewith_-.chars
1866 $ echo $TESTCASE
1866 $ echo $TESTCASE
1867 simple
1867 simple
1868
1868
1869 $ rt test-cases-advanced-cases.t
1869 $ rt test-cases-advanced-cases.t
1870 running 3 tests using 1 parallel processes
1870 running 3 tests using 1 parallel processes
1871
1871
1872 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1872 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1873 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1873 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1874 @@ -1,3 +1,3 @@
1874 @@ -1,3 +1,3 @@
1875 #testcases simple case-with-dashes casewith_-.chars
1875 #testcases simple case-with-dashes casewith_-.chars
1876 $ echo $TESTCASE
1876 $ echo $TESTCASE
1877 - simple
1877 - simple
1878 + case-with-dashes
1878 + case-with-dashes
1879
1879
1880 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1880 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1881 !
1881 !
1882 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1882 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1883 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1883 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1884 @@ -1,3 +1,3 @@
1884 @@ -1,3 +1,3 @@
1885 #testcases simple case-with-dashes casewith_-.chars
1885 #testcases simple case-with-dashes casewith_-.chars
1886 $ echo $TESTCASE
1886 $ echo $TESTCASE
1887 - simple
1887 - simple
1888 + casewith_-.chars
1888 + casewith_-.chars
1889
1889
1890 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1890 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1891 !.
1891 !.
1892 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1892 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1893 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1893 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1894 # Ran 3 tests, 0 skipped, 2 failed.
1894 # Ran 3 tests, 0 skipped, 2 failed.
1895 python hash seed: * (glob)
1895 python hash seed: * (glob)
1896 [1]
1896 [1]
1897
1897
1898 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1898 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1899 running 1 tests using 1 parallel processes
1899 running 1 tests using 1 parallel processes
1900
1900
1901 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1901 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1902 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1902 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1903 @@ -1,3 +1,3 @@
1903 @@ -1,3 +1,3 @@
1904 #testcases simple case-with-dashes casewith_-.chars
1904 #testcases simple case-with-dashes casewith_-.chars
1905 $ echo $TESTCASE
1905 $ echo $TESTCASE
1906 - simple
1906 - simple
1907 + case-with-dashes
1907 + case-with-dashes
1908
1908
1909 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1909 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1910 !
1910 !
1911 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1911 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1912 # Ran 1 tests, 0 skipped, 1 failed.
1912 # Ran 1 tests, 0 skipped, 1 failed.
1913 python hash seed: * (glob)
1913 python hash seed: * (glob)
1914 [1]
1914 [1]
1915
1915
1916 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1916 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1917 running 1 tests using 1 parallel processes
1917 running 1 tests using 1 parallel processes
1918
1918
1919 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1919 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1920 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1920 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1921 @@ -1,3 +1,3 @@
1921 @@ -1,3 +1,3 @@
1922 #testcases simple case-with-dashes casewith_-.chars
1922 #testcases simple case-with-dashes casewith_-.chars
1923 $ echo $TESTCASE
1923 $ echo $TESTCASE
1924 - simple
1924 - simple
1925 + casewith_-.chars
1925 + casewith_-.chars
1926
1926
1927 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1927 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1928 !
1928 !
1929 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1929 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1930 # Ran 1 tests, 0 skipped, 1 failed.
1930 # Ran 1 tests, 0 skipped, 1 failed.
1931 python hash seed: * (glob)
1931 python hash seed: * (glob)
1932 [1]
1932 [1]
1933
1933
1934 Test automatic pattern replacement
1934 Test automatic pattern replacement
1935 ==================================
1935 ==================================
1936
1936
1937 $ cat << EOF >> common-pattern.py
1937 $ cat << EOF >> common-pattern.py
1938 > substitutions = [
1938 > substitutions = [
1939 > (br'foo-(.*)\\b',
1939 > (br'foo-(.*)\\b',
1940 > br'\$XXX=\\1\$'),
1940 > br'\$XXX=\\1\$'),
1941 > (br'bar\\n',
1941 > (br'bar\\n',
1942 > br'\$YYY$\\n'),
1942 > br'\$YYY$\\n'),
1943 > ]
1943 > ]
1944 > EOF
1944 > EOF
1945
1945
1946 $ cat << EOF >> test-substitution.t
1946 $ cat << EOF >> test-substitution.t
1947 > $ echo foo-12
1947 > $ echo foo-12
1948 > \$XXX=12$
1948 > \$XXX=12$
1949 > $ echo foo-42
1949 > $ echo foo-42
1950 > \$XXX=42$
1950 > \$XXX=42$
1951 > $ echo bar prior
1951 > $ echo bar prior
1952 > bar prior
1952 > bar prior
1953 > $ echo lastbar
1953 > $ echo lastbar
1954 > last\$YYY$
1954 > last\$YYY$
1955 > $ echo foo-bar foo-baz
1955 > $ echo foo-bar foo-baz
1956 > EOF
1956 > EOF
1957
1957
1958 $ rt test-substitution.t
1958 $ rt test-substitution.t
1959 running 1 tests using 1 parallel processes
1959 running 1 tests using 1 parallel processes
1960
1960
1961 --- $TESTTMP/anothertests/cases/test-substitution.t
1961 --- $TESTTMP/anothertests/cases/test-substitution.t
1962 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1962 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1963 @@ -7,3 +7,4 @@
1963 @@ -7,3 +7,4 @@
1964 $ echo lastbar
1964 $ echo lastbar
1965 last$YYY$
1965 last$YYY$
1966 $ echo foo-bar foo-baz
1966 $ echo foo-bar foo-baz
1967 + $XXX=bar foo-baz$
1967 + $XXX=bar foo-baz$
1968
1968
1969 ERROR: test-substitution.t output changed
1969 ERROR: test-substitution.t output changed
1970 !
1970 !
1971 Failed test-substitution.t: output changed
1971 Failed test-substitution.t: output changed
1972 # Ran 1 tests, 0 skipped, 1 failed.
1972 # Ran 1 tests, 0 skipped, 1 failed.
1973 python hash seed: * (glob)
1973 python hash seed: * (glob)
1974 [1]
1974 [1]
1975
1975
1976 --extra-config-opt works
1976 --extra-config-opt works
1977
1977
1978 $ cat << EOF >> test-config-opt.t
1978 $ cat << EOF >> test-config-opt.t
1979 > $ hg init test-config-opt
1979 > $ hg init test-config-opt
1980 > $ hg -R test-config-opt purge
1980 > $ hg -R test-config-opt purge
1981 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1981 > $ echo "HGTESTEXTRAEXTENSIONS: \$HGTESTEXTRAEXTENSIONS"
1982 > HGTESTEXTRAEXTENSIONS: purge
1982 > HGTESTEXTRAEXTENSIONS: purge
1983 > EOF
1983 > EOF
1984
1984
1985 $ rt --extra-config-opt extensions.purge= \
1985 $ rt --extra-config-opt extensions.purge= \
1986 > --extra-config-opt not.an.extension=True test-config-opt.t
1986 > --extra-config-opt not.an.extension=True test-config-opt.t
1987 running 1 tests using 1 parallel processes
1987 running 1 tests using 1 parallel processes
1988 .
1988 .
1989 # Ran 1 tests, 0 skipped, 0 failed.
1989 # Ran 1 tests, 0 skipped, 0 failed.
1990
1990
1991 Test conditional output matching
1991 Test conditional output matching
1992 ================================
1992 ================================
1993
1993
1994 $ cat << EOF >> test-conditional-matching.t
1994 $ cat << EOF >> test-conditional-matching.t
1995 > #testcases foo bar
1995 > #testcases foo bar
1996 > $ echo richtig
1996 > $ echo richtig
1997 > richtig (true !)
1997 > richtig (true !)
1998 > $ echo falsch
1998 > $ echo falsch
1999 > falsch (false !)
1999 > falsch (false !)
2000 > #if foo
2000 > #if foo
2001 > $ echo arthur
2001 > $ echo arthur
2002 > arthur (bar !)
2002 > arthur (bar !)
2003 > #endif
2003 > #endif
2004 > $ echo celeste
2004 > $ echo celeste
2005 > celeste (foo !)
2005 > celeste (foo !)
2006 > $ echo zephir
2006 > $ echo zephir
2007 > zephir (bar !)
2007 > zephir (bar !)
2008 > EOF
2008 > EOF
2009
2009
2010 $ rt test-conditional-matching.t
2010 $ rt test-conditional-matching.t
2011 running 2 tests using 1 parallel processes
2011 running 2 tests using 1 parallel processes
2012
2012
2013 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2013 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2014 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
2014 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#bar.err
2015 @@ -3,11 +3,13 @@
2015 @@ -3,11 +3,13 @@
2016 richtig (true !)
2016 richtig (true !)
2017 $ echo falsch
2017 $ echo falsch
2018 falsch (false !)
2018 falsch (false !)
2019 + falsch
2019 + falsch
2020 #if foo
2020 #if foo
2021 $ echo arthur
2021 $ echo arthur
2022 arthur \(bar !\) (re)
2022 arthur \(bar !\) (re)
2023 #endif
2023 #endif
2024 $ echo celeste
2024 $ echo celeste
2025 celeste \(foo !\) (re)
2025 celeste \(foo !\) (re)
2026 + celeste
2026 + celeste
2027 $ echo zephir
2027 $ echo zephir
2028 zephir \(bar !\) (re)
2028 zephir \(bar !\) (re)
2029
2029
2030 ERROR: test-conditional-matching.t#bar output changed
2030 ERROR: test-conditional-matching.t#bar output changed
2031 !
2031 !
2032 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2032 --- $TESTTMP/anothertests/cases/test-conditional-matching.t
2033 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2033 +++ $TESTTMP/anothertests/cases/test-conditional-matching.t#foo.err
2034 @@ -3,11 +3,14 @@
2034 @@ -3,11 +3,14 @@
2035 richtig (true !)
2035 richtig (true !)
2036 $ echo falsch
2036 $ echo falsch
2037 falsch (false !)
2037 falsch (false !)
2038 + falsch
2038 + falsch
2039 #if foo
2039 #if foo
2040 $ echo arthur
2040 $ echo arthur
2041 arthur \(bar !\) (re)
2041 arthur \(bar !\) (re)
2042 + arthur
2042 + arthur
2043 #endif
2043 #endif
2044 $ echo celeste
2044 $ echo celeste
2045 celeste \(foo !\) (re)
2045 celeste \(foo !\) (re)
2046 $ echo zephir
2046 $ echo zephir
2047 zephir \(bar !\) (re)
2047 zephir \(bar !\) (re)
2048 + zephir
2048 + zephir
2049
2049
2050 ERROR: test-conditional-matching.t#foo output changed
2050 ERROR: test-conditional-matching.t#foo output changed
2051 !
2051 !
2052 Failed test-conditional-matching.t#bar: output changed
2052 Failed test-conditional-matching.t#bar: output changed
2053 Failed test-conditional-matching.t#foo: output changed
2053 Failed test-conditional-matching.t#foo: output changed
2054 # Ran 2 tests, 0 skipped, 2 failed.
2054 # Ran 2 tests, 0 skipped, 2 failed.
2055 python hash seed: * (glob)
2055 python hash seed: * (glob)
2056 [1]
2056 [1]
2057
2057
2058 Test that a proper "python" has been set up
2058 Test that a proper "python" has been set up
2059 ===========================================
2059 ===========================================
2060
2060
2061 (with a small check-code work around)
2061 (with a small check-code work around)
2062 $ printf "#!/usr/bi" > test-py3.tmp
2062 $ printf "#!/usr/bi" > test-py3.tmp
2063 $ printf "n/en" >> test-py3.tmp
2063 $ printf "n/en" >> test-py3.tmp
2064 $ cat << EOF >> test-py3.tmp
2064 $ cat << EOF >> test-py3.tmp
2065 > v python3
2065 > v python3
2066 > import sys
2066 > import sys
2067 > print('.'.join(str(x) for x in sys.version_info))
2067 > print('.'.join(str(x) for x in sys.version_info))
2068 > EOF
2068 > EOF
2069 $ mv test-py3.tmp test-py3.py
2069 $ mv test-py3.tmp test-py3.py
2070 $ chmod +x test-py3.py
2070 $ chmod +x test-py3.py
2071
2071
2072 (with a small check-code work around)
2072 (with a small check-code work around)
2073 $ printf "#!/usr/bi" > test-py.tmp
2073 $ printf "#!/usr/bi" > test-py.tmp
2074 $ printf "n/en" >> test-py.tmp
2074 $ printf "n/en" >> test-py.tmp
2075 $ cat << EOF >> test-py.tmp
2075 $ cat << EOF >> test-py.tmp
2076 > v python
2076 > v python
2077 > import sys
2077 > import sys
2078 > print('.'.join(str(x) for x in sys.version_info))
2078 > print('.'.join(str(x) for x in sys.version_info))
2079 > EOF
2079 > EOF
2080 $ mv test-py.tmp test-py.py
2080 $ mv test-py.tmp test-py.py
2081 $ chmod +x test-py.py
2081 $ chmod +x test-py.py
2082
2082
2083 $ ./test-py3.py
2083 $ ./test-py3.py
2084 3.* (glob)
2084 3.* (glob)
2085 $ ./test-py.py
2085 $ ./test-py.py
2086 2.* (glob) (no-py3 !)
2086 2.* (glob) (no-py3 !)
2087 3.* (glob) (py3 !)
2087 3.* (glob) (py3 !)
General Comments 0
You need to be logged in to leave comments. Login now