##// END OF EJS Templates
run-tests: alias hg to hg.exe on Windows...
Matt Harbison -
r41011:2465e0b2 default
parent child Browse files
Show More
@@ -1,3272 +1,3275 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 # 10) parallel, pure, tests that call run-tests:
38 # 10) parallel, pure, tests that call run-tests:
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
39 # ./run-tests.py --pure `grep -l run-tests.py *.t`
40 #
40 #
41 # (You could use any subset of the tests: test-s* happens to match
41 # (You could use any subset of the tests: test-s* happens to match
42 # enough that it's worth doing parallel runs, few enough that it
42 # enough that it's worth doing parallel runs, few enough that it
43 # completes fairly quickly, includes both shell and Python scripts, and
43 # completes fairly quickly, includes both shell and Python scripts, and
44 # includes some scripts that run daemon processes.)
44 # includes some scripts that run daemon processes.)
45
45
46 from __future__ import absolute_import, print_function
46 from __future__ import absolute_import, print_function
47
47
48 import argparse
48 import argparse
49 import collections
49 import collections
50 import difflib
50 import difflib
51 import distutils.version as version
51 import distutils.version as version
52 import errno
52 import errno
53 import json
53 import json
54 import multiprocessing
54 import multiprocessing
55 import os
55 import os
56 import random
56 import random
57 import re
57 import re
58 import shutil
58 import shutil
59 import signal
59 import signal
60 import socket
60 import socket
61 import subprocess
61 import subprocess
62 import sys
62 import sys
63 import sysconfig
63 import sysconfig
64 import tempfile
64 import tempfile
65 import threading
65 import threading
66 import time
66 import time
67 import unittest
67 import unittest
68 import uuid
68 import uuid
69 import xml.dom.minidom as minidom
69 import xml.dom.minidom as minidom
70
70
71 try:
71 try:
72 import Queue as queue
72 import Queue as queue
73 except ImportError:
73 except ImportError:
74 import queue
74 import queue
75
75
76 try:
76 try:
77 import shlex
77 import shlex
78 shellquote = shlex.quote
78 shellquote = shlex.quote
79 except (ImportError, AttributeError):
79 except (ImportError, AttributeError):
80 import pipes
80 import pipes
81 shellquote = pipes.quote
81 shellquote = pipes.quote
82
82
83 if os.environ.get('RTUNICODEPEDANTRY', False):
83 if os.environ.get('RTUNICODEPEDANTRY', False):
84 try:
84 try:
85 reload(sys)
85 reload(sys)
86 sys.setdefaultencoding("undefined")
86 sys.setdefaultencoding("undefined")
87 except NameError:
87 except NameError:
88 pass
88 pass
89
89
90 processlock = threading.Lock()
90 processlock = threading.Lock()
91
91
92 pygmentspresent = False
92 pygmentspresent = False
93 # ANSI color is unsupported prior to Windows 10
93 # ANSI color is unsupported prior to Windows 10
94 if os.name != 'nt':
94 if os.name != 'nt':
95 try: # is pygments installed
95 try: # is pygments installed
96 import pygments
96 import pygments
97 import pygments.lexers as lexers
97 import pygments.lexers as lexers
98 import pygments.lexer as lexer
98 import pygments.lexer as lexer
99 import pygments.formatters as formatters
99 import pygments.formatters as formatters
100 import pygments.token as token
100 import pygments.token as token
101 import pygments.style as style
101 import pygments.style as style
102 pygmentspresent = True
102 pygmentspresent = True
103 difflexer = lexers.DiffLexer()
103 difflexer = lexers.DiffLexer()
104 terminal256formatter = formatters.Terminal256Formatter()
104 terminal256formatter = formatters.Terminal256Formatter()
105 except ImportError:
105 except ImportError:
106 pass
106 pass
107
107
108 if pygmentspresent:
108 if pygmentspresent:
109 class TestRunnerStyle(style.Style):
109 class TestRunnerStyle(style.Style):
110 default_style = ""
110 default_style = ""
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
111 skipped = token.string_to_tokentype("Token.Generic.Skipped")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
112 failed = token.string_to_tokentype("Token.Generic.Failed")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
113 skippedname = token.string_to_tokentype("Token.Generic.SName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
114 failedname = token.string_to_tokentype("Token.Generic.FName")
115 styles = {
115 styles = {
116 skipped: '#e5e5e5',
116 skipped: '#e5e5e5',
117 skippedname: '#00ffff',
117 skippedname: '#00ffff',
118 failed: '#7f0000',
118 failed: '#7f0000',
119 failedname: '#ff0000',
119 failedname: '#ff0000',
120 }
120 }
121
121
122 class TestRunnerLexer(lexer.RegexLexer):
122 class TestRunnerLexer(lexer.RegexLexer):
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
123 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
124 tokens = {
124 tokens = {
125 'root': [
125 'root': [
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
126 (r'^Skipped', token.Generic.Skipped, 'skipped'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
127 (r'^Failed ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
128 (r'^ERROR: ', token.Generic.Failed, 'failed'),
129 ],
129 ],
130 'skipped': [
130 'skipped': [
131 (testpattern, token.Generic.SName),
131 (testpattern, token.Generic.SName),
132 (r':.*', token.Generic.Skipped),
132 (r':.*', token.Generic.Skipped),
133 ],
133 ],
134 'failed': [
134 'failed': [
135 (testpattern, token.Generic.FName),
135 (testpattern, token.Generic.FName),
136 (r'(:| ).*', token.Generic.Failed),
136 (r'(:| ).*', token.Generic.Failed),
137 ]
137 ]
138 }
138 }
139
139
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
140 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
141 runnerlexer = TestRunnerLexer()
141 runnerlexer = TestRunnerLexer()
142
142
143 origenviron = os.environ.copy()
143 origenviron = os.environ.copy()
144
144
145 if sys.version_info > (3, 5, 0):
145 if sys.version_info > (3, 5, 0):
146 PYTHON3 = True
146 PYTHON3 = True
147 xrange = range # we use xrange in one place, and we'd rather not use range
147 xrange = range # we use xrange in one place, and we'd rather not use range
148 def _bytespath(p):
148 def _bytespath(p):
149 if p is None:
149 if p is None:
150 return p
150 return p
151 return p.encode('utf-8')
151 return p.encode('utf-8')
152
152
153 def _strpath(p):
153 def _strpath(p):
154 if p is None:
154 if p is None:
155 return p
155 return p
156 return p.decode('utf-8')
156 return p.decode('utf-8')
157
157
158 osenvironb = getattr(os, 'environb', None)
158 osenvironb = getattr(os, 'environb', None)
159 if osenvironb is None:
159 if osenvironb is None:
160 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # Windows lacks os.environb, for instance. A proxy over the real thing
161 # instead of a copy allows the environment to be updated via bytes on
161 # instead of a copy allows the environment to be updated via bytes on
162 # all platforms.
162 # all platforms.
163 class environbytes(object):
163 class environbytes(object):
164 def __init__(self, strenv):
164 def __init__(self, strenv):
165 self.__len__ = strenv.__len__
165 self.__len__ = strenv.__len__
166 self.clear = strenv.clear
166 self.clear = strenv.clear
167 self._strenv = strenv
167 self._strenv = strenv
168 def __getitem__(self, k):
168 def __getitem__(self, k):
169 v = self._strenv.__getitem__(_strpath(k))
169 v = self._strenv.__getitem__(_strpath(k))
170 return _bytespath(v)
170 return _bytespath(v)
171 def __setitem__(self, k, v):
171 def __setitem__(self, k, v):
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
172 self._strenv.__setitem__(_strpath(k), _strpath(v))
173 def __delitem__(self, k):
173 def __delitem__(self, k):
174 self._strenv.__delitem__(_strpath(k))
174 self._strenv.__delitem__(_strpath(k))
175 def __contains__(self, k):
175 def __contains__(self, k):
176 return self._strenv.__contains__(_strpath(k))
176 return self._strenv.__contains__(_strpath(k))
177 def __iter__(self):
177 def __iter__(self):
178 return iter([_bytespath(k) for k in iter(self._strenv)])
178 return iter([_bytespath(k) for k in iter(self._strenv)])
179 def get(self, k, default=None):
179 def get(self, k, default=None):
180 v = self._strenv.get(_strpath(k), _strpath(default))
180 v = self._strenv.get(_strpath(k), _strpath(default))
181 return _bytespath(v)
181 return _bytespath(v)
182 def pop(self, k, default=None):
182 def pop(self, k, default=None):
183 v = self._strenv.pop(_strpath(k), _strpath(default))
183 v = self._strenv.pop(_strpath(k), _strpath(default))
184 return _bytespath(v)
184 return _bytespath(v)
185
185
186 osenvironb = environbytes(os.environ)
186 osenvironb = environbytes(os.environ)
187
187
188 getcwdb = getattr(os, 'getcwdb')
188 getcwdb = getattr(os, 'getcwdb')
189 if not getcwdb or os.name == 'nt':
189 if not getcwdb or os.name == 'nt':
190 getcwdb = lambda: _bytespath(os.getcwd())
190 getcwdb = lambda: _bytespath(os.getcwd())
191
191
192 elif sys.version_info >= (3, 0, 0):
192 elif sys.version_info >= (3, 0, 0):
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
193 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
194 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
195 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
196 else:
196 else:
197 PYTHON3 = False
197 PYTHON3 = False
198
198
199 # In python 2.x, path operations are generally done using
199 # In python 2.x, path operations are generally done using
200 # bytestrings by default, so we don't have to do any extra
200 # bytestrings by default, so we don't have to do any extra
201 # fiddling there. We define the wrapper functions anyway just to
201 # fiddling there. We define the wrapper functions anyway just to
202 # help keep code consistent between platforms.
202 # help keep code consistent between platforms.
203 def _bytespath(p):
203 def _bytespath(p):
204 return p
204 return p
205
205
206 _strpath = _bytespath
206 _strpath = _bytespath
207 osenvironb = os.environ
207 osenvironb = os.environ
208 getcwdb = os.getcwd
208 getcwdb = os.getcwd
209
209
210 # For Windows support
210 # For Windows support
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
211 wifexited = getattr(os, "WIFEXITED", lambda x: False)
212
212
213 # Whether to use IPv6
213 # Whether to use IPv6
214 def checksocketfamily(name, port=20058):
214 def checksocketfamily(name, port=20058):
215 """return true if we can listen on localhost using family=name
215 """return true if we can listen on localhost using family=name
216
216
217 name should be either 'AF_INET', or 'AF_INET6'.
217 name should be either 'AF_INET', or 'AF_INET6'.
218 port being used is okay - EADDRINUSE is considered as successful.
218 port being used is okay - EADDRINUSE is considered as successful.
219 """
219 """
220 family = getattr(socket, name, None)
220 family = getattr(socket, name, None)
221 if family is None:
221 if family is None:
222 return False
222 return False
223 try:
223 try:
224 s = socket.socket(family, socket.SOCK_STREAM)
224 s = socket.socket(family, socket.SOCK_STREAM)
225 s.bind(('localhost', port))
225 s.bind(('localhost', port))
226 s.close()
226 s.close()
227 return True
227 return True
228 except socket.error as exc:
228 except socket.error as exc:
229 if exc.errno == errno.EADDRINUSE:
229 if exc.errno == errno.EADDRINUSE:
230 return True
230 return True
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
231 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
232 return False
232 return False
233 else:
233 else:
234 raise
234 raise
235 else:
235 else:
236 return False
236 return False
237
237
238 # useipv6 will be set by parseargs
238 # useipv6 will be set by parseargs
239 useipv6 = None
239 useipv6 = None
240
240
241 def checkportisavailable(port):
241 def checkportisavailable(port):
242 """return true if a port seems free to bind on localhost"""
242 """return true if a port seems free to bind on localhost"""
243 if useipv6:
243 if useipv6:
244 family = socket.AF_INET6
244 family = socket.AF_INET6
245 else:
245 else:
246 family = socket.AF_INET
246 family = socket.AF_INET
247 try:
247 try:
248 s = socket.socket(family, socket.SOCK_STREAM)
248 s = socket.socket(family, socket.SOCK_STREAM)
249 s.bind(('localhost', port))
249 s.bind(('localhost', port))
250 s.close()
250 s.close()
251 return True
251 return True
252 except socket.error as exc:
252 except socket.error as exc:
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
253 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
254 errno.EPROTONOSUPPORT):
254 errno.EPROTONOSUPPORT):
255 raise
255 raise
256 return False
256 return False
257
257
258 closefds = os.name == 'posix'
258 closefds = os.name == 'posix'
259 def Popen4(cmd, wd, timeout, env=None):
259 def Popen4(cmd, wd, timeout, env=None):
260 processlock.acquire()
260 processlock.acquire()
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
261 p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
262 cwd=_strpath(wd), env=env,
262 cwd=_strpath(wd), env=env,
263 close_fds=closefds,
263 close_fds=closefds,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
264 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
265 stderr=subprocess.STDOUT)
265 stderr=subprocess.STDOUT)
266 processlock.release()
266 processlock.release()
267
267
268 p.fromchild = p.stdout
268 p.fromchild = p.stdout
269 p.tochild = p.stdin
269 p.tochild = p.stdin
270 p.childerr = p.stderr
270 p.childerr = p.stderr
271
271
272 p.timeout = False
272 p.timeout = False
273 if timeout:
273 if timeout:
274 def t():
274 def t():
275 start = time.time()
275 start = time.time()
276 while time.time() - start < timeout and p.returncode is None:
276 while time.time() - start < timeout and p.returncode is None:
277 time.sleep(.1)
277 time.sleep(.1)
278 p.timeout = True
278 p.timeout = True
279 if p.returncode is None:
279 if p.returncode is None:
280 terminate(p)
280 terminate(p)
281 threading.Thread(target=t).start()
281 threading.Thread(target=t).start()
282
282
283 return p
283 return p
284
284
285 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
285 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
286 IMPL_PATH = b'PYTHONPATH'
286 IMPL_PATH = b'PYTHONPATH'
287 if 'java' in sys.platform:
287 if 'java' in sys.platform:
288 IMPL_PATH = b'JYTHONPATH'
288 IMPL_PATH = b'JYTHONPATH'
289
289
290 defaults = {
290 defaults = {
291 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
291 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
292 'timeout': ('HGTEST_TIMEOUT', 180),
292 'timeout': ('HGTEST_TIMEOUT', 180),
293 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
293 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
294 'port': ('HGTEST_PORT', 20059),
294 'port': ('HGTEST_PORT', 20059),
295 'shell': ('HGTEST_SHELL', 'sh'),
295 'shell': ('HGTEST_SHELL', 'sh'),
296 }
296 }
297
297
298 def canonpath(path):
298 def canonpath(path):
299 return os.path.realpath(os.path.expanduser(path))
299 return os.path.realpath(os.path.expanduser(path))
300
300
301 def parselistfiles(files, listtype, warn=True):
301 def parselistfiles(files, listtype, warn=True):
302 entries = dict()
302 entries = dict()
303 for filename in files:
303 for filename in files:
304 try:
304 try:
305 path = os.path.expanduser(os.path.expandvars(filename))
305 path = os.path.expanduser(os.path.expandvars(filename))
306 f = open(path, "rb")
306 f = open(path, "rb")
307 except IOError as err:
307 except IOError as err:
308 if err.errno != errno.ENOENT:
308 if err.errno != errno.ENOENT:
309 raise
309 raise
310 if warn:
310 if warn:
311 print("warning: no such %s file: %s" % (listtype, filename))
311 print("warning: no such %s file: %s" % (listtype, filename))
312 continue
312 continue
313
313
314 for line in f.readlines():
314 for line in f.readlines():
315 line = line.split(b'#', 1)[0].strip()
315 line = line.split(b'#', 1)[0].strip()
316 if line:
316 if line:
317 entries[line] = filename
317 entries[line] = filename
318
318
319 f.close()
319 f.close()
320 return entries
320 return entries
321
321
322 def parsettestcases(path):
322 def parsettestcases(path):
323 """read a .t test file, return a set of test case names
323 """read a .t test file, return a set of test case names
324
324
325 If path does not exist, return an empty set.
325 If path does not exist, return an empty set.
326 """
326 """
327 cases = []
327 cases = []
328 try:
328 try:
329 with open(path, 'rb') as f:
329 with open(path, 'rb') as f:
330 for l in f:
330 for l in f:
331 if l.startswith(b'#testcases '):
331 if l.startswith(b'#testcases '):
332 cases.append(sorted(l[11:].split()))
332 cases.append(sorted(l[11:].split()))
333 except IOError as ex:
333 except IOError as ex:
334 if ex.errno != errno.ENOENT:
334 if ex.errno != errno.ENOENT:
335 raise
335 raise
336 return cases
336 return cases
337
337
338 def getparser():
338 def getparser():
339 """Obtain the OptionParser used by the CLI."""
339 """Obtain the OptionParser used by the CLI."""
340 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
340 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
341
341
342 selection = parser.add_argument_group('Test Selection')
342 selection = parser.add_argument_group('Test Selection')
343 selection.add_argument('--allow-slow-tests', action='store_true',
343 selection.add_argument('--allow-slow-tests', action='store_true',
344 help='allow extremely slow tests')
344 help='allow extremely slow tests')
345 selection.add_argument("--blacklist", action="append",
345 selection.add_argument("--blacklist", action="append",
346 help="skip tests listed in the specified blacklist file")
346 help="skip tests listed in the specified blacklist file")
347 selection.add_argument("--changed",
347 selection.add_argument("--changed",
348 help="run tests that are changed in parent rev or working directory")
348 help="run tests that are changed in parent rev or working directory")
349 selection.add_argument("-k", "--keywords",
349 selection.add_argument("-k", "--keywords",
350 help="run tests matching keywords")
350 help="run tests matching keywords")
351 selection.add_argument("-r", "--retest", action="store_true",
351 selection.add_argument("-r", "--retest", action="store_true",
352 help = "retest failed tests")
352 help = "retest failed tests")
353 selection.add_argument("--test-list", action="append",
353 selection.add_argument("--test-list", action="append",
354 help="read tests to run from the specified file")
354 help="read tests to run from the specified file")
355 selection.add_argument("--whitelist", action="append",
355 selection.add_argument("--whitelist", action="append",
356 help="always run tests listed in the specified whitelist file")
356 help="always run tests listed in the specified whitelist file")
357 selection.add_argument('tests', metavar='TESTS', nargs='*',
357 selection.add_argument('tests', metavar='TESTS', nargs='*',
358 help='Tests to run')
358 help='Tests to run')
359
359
360 harness = parser.add_argument_group('Test Harness Behavior')
360 harness = parser.add_argument_group('Test Harness Behavior')
361 harness.add_argument('--bisect-repo',
361 harness.add_argument('--bisect-repo',
362 metavar='bisect_repo',
362 metavar='bisect_repo',
363 help=("Path of a repo to bisect. Use together with "
363 help=("Path of a repo to bisect. Use together with "
364 "--known-good-rev"))
364 "--known-good-rev"))
365 harness.add_argument("-d", "--debug", action="store_true",
365 harness.add_argument("-d", "--debug", action="store_true",
366 help="debug mode: write output of test scripts to console"
366 help="debug mode: write output of test scripts to console"
367 " rather than capturing and diffing it (disables timeout)")
367 " rather than capturing and diffing it (disables timeout)")
368 harness.add_argument("-f", "--first", action="store_true",
368 harness.add_argument("-f", "--first", action="store_true",
369 help="exit on the first test failure")
369 help="exit on the first test failure")
370 harness.add_argument("-i", "--interactive", action="store_true",
370 harness.add_argument("-i", "--interactive", action="store_true",
371 help="prompt to accept changed output")
371 help="prompt to accept changed output")
372 harness.add_argument("-j", "--jobs", type=int,
372 harness.add_argument("-j", "--jobs", type=int,
373 help="number of jobs to run in parallel"
373 help="number of jobs to run in parallel"
374 " (default: $%s or %d)" % defaults['jobs'])
374 " (default: $%s or %d)" % defaults['jobs'])
375 harness.add_argument("--keep-tmpdir", action="store_true",
375 harness.add_argument("--keep-tmpdir", action="store_true",
376 help="keep temporary directory after running tests")
376 help="keep temporary directory after running tests")
377 harness.add_argument('--known-good-rev',
377 harness.add_argument('--known-good-rev',
378 metavar="known_good_rev",
378 metavar="known_good_rev",
379 help=("Automatically bisect any failures using this "
379 help=("Automatically bisect any failures using this "
380 "revision as a known-good revision."))
380 "revision as a known-good revision."))
381 harness.add_argument("--list-tests", action="store_true",
381 harness.add_argument("--list-tests", action="store_true",
382 help="list tests instead of running them")
382 help="list tests instead of running them")
383 harness.add_argument("--loop", action="store_true",
383 harness.add_argument("--loop", action="store_true",
384 help="loop tests repeatedly")
384 help="loop tests repeatedly")
385 harness.add_argument('--random', action="store_true",
385 harness.add_argument('--random', action="store_true",
386 help='run tests in random order')
386 help='run tests in random order')
387 harness.add_argument('--order-by-runtime', action="store_true",
387 harness.add_argument('--order-by-runtime', action="store_true",
388 help='run slowest tests first, according to .testtimes')
388 help='run slowest tests first, according to .testtimes')
389 harness.add_argument("-p", "--port", type=int,
389 harness.add_argument("-p", "--port", type=int,
390 help="port on which servers should listen"
390 help="port on which servers should listen"
391 " (default: $%s or %d)" % defaults['port'])
391 " (default: $%s or %d)" % defaults['port'])
392 harness.add_argument('--profile-runner', action='store_true',
392 harness.add_argument('--profile-runner', action='store_true',
393 help='run statprof on run-tests')
393 help='run statprof on run-tests')
394 harness.add_argument("-R", "--restart", action="store_true",
394 harness.add_argument("-R", "--restart", action="store_true",
395 help="restart at last error")
395 help="restart at last error")
396 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
396 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
397 help="run each test N times (default=1)", default=1)
397 help="run each test N times (default=1)", default=1)
398 harness.add_argument("--shell",
398 harness.add_argument("--shell",
399 help="shell to use (default: $%s or %s)" % defaults['shell'])
399 help="shell to use (default: $%s or %s)" % defaults['shell'])
400 harness.add_argument('--showchannels', action='store_true',
400 harness.add_argument('--showchannels', action='store_true',
401 help='show scheduling channels')
401 help='show scheduling channels')
402 harness.add_argument("--slowtimeout", type=int,
402 harness.add_argument("--slowtimeout", type=int,
403 help="kill errant slow tests after SLOWTIMEOUT seconds"
403 help="kill errant slow tests after SLOWTIMEOUT seconds"
404 " (default: $%s or %d)" % defaults['slowtimeout'])
404 " (default: $%s or %d)" % defaults['slowtimeout'])
405 harness.add_argument("-t", "--timeout", type=int,
405 harness.add_argument("-t", "--timeout", type=int,
406 help="kill errant tests after TIMEOUT seconds"
406 help="kill errant tests after TIMEOUT seconds"
407 " (default: $%s or %d)" % defaults['timeout'])
407 " (default: $%s or %d)" % defaults['timeout'])
408 harness.add_argument("--tmpdir",
408 harness.add_argument("--tmpdir",
409 help="run tests in the given temporary directory"
409 help="run tests in the given temporary directory"
410 " (implies --keep-tmpdir)")
410 " (implies --keep-tmpdir)")
411 harness.add_argument("-v", "--verbose", action="store_true",
411 harness.add_argument("-v", "--verbose", action="store_true",
412 help="output verbose messages")
412 help="output verbose messages")
413
413
414 hgconf = parser.add_argument_group('Mercurial Configuration')
414 hgconf = parser.add_argument_group('Mercurial Configuration')
415 hgconf.add_argument("--chg", action="store_true",
415 hgconf.add_argument("--chg", action="store_true",
416 help="install and use chg wrapper in place of hg")
416 help="install and use chg wrapper in place of hg")
417 hgconf.add_argument("--compiler",
417 hgconf.add_argument("--compiler",
418 help="compiler to build with")
418 help="compiler to build with")
419 hgconf.add_argument('--extra-config-opt', action="append", default=[],
419 hgconf.add_argument('--extra-config-opt', action="append", default=[],
420 help='set the given config opt in the test hgrc')
420 help='set the given config opt in the test hgrc')
421 hgconf.add_argument("-l", "--local", action="store_true",
421 hgconf.add_argument("-l", "--local", action="store_true",
422 help="shortcut for --with-hg=<testdir>/../hg, "
422 help="shortcut for --with-hg=<testdir>/../hg, "
423 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
423 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
424 hgconf.add_argument("--ipv6", action="store_true",
424 hgconf.add_argument("--ipv6", action="store_true",
425 help="prefer IPv6 to IPv4 for network related tests")
425 help="prefer IPv6 to IPv4 for network related tests")
426 hgconf.add_argument("--pure", action="store_true",
426 hgconf.add_argument("--pure", action="store_true",
427 help="use pure Python code instead of C extensions")
427 help="use pure Python code instead of C extensions")
428 hgconf.add_argument("-3", "--py3-warnings", action="store_true",
428 hgconf.add_argument("-3", "--py3-warnings", action="store_true",
429 help="enable Py3k warnings on Python 2.7+")
429 help="enable Py3k warnings on Python 2.7+")
430 hgconf.add_argument("--with-chg", metavar="CHG",
430 hgconf.add_argument("--with-chg", metavar="CHG",
431 help="use specified chg wrapper in place of hg")
431 help="use specified chg wrapper in place of hg")
432 hgconf.add_argument("--with-hg",
432 hgconf.add_argument("--with-hg",
433 metavar="HG",
433 metavar="HG",
434 help="test using specified hg script rather than a "
434 help="test using specified hg script rather than a "
435 "temporary installation")
435 "temporary installation")
436
436
437 reporting = parser.add_argument_group('Results Reporting')
437 reporting = parser.add_argument_group('Results Reporting')
438 reporting.add_argument("-C", "--annotate", action="store_true",
438 reporting.add_argument("-C", "--annotate", action="store_true",
439 help="output files annotated with coverage")
439 help="output files annotated with coverage")
440 reporting.add_argument("--color", choices=["always", "auto", "never"],
440 reporting.add_argument("--color", choices=["always", "auto", "never"],
441 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
441 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
442 help="colorisation: always|auto|never (default: auto)")
442 help="colorisation: always|auto|never (default: auto)")
443 reporting.add_argument("-c", "--cover", action="store_true",
443 reporting.add_argument("-c", "--cover", action="store_true",
444 help="print a test coverage report")
444 help="print a test coverage report")
445 reporting.add_argument('--exceptions', action='store_true',
445 reporting.add_argument('--exceptions', action='store_true',
446 help='log all exceptions and generate an exception report')
446 help='log all exceptions and generate an exception report')
447 reporting.add_argument("-H", "--htmlcov", action="store_true",
447 reporting.add_argument("-H", "--htmlcov", action="store_true",
448 help="create an HTML report of the coverage of the files")
448 help="create an HTML report of the coverage of the files")
449 reporting.add_argument("--json", action="store_true",
449 reporting.add_argument("--json", action="store_true",
450 help="store test result data in 'report.json' file")
450 help="store test result data in 'report.json' file")
451 reporting.add_argument("--outputdir",
451 reporting.add_argument("--outputdir",
452 help="directory to write error logs to (default=test directory)")
452 help="directory to write error logs to (default=test directory)")
453 reporting.add_argument("-n", "--nodiff", action="store_true",
453 reporting.add_argument("-n", "--nodiff", action="store_true",
454 help="skip showing test changes")
454 help="skip showing test changes")
455 reporting.add_argument("-S", "--noskips", action="store_true",
455 reporting.add_argument("-S", "--noskips", action="store_true",
456 help="don't report skip tests verbosely")
456 help="don't report skip tests verbosely")
457 reporting.add_argument("--time", action="store_true",
457 reporting.add_argument("--time", action="store_true",
458 help="time how long each test takes")
458 help="time how long each test takes")
459 reporting.add_argument("--view",
459 reporting.add_argument("--view",
460 help="external diff viewer")
460 help="external diff viewer")
461 reporting.add_argument("--xunit",
461 reporting.add_argument("--xunit",
462 help="record xunit results at specified path")
462 help="record xunit results at specified path")
463
463
464 for option, (envvar, default) in defaults.items():
464 for option, (envvar, default) in defaults.items():
465 defaults[option] = type(default)(os.environ.get(envvar, default))
465 defaults[option] = type(default)(os.environ.get(envvar, default))
466 parser.set_defaults(**defaults)
466 parser.set_defaults(**defaults)
467
467
468 return parser
468 return parser
469
469
470 def parseargs(args, parser):
470 def parseargs(args, parser):
471 """Parse arguments with our OptionParser and validate results."""
471 """Parse arguments with our OptionParser and validate results."""
472 options = parser.parse_args(args)
472 options = parser.parse_args(args)
473
473
474 # jython is always pure
474 # jython is always pure
475 if 'java' in sys.platform or '__pypy__' in sys.modules:
475 if 'java' in sys.platform or '__pypy__' in sys.modules:
476 options.pure = True
476 options.pure = True
477
477
478 if options.with_hg:
478 if options.with_hg:
479 options.with_hg = canonpath(_bytespath(options.with_hg))
479 options.with_hg = canonpath(_bytespath(options.with_hg))
480 if not (os.path.isfile(options.with_hg) and
480 if not (os.path.isfile(options.with_hg) and
481 os.access(options.with_hg, os.X_OK)):
481 os.access(options.with_hg, os.X_OK)):
482 parser.error('--with-hg must specify an executable hg script')
482 parser.error('--with-hg must specify an executable hg script')
483 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
483 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
484 sys.stderr.write('warning: --with-hg should specify an hg script\n')
484 sys.stderr.write('warning: --with-hg should specify an hg script\n')
485 sys.stderr.flush()
485 sys.stderr.flush()
486 if options.local:
486 if options.local:
487 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
487 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
488 reporootdir = os.path.dirname(testdir)
488 reporootdir = os.path.dirname(testdir)
489 pathandattrs = [(b'hg', 'with_hg')]
489 pathandattrs = [(b'hg', 'with_hg')]
490 if options.chg:
490 if options.chg:
491 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
491 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
492 for relpath, attr in pathandattrs:
492 for relpath, attr in pathandattrs:
493 binpath = os.path.join(reporootdir, relpath)
493 binpath = os.path.join(reporootdir, relpath)
494 if os.name != 'nt' and not os.access(binpath, os.X_OK):
494 if os.name != 'nt' and not os.access(binpath, os.X_OK):
495 parser.error('--local specified, but %r not found or '
495 parser.error('--local specified, but %r not found or '
496 'not executable' % binpath)
496 'not executable' % binpath)
497 setattr(options, attr, binpath)
497 setattr(options, attr, binpath)
498
498
499 if (options.chg or options.with_chg) and os.name == 'nt':
499 if (options.chg or options.with_chg) and os.name == 'nt':
500 parser.error('chg does not work on %s' % os.name)
500 parser.error('chg does not work on %s' % os.name)
501 if options.with_chg:
501 if options.with_chg:
502 options.chg = False # no installation to temporary location
502 options.chg = False # no installation to temporary location
503 options.with_chg = canonpath(_bytespath(options.with_chg))
503 options.with_chg = canonpath(_bytespath(options.with_chg))
504 if not (os.path.isfile(options.with_chg) and
504 if not (os.path.isfile(options.with_chg) and
505 os.access(options.with_chg, os.X_OK)):
505 os.access(options.with_chg, os.X_OK)):
506 parser.error('--with-chg must specify a chg executable')
506 parser.error('--with-chg must specify a chg executable')
507 if options.chg and options.with_hg:
507 if options.chg and options.with_hg:
508 # chg shares installation location with hg
508 # chg shares installation location with hg
509 parser.error('--chg does not work when --with-hg is specified '
509 parser.error('--chg does not work when --with-hg is specified '
510 '(use --with-chg instead)')
510 '(use --with-chg instead)')
511
511
512 if options.color == 'always' and not pygmentspresent:
512 if options.color == 'always' and not pygmentspresent:
513 sys.stderr.write('warning: --color=always ignored because '
513 sys.stderr.write('warning: --color=always ignored because '
514 'pygments is not installed\n')
514 'pygments is not installed\n')
515
515
516 if options.bisect_repo and not options.known_good_rev:
516 if options.bisect_repo and not options.known_good_rev:
517 parser.error("--bisect-repo cannot be used without --known-good-rev")
517 parser.error("--bisect-repo cannot be used without --known-good-rev")
518
518
519 global useipv6
519 global useipv6
520 if options.ipv6:
520 if options.ipv6:
521 useipv6 = checksocketfamily('AF_INET6')
521 useipv6 = checksocketfamily('AF_INET6')
522 else:
522 else:
523 # only use IPv6 if IPv4 is unavailable and IPv6 is available
523 # only use IPv6 if IPv4 is unavailable and IPv6 is available
524 useipv6 = ((not checksocketfamily('AF_INET'))
524 useipv6 = ((not checksocketfamily('AF_INET'))
525 and checksocketfamily('AF_INET6'))
525 and checksocketfamily('AF_INET6'))
526
526
527 options.anycoverage = options.cover or options.annotate or options.htmlcov
527 options.anycoverage = options.cover or options.annotate or options.htmlcov
528 if options.anycoverage:
528 if options.anycoverage:
529 try:
529 try:
530 import coverage
530 import coverage
531 covver = version.StrictVersion(coverage.__version__).version
531 covver = version.StrictVersion(coverage.__version__).version
532 if covver < (3, 3):
532 if covver < (3, 3):
533 parser.error('coverage options require coverage 3.3 or later')
533 parser.error('coverage options require coverage 3.3 or later')
534 except ImportError:
534 except ImportError:
535 parser.error('coverage options now require the coverage package')
535 parser.error('coverage options now require the coverage package')
536
536
537 if options.anycoverage and options.local:
537 if options.anycoverage and options.local:
538 # this needs some path mangling somewhere, I guess
538 # this needs some path mangling somewhere, I guess
539 parser.error("sorry, coverage options do not work when --local "
539 parser.error("sorry, coverage options do not work when --local "
540 "is specified")
540 "is specified")
541
541
542 if options.anycoverage and options.with_hg:
542 if options.anycoverage and options.with_hg:
543 parser.error("sorry, coverage options do not work when --with-hg "
543 parser.error("sorry, coverage options do not work when --with-hg "
544 "is specified")
544 "is specified")
545
545
546 global verbose
546 global verbose
547 if options.verbose:
547 if options.verbose:
548 verbose = ''
548 verbose = ''
549
549
550 if options.tmpdir:
550 if options.tmpdir:
551 options.tmpdir = canonpath(options.tmpdir)
551 options.tmpdir = canonpath(options.tmpdir)
552
552
553 if options.jobs < 1:
553 if options.jobs < 1:
554 parser.error('--jobs must be positive')
554 parser.error('--jobs must be positive')
555 if options.interactive and options.debug:
555 if options.interactive and options.debug:
556 parser.error("-i/--interactive and -d/--debug are incompatible")
556 parser.error("-i/--interactive and -d/--debug are incompatible")
557 if options.debug:
557 if options.debug:
558 if options.timeout != defaults['timeout']:
558 if options.timeout != defaults['timeout']:
559 sys.stderr.write(
559 sys.stderr.write(
560 'warning: --timeout option ignored with --debug\n')
560 'warning: --timeout option ignored with --debug\n')
561 if options.slowtimeout != defaults['slowtimeout']:
561 if options.slowtimeout != defaults['slowtimeout']:
562 sys.stderr.write(
562 sys.stderr.write(
563 'warning: --slowtimeout option ignored with --debug\n')
563 'warning: --slowtimeout option ignored with --debug\n')
564 options.timeout = 0
564 options.timeout = 0
565 options.slowtimeout = 0
565 options.slowtimeout = 0
566 if options.py3_warnings:
566 if options.py3_warnings:
567 if PYTHON3:
567 if PYTHON3:
568 parser.error(
568 parser.error(
569 '--py3-warnings can only be used on Python 2.7')
569 '--py3-warnings can only be used on Python 2.7')
570
570
571 if options.blacklist:
571 if options.blacklist:
572 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
572 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
573 if options.whitelist:
573 if options.whitelist:
574 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
574 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
575 else:
575 else:
576 options.whitelisted = {}
576 options.whitelisted = {}
577
577
578 if options.showchannels:
578 if options.showchannels:
579 options.nodiff = True
579 options.nodiff = True
580
580
581 return options
581 return options
582
582
583 def rename(src, dst):
583 def rename(src, dst):
584 """Like os.rename(), trade atomicity and opened files friendliness
584 """Like os.rename(), trade atomicity and opened files friendliness
585 for existing destination support.
585 for existing destination support.
586 """
586 """
587 shutil.copy(src, dst)
587 shutil.copy(src, dst)
588 os.remove(src)
588 os.remove(src)
589
589
590 _unified_diff = difflib.unified_diff
590 _unified_diff = difflib.unified_diff
591 if PYTHON3:
591 if PYTHON3:
592 import functools
592 import functools
593 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
593 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
594
594
595 def getdiff(expected, output, ref, err):
595 def getdiff(expected, output, ref, err):
596 servefail = False
596 servefail = False
597 lines = []
597 lines = []
598 for line in _unified_diff(expected, output, ref, err):
598 for line in _unified_diff(expected, output, ref, err):
599 if line.startswith(b'+++') or line.startswith(b'---'):
599 if line.startswith(b'+++') or line.startswith(b'---'):
600 line = line.replace(b'\\', b'/')
600 line = line.replace(b'\\', b'/')
601 if line.endswith(b' \n'):
601 if line.endswith(b' \n'):
602 line = line[:-2] + b'\n'
602 line = line[:-2] + b'\n'
603 lines.append(line)
603 lines.append(line)
604 if not servefail and line.startswith(
604 if not servefail and line.startswith(
605 b'+ abort: child process failed to start'):
605 b'+ abort: child process failed to start'):
606 servefail = True
606 servefail = True
607
607
608 return servefail, lines
608 return servefail, lines
609
609
610 verbose = False
610 verbose = False
611 def vlog(*msg):
611 def vlog(*msg):
612 """Log only when in verbose mode."""
612 """Log only when in verbose mode."""
613 if verbose is False:
613 if verbose is False:
614 return
614 return
615
615
616 return log(*msg)
616 return log(*msg)
617
617
618 # Bytes that break XML even in a CDATA block: control characters 0-31
618 # Bytes that break XML even in a CDATA block: control characters 0-31
619 # sans \t, \n and \r
619 # sans \t, \n and \r
620 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
620 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
621
621
622 # Match feature conditionalized output lines in the form, capturing the feature
622 # Match feature conditionalized output lines in the form, capturing the feature
623 # list in group 2, and the preceeding line output in group 1:
623 # list in group 2, and the preceeding line output in group 1:
624 #
624 #
625 # output..output (feature !)\n
625 # output..output (feature !)\n
626 optline = re.compile(b'(.*) \((.+?) !\)\n$')
626 optline = re.compile(b'(.*) \((.+?) !\)\n$')
627
627
628 def cdatasafe(data):
628 def cdatasafe(data):
629 """Make a string safe to include in a CDATA block.
629 """Make a string safe to include in a CDATA block.
630
630
631 Certain control characters are illegal in a CDATA block, and
631 Certain control characters are illegal in a CDATA block, and
632 there's no way to include a ]]> in a CDATA either. This function
632 there's no way to include a ]]> in a CDATA either. This function
633 replaces illegal bytes with ? and adds a space between the ]] so
633 replaces illegal bytes with ? and adds a space between the ]] so
634 that it won't break the CDATA block.
634 that it won't break the CDATA block.
635 """
635 """
636 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
636 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
637
637
638 def log(*msg):
638 def log(*msg):
639 """Log something to stdout.
639 """Log something to stdout.
640
640
641 Arguments are strings to print.
641 Arguments are strings to print.
642 """
642 """
643 with iolock:
643 with iolock:
644 if verbose:
644 if verbose:
645 print(verbose, end=' ')
645 print(verbose, end=' ')
646 for m in msg:
646 for m in msg:
647 print(m, end=' ')
647 print(m, end=' ')
648 print()
648 print()
649 sys.stdout.flush()
649 sys.stdout.flush()
650
650
651 def highlightdiff(line, color):
651 def highlightdiff(line, color):
652 if not color:
652 if not color:
653 return line
653 return line
654 assert pygmentspresent
654 assert pygmentspresent
655 return pygments.highlight(line.decode('latin1'), difflexer,
655 return pygments.highlight(line.decode('latin1'), difflexer,
656 terminal256formatter).encode('latin1')
656 terminal256formatter).encode('latin1')
657
657
658 def highlightmsg(msg, color):
658 def highlightmsg(msg, color):
659 if not color:
659 if not color:
660 return msg
660 return msg
661 assert pygmentspresent
661 assert pygmentspresent
662 return pygments.highlight(msg, runnerlexer, runnerformatter)
662 return pygments.highlight(msg, runnerlexer, runnerformatter)
663
663
664 def terminate(proc):
664 def terminate(proc):
665 """Terminate subprocess"""
665 """Terminate subprocess"""
666 vlog('# Terminating process %d' % proc.pid)
666 vlog('# Terminating process %d' % proc.pid)
667 try:
667 try:
668 proc.terminate()
668 proc.terminate()
669 except OSError:
669 except OSError:
670 pass
670 pass
671
671
672 def killdaemons(pidfile):
672 def killdaemons(pidfile):
673 import killdaemons as killmod
673 import killdaemons as killmod
674 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
674 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
675 logfn=vlog)
675 logfn=vlog)
676
676
677 class Test(unittest.TestCase):
677 class Test(unittest.TestCase):
678 """Encapsulates a single, runnable test.
678 """Encapsulates a single, runnable test.
679
679
680 While this class conforms to the unittest.TestCase API, it differs in that
680 While this class conforms to the unittest.TestCase API, it differs in that
681 instances need to be instantiated manually. (Typically, unittest.TestCase
681 instances need to be instantiated manually. (Typically, unittest.TestCase
682 classes are instantiated automatically by scanning modules.)
682 classes are instantiated automatically by scanning modules.)
683 """
683 """
684
684
685 # Status code reserved for skipped tests (used by hghave).
685 # Status code reserved for skipped tests (used by hghave).
686 SKIPPED_STATUS = 80
686 SKIPPED_STATUS = 80
687
687
688 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
688 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
689 debug=False,
689 debug=False,
690 first=False,
690 first=False,
691 timeout=None,
691 timeout=None,
692 startport=None, extraconfigopts=None,
692 startport=None, extraconfigopts=None,
693 py3warnings=False, shell=None, hgcommand=None,
693 py3warnings=False, shell=None, hgcommand=None,
694 slowtimeout=None, usechg=False,
694 slowtimeout=None, usechg=False,
695 useipv6=False):
695 useipv6=False):
696 """Create a test from parameters.
696 """Create a test from parameters.
697
697
698 path is the full path to the file defining the test.
698 path is the full path to the file defining the test.
699
699
700 tmpdir is the main temporary directory to use for this test.
700 tmpdir is the main temporary directory to use for this test.
701
701
702 keeptmpdir determines whether to keep the test's temporary directory
702 keeptmpdir determines whether to keep the test's temporary directory
703 after execution. It defaults to removal (False).
703 after execution. It defaults to removal (False).
704
704
705 debug mode will make the test execute verbosely, with unfiltered
705 debug mode will make the test execute verbosely, with unfiltered
706 output.
706 output.
707
707
708 timeout controls the maximum run time of the test. It is ignored when
708 timeout controls the maximum run time of the test. It is ignored when
709 debug is True. See slowtimeout for tests with #require slow.
709 debug is True. See slowtimeout for tests with #require slow.
710
710
711 slowtimeout overrides timeout if the test has #require slow.
711 slowtimeout overrides timeout if the test has #require slow.
712
712
713 startport controls the starting port number to use for this test. Each
713 startport controls the starting port number to use for this test. Each
714 test will reserve 3 port numbers for execution. It is the caller's
714 test will reserve 3 port numbers for execution. It is the caller's
715 responsibility to allocate a non-overlapping port range to Test
715 responsibility to allocate a non-overlapping port range to Test
716 instances.
716 instances.
717
717
718 extraconfigopts is an iterable of extra hgrc config options. Values
718 extraconfigopts is an iterable of extra hgrc config options. Values
719 must have the form "key=value" (something understood by hgrc). Values
719 must have the form "key=value" (something understood by hgrc). Values
720 of the form "foo.key=value" will result in "[foo] key=value".
720 of the form "foo.key=value" will result in "[foo] key=value".
721
721
722 py3warnings enables Py3k warnings.
722 py3warnings enables Py3k warnings.
723
723
724 shell is the shell to execute tests in.
724 shell is the shell to execute tests in.
725 """
725 """
726 if timeout is None:
726 if timeout is None:
727 timeout = defaults['timeout']
727 timeout = defaults['timeout']
728 if startport is None:
728 if startport is None:
729 startport = defaults['port']
729 startport = defaults['port']
730 if slowtimeout is None:
730 if slowtimeout is None:
731 slowtimeout = defaults['slowtimeout']
731 slowtimeout = defaults['slowtimeout']
732 self.path = path
732 self.path = path
733 self.bname = os.path.basename(path)
733 self.bname = os.path.basename(path)
734 self.name = _strpath(self.bname)
734 self.name = _strpath(self.bname)
735 self._testdir = os.path.dirname(path)
735 self._testdir = os.path.dirname(path)
736 self._outputdir = outputdir
736 self._outputdir = outputdir
737 self._tmpname = os.path.basename(path)
737 self._tmpname = os.path.basename(path)
738 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
738 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
739
739
740 self._threadtmp = tmpdir
740 self._threadtmp = tmpdir
741 self._keeptmpdir = keeptmpdir
741 self._keeptmpdir = keeptmpdir
742 self._debug = debug
742 self._debug = debug
743 self._first = first
743 self._first = first
744 self._timeout = timeout
744 self._timeout = timeout
745 self._slowtimeout = slowtimeout
745 self._slowtimeout = slowtimeout
746 self._startport = startport
746 self._startport = startport
747 self._extraconfigopts = extraconfigopts or []
747 self._extraconfigopts = extraconfigopts or []
748 self._py3warnings = py3warnings
748 self._py3warnings = py3warnings
749 self._shell = _bytespath(shell)
749 self._shell = _bytespath(shell)
750 self._hgcommand = hgcommand or b'hg'
750 self._hgcommand = hgcommand or b'hg'
751 self._usechg = usechg
751 self._usechg = usechg
752 self._useipv6 = useipv6
752 self._useipv6 = useipv6
753
753
754 self._aborted = False
754 self._aborted = False
755 self._daemonpids = []
755 self._daemonpids = []
756 self._finished = None
756 self._finished = None
757 self._ret = None
757 self._ret = None
758 self._out = None
758 self._out = None
759 self._skipped = None
759 self._skipped = None
760 self._testtmp = None
760 self._testtmp = None
761 self._chgsockdir = None
761 self._chgsockdir = None
762
762
763 self._refout = self.readrefout()
763 self._refout = self.readrefout()
764
764
765 def readrefout(self):
765 def readrefout(self):
766 """read reference output"""
766 """read reference output"""
767 # If we're not in --debug mode and reference output file exists,
767 # If we're not in --debug mode and reference output file exists,
768 # check test output against it.
768 # check test output against it.
769 if self._debug:
769 if self._debug:
770 return None # to match "out is None"
770 return None # to match "out is None"
771 elif os.path.exists(self.refpath):
771 elif os.path.exists(self.refpath):
772 with open(self.refpath, 'rb') as f:
772 with open(self.refpath, 'rb') as f:
773 return f.read().splitlines(True)
773 return f.read().splitlines(True)
774 else:
774 else:
775 return []
775 return []
776
776
777 # needed to get base class __repr__ running
777 # needed to get base class __repr__ running
778 @property
778 @property
779 def _testMethodName(self):
779 def _testMethodName(self):
780 return self.name
780 return self.name
781
781
782 def __str__(self):
782 def __str__(self):
783 return self.name
783 return self.name
784
784
785 def shortDescription(self):
785 def shortDescription(self):
786 return self.name
786 return self.name
787
787
788 def setUp(self):
788 def setUp(self):
789 """Tasks to perform before run()."""
789 """Tasks to perform before run()."""
790 self._finished = False
790 self._finished = False
791 self._ret = None
791 self._ret = None
792 self._out = None
792 self._out = None
793 self._skipped = None
793 self._skipped = None
794
794
795 try:
795 try:
796 os.mkdir(self._threadtmp)
796 os.mkdir(self._threadtmp)
797 except OSError as e:
797 except OSError as e:
798 if e.errno != errno.EEXIST:
798 if e.errno != errno.EEXIST:
799 raise
799 raise
800
800
801 name = self._tmpname
801 name = self._tmpname
802 self._testtmp = os.path.join(self._threadtmp, name)
802 self._testtmp = os.path.join(self._threadtmp, name)
803 os.mkdir(self._testtmp)
803 os.mkdir(self._testtmp)
804
804
805 # Remove any previous output files.
805 # Remove any previous output files.
806 if os.path.exists(self.errpath):
806 if os.path.exists(self.errpath):
807 try:
807 try:
808 os.remove(self.errpath)
808 os.remove(self.errpath)
809 except OSError as e:
809 except OSError as e:
810 # We might have raced another test to clean up a .err
810 # We might have raced another test to clean up a .err
811 # file, so ignore ENOENT when removing a previous .err
811 # file, so ignore ENOENT when removing a previous .err
812 # file.
812 # file.
813 if e.errno != errno.ENOENT:
813 if e.errno != errno.ENOENT:
814 raise
814 raise
815
815
816 if self._usechg:
816 if self._usechg:
817 self._chgsockdir = os.path.join(self._threadtmp,
817 self._chgsockdir = os.path.join(self._threadtmp,
818 b'%s.chgsock' % name)
818 b'%s.chgsock' % name)
819 os.mkdir(self._chgsockdir)
819 os.mkdir(self._chgsockdir)
820
820
821 def run(self, result):
821 def run(self, result):
822 """Run this test and report results against a TestResult instance."""
822 """Run this test and report results against a TestResult instance."""
823 # This function is extremely similar to unittest.TestCase.run(). Once
823 # This function is extremely similar to unittest.TestCase.run(). Once
824 # we require Python 2.7 (or at least its version of unittest), this
824 # we require Python 2.7 (or at least its version of unittest), this
825 # function can largely go away.
825 # function can largely go away.
826 self._result = result
826 self._result = result
827 result.startTest(self)
827 result.startTest(self)
828 try:
828 try:
829 try:
829 try:
830 self.setUp()
830 self.setUp()
831 except (KeyboardInterrupt, SystemExit):
831 except (KeyboardInterrupt, SystemExit):
832 self._aborted = True
832 self._aborted = True
833 raise
833 raise
834 except Exception:
834 except Exception:
835 result.addError(self, sys.exc_info())
835 result.addError(self, sys.exc_info())
836 return
836 return
837
837
838 success = False
838 success = False
839 try:
839 try:
840 self.runTest()
840 self.runTest()
841 except KeyboardInterrupt:
841 except KeyboardInterrupt:
842 self._aborted = True
842 self._aborted = True
843 raise
843 raise
844 except unittest.SkipTest as e:
844 except unittest.SkipTest as e:
845 result.addSkip(self, str(e))
845 result.addSkip(self, str(e))
846 # The base class will have already counted this as a
846 # The base class will have already counted this as a
847 # test we "ran", but we want to exclude skipped tests
847 # test we "ran", but we want to exclude skipped tests
848 # from those we count towards those run.
848 # from those we count towards those run.
849 result.testsRun -= 1
849 result.testsRun -= 1
850 except self.failureException as e:
850 except self.failureException as e:
851 # This differs from unittest in that we don't capture
851 # This differs from unittest in that we don't capture
852 # the stack trace. This is for historical reasons and
852 # the stack trace. This is for historical reasons and
853 # this decision could be revisited in the future,
853 # this decision could be revisited in the future,
854 # especially for PythonTest instances.
854 # especially for PythonTest instances.
855 if result.addFailure(self, str(e)):
855 if result.addFailure(self, str(e)):
856 success = True
856 success = True
857 except Exception:
857 except Exception:
858 result.addError(self, sys.exc_info())
858 result.addError(self, sys.exc_info())
859 else:
859 else:
860 success = True
860 success = True
861
861
862 try:
862 try:
863 self.tearDown()
863 self.tearDown()
864 except (KeyboardInterrupt, SystemExit):
864 except (KeyboardInterrupt, SystemExit):
865 self._aborted = True
865 self._aborted = True
866 raise
866 raise
867 except Exception:
867 except Exception:
868 result.addError(self, sys.exc_info())
868 result.addError(self, sys.exc_info())
869 success = False
869 success = False
870
870
871 if success:
871 if success:
872 result.addSuccess(self)
872 result.addSuccess(self)
873 finally:
873 finally:
874 result.stopTest(self, interrupted=self._aborted)
874 result.stopTest(self, interrupted=self._aborted)
875
875
876 def runTest(self):
876 def runTest(self):
877 """Run this test instance.
877 """Run this test instance.
878
878
879 This will return a tuple describing the result of the test.
879 This will return a tuple describing the result of the test.
880 """
880 """
881 env = self._getenv()
881 env = self._getenv()
882 self._genrestoreenv(env)
882 self._genrestoreenv(env)
883 self._daemonpids.append(env['DAEMON_PIDS'])
883 self._daemonpids.append(env['DAEMON_PIDS'])
884 self._createhgrc(env['HGRCPATH'])
884 self._createhgrc(env['HGRCPATH'])
885
885
886 vlog('# Test', self.name)
886 vlog('# Test', self.name)
887
887
888 ret, out = self._run(env)
888 ret, out = self._run(env)
889 self._finished = True
889 self._finished = True
890 self._ret = ret
890 self._ret = ret
891 self._out = out
891 self._out = out
892
892
893 def describe(ret):
893 def describe(ret):
894 if ret < 0:
894 if ret < 0:
895 return 'killed by signal: %d' % -ret
895 return 'killed by signal: %d' % -ret
896 return 'returned error code %d' % ret
896 return 'returned error code %d' % ret
897
897
898 self._skipped = False
898 self._skipped = False
899
899
900 if ret == self.SKIPPED_STATUS:
900 if ret == self.SKIPPED_STATUS:
901 if out is None: # Debug mode, nothing to parse.
901 if out is None: # Debug mode, nothing to parse.
902 missing = ['unknown']
902 missing = ['unknown']
903 failed = None
903 failed = None
904 else:
904 else:
905 missing, failed = TTest.parsehghaveoutput(out)
905 missing, failed = TTest.parsehghaveoutput(out)
906
906
907 if not missing:
907 if not missing:
908 missing = ['skipped']
908 missing = ['skipped']
909
909
910 if failed:
910 if failed:
911 self.fail('hg have failed checking for %s' % failed[-1])
911 self.fail('hg have failed checking for %s' % failed[-1])
912 else:
912 else:
913 self._skipped = True
913 self._skipped = True
914 raise unittest.SkipTest(missing[-1])
914 raise unittest.SkipTest(missing[-1])
915 elif ret == 'timeout':
915 elif ret == 'timeout':
916 self.fail('timed out')
916 self.fail('timed out')
917 elif ret is False:
917 elif ret is False:
918 self.fail('no result code from test')
918 self.fail('no result code from test')
919 elif out != self._refout:
919 elif out != self._refout:
920 # Diff generation may rely on written .err file.
920 # Diff generation may rely on written .err file.
921 if (ret != 0 or out != self._refout) and not self._skipped \
921 if (ret != 0 or out != self._refout) and not self._skipped \
922 and not self._debug:
922 and not self._debug:
923 with open(self.errpath, 'wb') as f:
923 with open(self.errpath, 'wb') as f:
924 for line in out:
924 for line in out:
925 f.write(line)
925 f.write(line)
926
926
927 # The result object handles diff calculation for us.
927 # The result object handles diff calculation for us.
928 with firstlock:
928 with firstlock:
929 if self._result.addOutputMismatch(self, ret, out, self._refout):
929 if self._result.addOutputMismatch(self, ret, out, self._refout):
930 # change was accepted, skip failing
930 # change was accepted, skip failing
931 return
931 return
932 if self._first:
932 if self._first:
933 global firsterror
933 global firsterror
934 firsterror = True
934 firsterror = True
935
935
936 if ret:
936 if ret:
937 msg = 'output changed and ' + describe(ret)
937 msg = 'output changed and ' + describe(ret)
938 else:
938 else:
939 msg = 'output changed'
939 msg = 'output changed'
940
940
941 self.fail(msg)
941 self.fail(msg)
942 elif ret:
942 elif ret:
943 self.fail(describe(ret))
943 self.fail(describe(ret))
944
944
945 def tearDown(self):
945 def tearDown(self):
946 """Tasks to perform after run()."""
946 """Tasks to perform after run()."""
947 for entry in self._daemonpids:
947 for entry in self._daemonpids:
948 killdaemons(entry)
948 killdaemons(entry)
949 self._daemonpids = []
949 self._daemonpids = []
950
950
951 if self._keeptmpdir:
951 if self._keeptmpdir:
952 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
952 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
953 (self._testtmp.decode('utf-8'),
953 (self._testtmp.decode('utf-8'),
954 self._threadtmp.decode('utf-8')))
954 self._threadtmp.decode('utf-8')))
955 else:
955 else:
956 shutil.rmtree(self._testtmp, True)
956 shutil.rmtree(self._testtmp, True)
957 shutil.rmtree(self._threadtmp, True)
957 shutil.rmtree(self._threadtmp, True)
958
958
959 if self._usechg:
959 if self._usechg:
960 # chgservers will stop automatically after they find the socket
960 # chgservers will stop automatically after they find the socket
961 # files are deleted
961 # files are deleted
962 shutil.rmtree(self._chgsockdir, True)
962 shutil.rmtree(self._chgsockdir, True)
963
963
964 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
964 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
965 and not self._debug and self._out:
965 and not self._debug and self._out:
966 with open(self.errpath, 'wb') as f:
966 with open(self.errpath, 'wb') as f:
967 for line in self._out:
967 for line in self._out:
968 f.write(line)
968 f.write(line)
969
969
970 vlog("# Ret was:", self._ret, '(%s)' % self.name)
970 vlog("# Ret was:", self._ret, '(%s)' % self.name)
971
971
972 def _run(self, env):
972 def _run(self, env):
973 # This should be implemented in child classes to run tests.
973 # This should be implemented in child classes to run tests.
974 raise unittest.SkipTest('unknown test type')
974 raise unittest.SkipTest('unknown test type')
975
975
976 def abort(self):
976 def abort(self):
977 """Terminate execution of this test."""
977 """Terminate execution of this test."""
978 self._aborted = True
978 self._aborted = True
979
979
980 def _portmap(self, i):
980 def _portmap(self, i):
981 offset = b'' if i == 0 else b'%d' % i
981 offset = b'' if i == 0 else b'%d' % i
982 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
982 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
983
983
984 def _getreplacements(self):
984 def _getreplacements(self):
985 """Obtain a mapping of text replacements to apply to test output.
985 """Obtain a mapping of text replacements to apply to test output.
986
986
987 Test output needs to be normalized so it can be compared to expected
987 Test output needs to be normalized so it can be compared to expected
988 output. This function defines how some of that normalization will
988 output. This function defines how some of that normalization will
989 occur.
989 occur.
990 """
990 """
991 r = [
991 r = [
992 # This list should be parallel to defineport in _getenv
992 # This list should be parallel to defineport in _getenv
993 self._portmap(0),
993 self._portmap(0),
994 self._portmap(1),
994 self._portmap(1),
995 self._portmap(2),
995 self._portmap(2),
996 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
996 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
997 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
997 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
998 ]
998 ]
999 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
999 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1000
1000
1001 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1001 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1002
1002
1003 if os.path.exists(replacementfile):
1003 if os.path.exists(replacementfile):
1004 data = {}
1004 data = {}
1005 with open(replacementfile, mode='rb') as source:
1005 with open(replacementfile, mode='rb') as source:
1006 # the intermediate 'compile' step help with debugging
1006 # the intermediate 'compile' step help with debugging
1007 code = compile(source.read(), replacementfile, 'exec')
1007 code = compile(source.read(), replacementfile, 'exec')
1008 exec(code, data)
1008 exec(code, data)
1009 for value in data.get('substitutions', ()):
1009 for value in data.get('substitutions', ()):
1010 if len(value) != 2:
1010 if len(value) != 2:
1011 msg = 'malformatted substitution in %s: %r'
1011 msg = 'malformatted substitution in %s: %r'
1012 msg %= (replacementfile, value)
1012 msg %= (replacementfile, value)
1013 raise ValueError(msg)
1013 raise ValueError(msg)
1014 r.append(value)
1014 r.append(value)
1015 return r
1015 return r
1016
1016
1017 def _escapepath(self, p):
1017 def _escapepath(self, p):
1018 if os.name == 'nt':
1018 if os.name == 'nt':
1019 return (
1019 return (
1020 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1020 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1021 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1021 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1022 for c in [p[i:i + 1] for i in range(len(p))]))
1022 for c in [p[i:i + 1] for i in range(len(p))]))
1023 )
1023 )
1024 else:
1024 else:
1025 return re.escape(p)
1025 return re.escape(p)
1026
1026
1027 def _localip(self):
1027 def _localip(self):
1028 if self._useipv6:
1028 if self._useipv6:
1029 return b'::1'
1029 return b'::1'
1030 else:
1030 else:
1031 return b'127.0.0.1'
1031 return b'127.0.0.1'
1032
1032
1033 def _genrestoreenv(self, testenv):
1033 def _genrestoreenv(self, testenv):
1034 """Generate a script that can be used by tests to restore the original
1034 """Generate a script that can be used by tests to restore the original
1035 environment."""
1035 environment."""
1036 # Put the restoreenv script inside self._threadtmp
1036 # Put the restoreenv script inside self._threadtmp
1037 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1037 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1038 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1038 testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
1039
1039
1040 # Only restore environment variable names that the shell allows
1040 # Only restore environment variable names that the shell allows
1041 # us to export.
1041 # us to export.
1042 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1042 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1043
1043
1044 # Do not restore these variables; otherwise tests would fail.
1044 # Do not restore these variables; otherwise tests would fail.
1045 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1045 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1046
1046
1047 with open(scriptpath, 'w') as envf:
1047 with open(scriptpath, 'w') as envf:
1048 for name, value in origenviron.items():
1048 for name, value in origenviron.items():
1049 if not name_regex.match(name):
1049 if not name_regex.match(name):
1050 # Skip environment variables with unusual names not
1050 # Skip environment variables with unusual names not
1051 # allowed by most shells.
1051 # allowed by most shells.
1052 continue
1052 continue
1053 if name in reqnames:
1053 if name in reqnames:
1054 continue
1054 continue
1055 envf.write('%s=%s\n' % (name, shellquote(value)))
1055 envf.write('%s=%s\n' % (name, shellquote(value)))
1056
1056
1057 for name in testenv:
1057 for name in testenv:
1058 if name in origenviron or name in reqnames:
1058 if name in origenviron or name in reqnames:
1059 continue
1059 continue
1060 envf.write('unset %s\n' % (name,))
1060 envf.write('unset %s\n' % (name,))
1061
1061
1062 def _getenv(self):
1062 def _getenv(self):
1063 """Obtain environment variables to use during test execution."""
1063 """Obtain environment variables to use during test execution."""
1064 def defineport(i):
1064 def defineport(i):
1065 offset = '' if i == 0 else '%s' % i
1065 offset = '' if i == 0 else '%s' % i
1066 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1066 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1067 env = os.environ.copy()
1067 env = os.environ.copy()
1068 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1068 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1069 env['HGEMITWARNINGS'] = '1'
1069 env['HGEMITWARNINGS'] = '1'
1070 env['TESTTMP'] = _strpath(self._testtmp)
1070 env['TESTTMP'] = _strpath(self._testtmp)
1071 env['TESTNAME'] = self.name
1071 env['TESTNAME'] = self.name
1072 env['HOME'] = _strpath(self._testtmp)
1072 env['HOME'] = _strpath(self._testtmp)
1073 # This number should match portneeded in _getport
1073 # This number should match portneeded in _getport
1074 for port in xrange(3):
1074 for port in xrange(3):
1075 # This list should be parallel to _portmap in _getreplacements
1075 # This list should be parallel to _portmap in _getreplacements
1076 defineport(port)
1076 defineport(port)
1077 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1077 env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
1078 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1078 env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
1079 b'daemon.pids'))
1079 b'daemon.pids'))
1080 env["HGEDITOR"] = ('"' + sys.executable + '"'
1080 env["HGEDITOR"] = ('"' + sys.executable + '"'
1081 + ' -c "import sys; sys.exit(0)"')
1081 + ' -c "import sys; sys.exit(0)"')
1082 env["HGUSER"] = "test"
1082 env["HGUSER"] = "test"
1083 env["HGENCODING"] = "ascii"
1083 env["HGENCODING"] = "ascii"
1084 env["HGENCODINGMODE"] = "strict"
1084 env["HGENCODINGMODE"] = "strict"
1085 env["HGHOSTNAME"] = "test-hostname"
1085 env["HGHOSTNAME"] = "test-hostname"
1086 env['HGIPV6'] = str(int(self._useipv6))
1086 env['HGIPV6'] = str(int(self._useipv6))
1087 # See contrib/catapipe.py for how to use this functionality.
1087 # See contrib/catapipe.py for how to use this functionality.
1088 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1088 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1089 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1089 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1090 # non-test one in as a default, otherwise set to devnull
1090 # non-test one in as a default, otherwise set to devnull
1091 env['HGTESTCATAPULTSERVERPIPE'] = \
1091 env['HGTESTCATAPULTSERVERPIPE'] = \
1092 env.get('HGCATAPULTSERVERPIPE', os.devnull)
1092 env.get('HGCATAPULTSERVERPIPE', os.devnull)
1093
1093
1094 extraextensions = []
1094 extraextensions = []
1095 for opt in self._extraconfigopts:
1095 for opt in self._extraconfigopts:
1096 section, key = opt.encode('utf-8').split(b'.', 1)
1096 section, key = opt.encode('utf-8').split(b'.', 1)
1097 if section != 'extensions':
1097 if section != 'extensions':
1098 continue
1098 continue
1099 name = key.split(b'=', 1)[0]
1099 name = key.split(b'=', 1)[0]
1100 extraextensions.append(name)
1100 extraextensions.append(name)
1101
1101
1102 if extraextensions:
1102 if extraextensions:
1103 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1103 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1104
1104
1105 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1105 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1106 # IP addresses.
1106 # IP addresses.
1107 env['LOCALIP'] = _strpath(self._localip())
1107 env['LOCALIP'] = _strpath(self._localip())
1108
1108
1109 # Reset some environment variables to well-known values so that
1109 # Reset some environment variables to well-known values so that
1110 # the tests produce repeatable output.
1110 # the tests produce repeatable output.
1111 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1111 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1112 env['TZ'] = 'GMT'
1112 env['TZ'] = 'GMT'
1113 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1113 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1114 env['COLUMNS'] = '80'
1114 env['COLUMNS'] = '80'
1115 env['TERM'] = 'xterm'
1115 env['TERM'] = 'xterm'
1116
1116
1117 dropped = [
1117 dropped = [
1118 'CDPATH',
1118 'CDPATH',
1119 'CHGDEBUG',
1119 'CHGDEBUG',
1120 'EDITOR',
1120 'EDITOR',
1121 'GREP_OPTIONS',
1121 'GREP_OPTIONS',
1122 'HG',
1122 'HG',
1123 'HGMERGE',
1123 'HGMERGE',
1124 'HGPLAIN',
1124 'HGPLAIN',
1125 'HGPLAINEXCEPT',
1125 'HGPLAINEXCEPT',
1126 'HGPROF',
1126 'HGPROF',
1127 'http_proxy',
1127 'http_proxy',
1128 'no_proxy',
1128 'no_proxy',
1129 'NO_PROXY',
1129 'NO_PROXY',
1130 'PAGER',
1130 'PAGER',
1131 'VISUAL',
1131 'VISUAL',
1132 ]
1132 ]
1133
1133
1134 for k in dropped:
1134 for k in dropped:
1135 if k in env:
1135 if k in env:
1136 del env[k]
1136 del env[k]
1137
1137
1138 # unset env related to hooks
1138 # unset env related to hooks
1139 for k in list(env):
1139 for k in list(env):
1140 if k.startswith('HG_'):
1140 if k.startswith('HG_'):
1141 del env[k]
1141 del env[k]
1142
1142
1143 if self._usechg:
1143 if self._usechg:
1144 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1144 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1145
1145
1146 return env
1146 return env
1147
1147
1148 def _createhgrc(self, path):
1148 def _createhgrc(self, path):
1149 """Create an hgrc file for this test."""
1149 """Create an hgrc file for this test."""
1150 with open(path, 'wb') as hgrc:
1150 with open(path, 'wb') as hgrc:
1151 hgrc.write(b'[ui]\n')
1151 hgrc.write(b'[ui]\n')
1152 hgrc.write(b'slash = True\n')
1152 hgrc.write(b'slash = True\n')
1153 hgrc.write(b'interactive = False\n')
1153 hgrc.write(b'interactive = False\n')
1154 hgrc.write(b'merge = internal:merge\n')
1154 hgrc.write(b'merge = internal:merge\n')
1155 hgrc.write(b'mergemarkers = detailed\n')
1155 hgrc.write(b'mergemarkers = detailed\n')
1156 hgrc.write(b'promptecho = True\n')
1156 hgrc.write(b'promptecho = True\n')
1157 hgrc.write(b'[defaults]\n')
1157 hgrc.write(b'[defaults]\n')
1158 hgrc.write(b'[devel]\n')
1158 hgrc.write(b'[devel]\n')
1159 hgrc.write(b'all-warnings = true\n')
1159 hgrc.write(b'all-warnings = true\n')
1160 hgrc.write(b'default-date = 0 0\n')
1160 hgrc.write(b'default-date = 0 0\n')
1161 hgrc.write(b'[largefiles]\n')
1161 hgrc.write(b'[largefiles]\n')
1162 hgrc.write(b'usercache = %s\n' %
1162 hgrc.write(b'usercache = %s\n' %
1163 (os.path.join(self._testtmp, b'.cache/largefiles')))
1163 (os.path.join(self._testtmp, b'.cache/largefiles')))
1164 hgrc.write(b'[lfs]\n')
1164 hgrc.write(b'[lfs]\n')
1165 hgrc.write(b'usercache = %s\n' %
1165 hgrc.write(b'usercache = %s\n' %
1166 (os.path.join(self._testtmp, b'.cache/lfs')))
1166 (os.path.join(self._testtmp, b'.cache/lfs')))
1167 hgrc.write(b'[web]\n')
1167 hgrc.write(b'[web]\n')
1168 hgrc.write(b'address = localhost\n')
1168 hgrc.write(b'address = localhost\n')
1169 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1169 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1170 hgrc.write(b'server-header = testing stub value\n')
1170 hgrc.write(b'server-header = testing stub value\n')
1171
1171
1172 for opt in self._extraconfigopts:
1172 for opt in self._extraconfigopts:
1173 section, key = opt.encode('utf-8').split(b'.', 1)
1173 section, key = opt.encode('utf-8').split(b'.', 1)
1174 assert b'=' in key, ('extra config opt %s must '
1174 assert b'=' in key, ('extra config opt %s must '
1175 'have an = for assignment' % opt)
1175 'have an = for assignment' % opt)
1176 hgrc.write(b'[%s]\n%s\n' % (section, key))
1176 hgrc.write(b'[%s]\n%s\n' % (section, key))
1177
1177
1178 def fail(self, msg):
1178 def fail(self, msg):
1179 # unittest differentiates between errored and failed.
1179 # unittest differentiates between errored and failed.
1180 # Failed is denoted by AssertionError (by default at least).
1180 # Failed is denoted by AssertionError (by default at least).
1181 raise AssertionError(msg)
1181 raise AssertionError(msg)
1182
1182
1183 def _runcommand(self, cmd, env, normalizenewlines=False):
1183 def _runcommand(self, cmd, env, normalizenewlines=False):
1184 """Run command in a sub-process, capturing the output (stdout and
1184 """Run command in a sub-process, capturing the output (stdout and
1185 stderr).
1185 stderr).
1186
1186
1187 Return a tuple (exitcode, output). output is None in debug mode.
1187 Return a tuple (exitcode, output). output is None in debug mode.
1188 """
1188 """
1189 if self._debug:
1189 if self._debug:
1190 proc = subprocess.Popen(_strpath(cmd), shell=True,
1190 proc = subprocess.Popen(_strpath(cmd), shell=True,
1191 cwd=_strpath(self._testtmp),
1191 cwd=_strpath(self._testtmp),
1192 env=env)
1192 env=env)
1193 ret = proc.wait()
1193 ret = proc.wait()
1194 return (ret, None)
1194 return (ret, None)
1195
1195
1196 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1196 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1197 def cleanup():
1197 def cleanup():
1198 terminate(proc)
1198 terminate(proc)
1199 ret = proc.wait()
1199 ret = proc.wait()
1200 if ret == 0:
1200 if ret == 0:
1201 ret = signal.SIGTERM << 8
1201 ret = signal.SIGTERM << 8
1202 killdaemons(env['DAEMON_PIDS'])
1202 killdaemons(env['DAEMON_PIDS'])
1203 return ret
1203 return ret
1204
1204
1205 output = b''
1205 output = b''
1206 proc.tochild.close()
1206 proc.tochild.close()
1207
1207
1208 try:
1208 try:
1209 output = proc.fromchild.read()
1209 output = proc.fromchild.read()
1210 except KeyboardInterrupt:
1210 except KeyboardInterrupt:
1211 vlog('# Handling keyboard interrupt')
1211 vlog('# Handling keyboard interrupt')
1212 cleanup()
1212 cleanup()
1213 raise
1213 raise
1214
1214
1215 ret = proc.wait()
1215 ret = proc.wait()
1216 if wifexited(ret):
1216 if wifexited(ret):
1217 ret = os.WEXITSTATUS(ret)
1217 ret = os.WEXITSTATUS(ret)
1218
1218
1219 if proc.timeout:
1219 if proc.timeout:
1220 ret = 'timeout'
1220 ret = 'timeout'
1221
1221
1222 if ret:
1222 if ret:
1223 killdaemons(env['DAEMON_PIDS'])
1223 killdaemons(env['DAEMON_PIDS'])
1224
1224
1225 for s, r in self._getreplacements():
1225 for s, r in self._getreplacements():
1226 output = re.sub(s, r, output)
1226 output = re.sub(s, r, output)
1227
1227
1228 if normalizenewlines:
1228 if normalizenewlines:
1229 output = output.replace(b'\r\n', b'\n')
1229 output = output.replace(b'\r\n', b'\n')
1230
1230
1231 return ret, output.splitlines(True)
1231 return ret, output.splitlines(True)
1232
1232
1233 class PythonTest(Test):
1233 class PythonTest(Test):
1234 """A Python-based test."""
1234 """A Python-based test."""
1235
1235
1236 @property
1236 @property
1237 def refpath(self):
1237 def refpath(self):
1238 return os.path.join(self._testdir, b'%s.out' % self.bname)
1238 return os.path.join(self._testdir, b'%s.out' % self.bname)
1239
1239
1240 def _run(self, env):
1240 def _run(self, env):
1241 py3switch = self._py3warnings and b' -3' or b''
1241 py3switch = self._py3warnings and b' -3' or b''
1242 # Quote the python(3) executable for Windows
1242 # Quote the python(3) executable for Windows
1243 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
1243 cmd = b'"%s"%s "%s"' % (PYTHON, py3switch, self.path)
1244 vlog("# Running", cmd)
1244 vlog("# Running", cmd)
1245 normalizenewlines = os.name == 'nt'
1245 normalizenewlines = os.name == 'nt'
1246 result = self._runcommand(cmd, env,
1246 result = self._runcommand(cmd, env,
1247 normalizenewlines=normalizenewlines)
1247 normalizenewlines=normalizenewlines)
1248 if self._aborted:
1248 if self._aborted:
1249 raise KeyboardInterrupt()
1249 raise KeyboardInterrupt()
1250
1250
1251 return result
1251 return result
1252
1252
1253 # Some glob patterns apply only in some circumstances, so the script
1253 # Some glob patterns apply only in some circumstances, so the script
1254 # might want to remove (glob) annotations that otherwise should be
1254 # might want to remove (glob) annotations that otherwise should be
1255 # retained.
1255 # retained.
1256 checkcodeglobpats = [
1256 checkcodeglobpats = [
1257 # On Windows it looks like \ doesn't require a (glob), but we know
1257 # On Windows it looks like \ doesn't require a (glob), but we know
1258 # better.
1258 # better.
1259 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1259 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1260 re.compile(br'^moving \S+/.*[^)]$'),
1260 re.compile(br'^moving \S+/.*[^)]$'),
1261 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1261 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1262 # Not all platforms have 127.0.0.1 as loopback (though most do),
1262 # Not all platforms have 127.0.0.1 as loopback (though most do),
1263 # so we always glob that too.
1263 # so we always glob that too.
1264 re.compile(br'.*\$LOCALIP.*$'),
1264 re.compile(br'.*\$LOCALIP.*$'),
1265 ]
1265 ]
1266
1266
1267 bchr = chr
1267 bchr = chr
1268 if PYTHON3:
1268 if PYTHON3:
1269 bchr = lambda x: bytes([x])
1269 bchr = lambda x: bytes([x])
1270
1270
1271 class TTest(Test):
1271 class TTest(Test):
1272 """A "t test" is a test backed by a .t file."""
1272 """A "t test" is a test backed by a .t file."""
1273
1273
1274 SKIPPED_PREFIX = b'skipped: '
1274 SKIPPED_PREFIX = b'skipped: '
1275 FAILED_PREFIX = b'hghave check failed: '
1275 FAILED_PREFIX = b'hghave check failed: '
1276 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1276 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1277
1277
1278 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1278 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1279 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1279 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1280 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1280 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1281
1281
1282 def __init__(self, path, *args, **kwds):
1282 def __init__(self, path, *args, **kwds):
1283 # accept an extra "case" parameter
1283 # accept an extra "case" parameter
1284 case = kwds.pop('case', [])
1284 case = kwds.pop('case', [])
1285 self._case = case
1285 self._case = case
1286 self._allcases = {x for y in parsettestcases(path) for x in y}
1286 self._allcases = {x for y in parsettestcases(path) for x in y}
1287 super(TTest, self).__init__(path, *args, **kwds)
1287 super(TTest, self).__init__(path, *args, **kwds)
1288 if case:
1288 if case:
1289 casepath = b'#'.join(case)
1289 casepath = b'#'.join(case)
1290 self.name = '%s#%s' % (self.name, _strpath(casepath))
1290 self.name = '%s#%s' % (self.name, _strpath(casepath))
1291 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1291 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1292 self._tmpname += b'-%s' % casepath
1292 self._tmpname += b'-%s' % casepath
1293 self._have = {}
1293 self._have = {}
1294
1294
1295 @property
1295 @property
1296 def refpath(self):
1296 def refpath(self):
1297 return os.path.join(self._testdir, self.bname)
1297 return os.path.join(self._testdir, self.bname)
1298
1298
1299 def _run(self, env):
1299 def _run(self, env):
1300 with open(self.path, 'rb') as f:
1300 with open(self.path, 'rb') as f:
1301 lines = f.readlines()
1301 lines = f.readlines()
1302
1302
1303 # .t file is both reference output and the test input, keep reference
1303 # .t file is both reference output and the test input, keep reference
1304 # output updated with the the test input. This avoids some race
1304 # output updated with the the test input. This avoids some race
1305 # conditions where the reference output does not match the actual test.
1305 # conditions where the reference output does not match the actual test.
1306 if self._refout is not None:
1306 if self._refout is not None:
1307 self._refout = lines
1307 self._refout = lines
1308
1308
1309 salt, script, after, expected = self._parsetest(lines)
1309 salt, script, after, expected = self._parsetest(lines)
1310
1310
1311 # Write out the generated script.
1311 # Write out the generated script.
1312 fname = b'%s.sh' % self._testtmp
1312 fname = b'%s.sh' % self._testtmp
1313 with open(fname, 'wb') as f:
1313 with open(fname, 'wb') as f:
1314 for l in script:
1314 for l in script:
1315 f.write(l)
1315 f.write(l)
1316
1316
1317 cmd = b'%s "%s"' % (self._shell, fname)
1317 cmd = b'%s "%s"' % (self._shell, fname)
1318 vlog("# Running", cmd)
1318 vlog("# Running", cmd)
1319
1319
1320 exitcode, output = self._runcommand(cmd, env)
1320 exitcode, output = self._runcommand(cmd, env)
1321
1321
1322 if self._aborted:
1322 if self._aborted:
1323 raise KeyboardInterrupt()
1323 raise KeyboardInterrupt()
1324
1324
1325 # Do not merge output if skipped. Return hghave message instead.
1325 # Do not merge output if skipped. Return hghave message instead.
1326 # Similarly, with --debug, output is None.
1326 # Similarly, with --debug, output is None.
1327 if exitcode == self.SKIPPED_STATUS or output is None:
1327 if exitcode == self.SKIPPED_STATUS or output is None:
1328 return exitcode, output
1328 return exitcode, output
1329
1329
1330 return self._processoutput(exitcode, output, salt, after, expected)
1330 return self._processoutput(exitcode, output, salt, after, expected)
1331
1331
1332 def _hghave(self, reqs):
1332 def _hghave(self, reqs):
1333 allreqs = b' '.join(reqs)
1333 allreqs = b' '.join(reqs)
1334 if allreqs in self._have:
1334 if allreqs in self._have:
1335 return self._have.get(allreqs)
1335 return self._have.get(allreqs)
1336
1336
1337 # TODO do something smarter when all other uses of hghave are gone.
1337 # TODO do something smarter when all other uses of hghave are gone.
1338 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1338 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1339 tdir = runtestdir.replace(b'\\', b'/')
1339 tdir = runtestdir.replace(b'\\', b'/')
1340 proc = Popen4(b'%s -c "%s/hghave %s"' %
1340 proc = Popen4(b'%s -c "%s/hghave %s"' %
1341 (self._shell, tdir, allreqs),
1341 (self._shell, tdir, allreqs),
1342 self._testtmp, 0, self._getenv())
1342 self._testtmp, 0, self._getenv())
1343 stdout, stderr = proc.communicate()
1343 stdout, stderr = proc.communicate()
1344 ret = proc.wait()
1344 ret = proc.wait()
1345 if wifexited(ret):
1345 if wifexited(ret):
1346 ret = os.WEXITSTATUS(ret)
1346 ret = os.WEXITSTATUS(ret)
1347 if ret == 2:
1347 if ret == 2:
1348 print(stdout.decode('utf-8'))
1348 print(stdout.decode('utf-8'))
1349 sys.exit(1)
1349 sys.exit(1)
1350
1350
1351 if ret != 0:
1351 if ret != 0:
1352 self._have[allreqs] = (False, stdout)
1352 self._have[allreqs] = (False, stdout)
1353 return False, stdout
1353 return False, stdout
1354
1354
1355 if b'slow' in reqs:
1355 if b'slow' in reqs:
1356 self._timeout = self._slowtimeout
1356 self._timeout = self._slowtimeout
1357
1357
1358 self._have[allreqs] = (True, None)
1358 self._have[allreqs] = (True, None)
1359 return True, None
1359 return True, None
1360
1360
1361 def _iftest(self, args):
1361 def _iftest(self, args):
1362 # implements "#if"
1362 # implements "#if"
1363 reqs = []
1363 reqs = []
1364 for arg in args:
1364 for arg in args:
1365 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1365 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1366 if arg[3:] in self._case:
1366 if arg[3:] in self._case:
1367 return False
1367 return False
1368 elif arg in self._allcases:
1368 elif arg in self._allcases:
1369 if arg not in self._case:
1369 if arg not in self._case:
1370 return False
1370 return False
1371 else:
1371 else:
1372 reqs.append(arg)
1372 reqs.append(arg)
1373 return self._hghave(reqs)[0]
1373 return self._hghave(reqs)[0]
1374
1374
1375 def _parsetest(self, lines):
1375 def _parsetest(self, lines):
1376 # We generate a shell script which outputs unique markers to line
1376 # We generate a shell script which outputs unique markers to line
1377 # up script results with our source. These markers include input
1377 # up script results with our source. These markers include input
1378 # line number and the last return code.
1378 # line number and the last return code.
1379 salt = b"SALT%d" % time.time()
1379 salt = b"SALT%d" % time.time()
1380 def addsalt(line, inpython):
1380 def addsalt(line, inpython):
1381 if inpython:
1381 if inpython:
1382 script.append(b'%s %d 0\n' % (salt, line))
1382 script.append(b'%s %d 0\n' % (salt, line))
1383 else:
1383 else:
1384 script.append(b'echo %s %d $?\n' % (salt, line))
1384 script.append(b'echo %s %d $?\n' % (salt, line))
1385 activetrace = []
1385 activetrace = []
1386 session = str(uuid.uuid4())
1386 session = str(uuid.uuid4())
1387 if PYTHON3:
1387 if PYTHON3:
1388 session = session.encode('ascii')
1388 session = session.encode('ascii')
1389 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE')
1389 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE')
1390 def toggletrace(cmd=None):
1390 def toggletrace(cmd=None):
1391 if not hgcatapult or hgcatapult == os.devnull:
1391 if not hgcatapult or hgcatapult == os.devnull:
1392 return
1392 return
1393
1393
1394 if activetrace:
1394 if activetrace:
1395 script.append(
1395 script.append(
1396 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1396 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1397 session, activetrace[0]))
1397 session, activetrace[0]))
1398 if cmd is None:
1398 if cmd is None:
1399 return
1399 return
1400
1400
1401 if isinstance(cmd, str):
1401 if isinstance(cmd, str):
1402 quoted = shellquote(cmd.strip())
1402 quoted = shellquote(cmd.strip())
1403 else:
1403 else:
1404 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1404 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1405 quoted = quoted.replace(b'\\', b'\\\\')
1405 quoted = quoted.replace(b'\\', b'\\\\')
1406 script.append(
1406 script.append(
1407 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1407 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n' % (
1408 session, quoted))
1408 session, quoted))
1409 activetrace[0:] = [quoted]
1409 activetrace[0:] = [quoted]
1410
1410
1411 script = []
1411 script = []
1412
1412
1413 # After we run the shell script, we re-unify the script output
1413 # After we run the shell script, we re-unify the script output
1414 # with non-active parts of the source, with synchronization by our
1414 # with non-active parts of the source, with synchronization by our
1415 # SALT line number markers. The after table contains the non-active
1415 # SALT line number markers. The after table contains the non-active
1416 # components, ordered by line number.
1416 # components, ordered by line number.
1417 after = {}
1417 after = {}
1418
1418
1419 # Expected shell script output.
1419 # Expected shell script output.
1420 expected = {}
1420 expected = {}
1421
1421
1422 pos = prepos = -1
1422 pos = prepos = -1
1423
1423
1424 # True or False when in a true or false conditional section
1424 # True or False when in a true or false conditional section
1425 skipping = None
1425 skipping = None
1426
1426
1427 # We keep track of whether or not we're in a Python block so we
1427 # We keep track of whether or not we're in a Python block so we
1428 # can generate the surrounding doctest magic.
1428 # can generate the surrounding doctest magic.
1429 inpython = False
1429 inpython = False
1430
1430
1431 if self._debug:
1431 if self._debug:
1432 script.append(b'set -x\n')
1432 script.append(b'set -x\n')
1433 if self._hgcommand != b'hg':
1433 if self._hgcommand != b'hg':
1434 script.append(b'alias hg="%s"\n' % self._hgcommand)
1434 script.append(b'alias hg="%s"\n' % self._hgcommand)
1435 if os.getenv('MSYSTEM'):
1435 if os.getenv('MSYSTEM'):
1436 script.append(b'alias pwd="pwd -W"\n')
1436 script.append(b'alias pwd="pwd -W"\n')
1437
1437
1438 if hgcatapult and hgcatapult != os.devnull:
1438 if hgcatapult and hgcatapult != os.devnull:
1439 # Kludge: use a while loop to keep the pipe from getting
1439 # Kludge: use a while loop to keep the pipe from getting
1440 # closed by our echo commands. The still-running file gets
1440 # closed by our echo commands. The still-running file gets
1441 # reaped at the end of the script, which causes the while
1441 # reaped at the end of the script, which causes the while
1442 # loop to exit and closes the pipe. Sigh.
1442 # loop to exit and closes the pipe. Sigh.
1443 script.append(
1443 script.append(
1444 b'rtendtracing() {\n'
1444 b'rtendtracing() {\n'
1445 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1445 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1446 b' rm -f "$TESTTMP/.still-running"\n'
1446 b' rm -f "$TESTTMP/.still-running"\n'
1447 b'}\n'
1447 b'}\n'
1448 b'trap "rtendtracing" 0\n'
1448 b'trap "rtendtracing" 0\n'
1449 b'touch "$TESTTMP/.still-running"\n'
1449 b'touch "$TESTTMP/.still-running"\n'
1450 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1450 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1451 b'> %(catapult)s &\n'
1451 b'> %(catapult)s &\n'
1452 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1452 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1453 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1453 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1454 % {
1454 % {
1455 'name': self.name,
1455 'name': self.name,
1456 'session': session,
1456 'session': session,
1457 'catapult': hgcatapult,
1457 'catapult': hgcatapult,
1458 }
1458 }
1459 )
1459 )
1460
1460
1461 if self._case:
1461 if self._case:
1462 casestr = b'#'.join(self._case)
1462 casestr = b'#'.join(self._case)
1463 if isinstance(self._case, str):
1463 if isinstance(self._case, str):
1464 quoted = shellquote(casestr)
1464 quoted = shellquote(casestr)
1465 else:
1465 else:
1466 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1466 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1467 script.append(b'TESTCASE=%s\n' % quoted)
1467 script.append(b'TESTCASE=%s\n' % quoted)
1468 script.append(b'export TESTCASE\n')
1468 script.append(b'export TESTCASE\n')
1469
1469
1470 n = 0
1470 n = 0
1471 for n, l in enumerate(lines):
1471 for n, l in enumerate(lines):
1472 if not l.endswith(b'\n'):
1472 if not l.endswith(b'\n'):
1473 l += b'\n'
1473 l += b'\n'
1474 if l.startswith(b'#require'):
1474 if l.startswith(b'#require'):
1475 lsplit = l.split()
1475 lsplit = l.split()
1476 if len(lsplit) < 2 or lsplit[0] != b'#require':
1476 if len(lsplit) < 2 or lsplit[0] != b'#require':
1477 after.setdefault(pos, []).append(' !!! invalid #require\n')
1477 after.setdefault(pos, []).append(' !!! invalid #require\n')
1478 if not skipping:
1478 if not skipping:
1479 haveresult, message = self._hghave(lsplit[1:])
1479 haveresult, message = self._hghave(lsplit[1:])
1480 if not haveresult:
1480 if not haveresult:
1481 script = [b'echo "%s"\nexit 80\n' % message]
1481 script = [b'echo "%s"\nexit 80\n' % message]
1482 break
1482 break
1483 after.setdefault(pos, []).append(l)
1483 after.setdefault(pos, []).append(l)
1484 elif l.startswith(b'#if'):
1484 elif l.startswith(b'#if'):
1485 lsplit = l.split()
1485 lsplit = l.split()
1486 if len(lsplit) < 2 or lsplit[0] != b'#if':
1486 if len(lsplit) < 2 or lsplit[0] != b'#if':
1487 after.setdefault(pos, []).append(' !!! invalid #if\n')
1487 after.setdefault(pos, []).append(' !!! invalid #if\n')
1488 if skipping is not None:
1488 if skipping is not None:
1489 after.setdefault(pos, []).append(' !!! nested #if\n')
1489 after.setdefault(pos, []).append(' !!! nested #if\n')
1490 skipping = not self._iftest(lsplit[1:])
1490 skipping = not self._iftest(lsplit[1:])
1491 after.setdefault(pos, []).append(l)
1491 after.setdefault(pos, []).append(l)
1492 elif l.startswith(b'#else'):
1492 elif l.startswith(b'#else'):
1493 if skipping is None:
1493 if skipping is None:
1494 after.setdefault(pos, []).append(' !!! missing #if\n')
1494 after.setdefault(pos, []).append(' !!! missing #if\n')
1495 skipping = not skipping
1495 skipping = not skipping
1496 after.setdefault(pos, []).append(l)
1496 after.setdefault(pos, []).append(l)
1497 elif l.startswith(b'#endif'):
1497 elif l.startswith(b'#endif'):
1498 if skipping is None:
1498 if skipping is None:
1499 after.setdefault(pos, []).append(' !!! missing #if\n')
1499 after.setdefault(pos, []).append(' !!! missing #if\n')
1500 skipping = None
1500 skipping = None
1501 after.setdefault(pos, []).append(l)
1501 after.setdefault(pos, []).append(l)
1502 elif skipping:
1502 elif skipping:
1503 after.setdefault(pos, []).append(l)
1503 after.setdefault(pos, []).append(l)
1504 elif l.startswith(b' >>> '): # python inlines
1504 elif l.startswith(b' >>> '): # python inlines
1505 after.setdefault(pos, []).append(l)
1505 after.setdefault(pos, []).append(l)
1506 prepos = pos
1506 prepos = pos
1507 pos = n
1507 pos = n
1508 if not inpython:
1508 if not inpython:
1509 # We've just entered a Python block. Add the header.
1509 # We've just entered a Python block. Add the header.
1510 inpython = True
1510 inpython = True
1511 addsalt(prepos, False) # Make sure we report the exit code.
1511 addsalt(prepos, False) # Make sure we report the exit code.
1512 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1512 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1513 addsalt(n, True)
1513 addsalt(n, True)
1514 script.append(l[2:])
1514 script.append(l[2:])
1515 elif l.startswith(b' ... '): # python inlines
1515 elif l.startswith(b' ... '): # python inlines
1516 after.setdefault(prepos, []).append(l)
1516 after.setdefault(prepos, []).append(l)
1517 script.append(l[2:])
1517 script.append(l[2:])
1518 elif l.startswith(b' $ '): # commands
1518 elif l.startswith(b' $ '): # commands
1519 if inpython:
1519 if inpython:
1520 script.append(b'EOF\n')
1520 script.append(b'EOF\n')
1521 inpython = False
1521 inpython = False
1522 after.setdefault(pos, []).append(l)
1522 after.setdefault(pos, []).append(l)
1523 prepos = pos
1523 prepos = pos
1524 pos = n
1524 pos = n
1525 addsalt(n, False)
1525 addsalt(n, False)
1526 rawcmd = l[4:]
1526 rawcmd = l[4:]
1527 cmd = rawcmd.split()
1527 cmd = rawcmd.split()
1528 toggletrace(rawcmd)
1528 toggletrace(rawcmd)
1529 if len(cmd) == 2 and cmd[0] == b'cd':
1529 if len(cmd) == 2 and cmd[0] == b'cd':
1530 l = b' $ cd %s || exit 1\n' % cmd[1]
1530 l = b' $ cd %s || exit 1\n' % cmd[1]
1531 script.append(rawcmd)
1531 script.append(rawcmd)
1532 elif l.startswith(b' > '): # continuations
1532 elif l.startswith(b' > '): # continuations
1533 after.setdefault(prepos, []).append(l)
1533 after.setdefault(prepos, []).append(l)
1534 script.append(l[4:])
1534 script.append(l[4:])
1535 elif l.startswith(b' '): # results
1535 elif l.startswith(b' '): # results
1536 # Queue up a list of expected results.
1536 # Queue up a list of expected results.
1537 expected.setdefault(pos, []).append(l[2:])
1537 expected.setdefault(pos, []).append(l[2:])
1538 else:
1538 else:
1539 if inpython:
1539 if inpython:
1540 script.append(b'EOF\n')
1540 script.append(b'EOF\n')
1541 inpython = False
1541 inpython = False
1542 # Non-command/result. Queue up for merged output.
1542 # Non-command/result. Queue up for merged output.
1543 after.setdefault(pos, []).append(l)
1543 after.setdefault(pos, []).append(l)
1544
1544
1545 if inpython:
1545 if inpython:
1546 script.append(b'EOF\n')
1546 script.append(b'EOF\n')
1547 if skipping is not None:
1547 if skipping is not None:
1548 after.setdefault(pos, []).append(' !!! missing #endif\n')
1548 after.setdefault(pos, []).append(' !!! missing #endif\n')
1549 addsalt(n + 1, False)
1549 addsalt(n + 1, False)
1550 # Need to end any current per-command trace
1550 # Need to end any current per-command trace
1551 if activetrace:
1551 if activetrace:
1552 toggletrace()
1552 toggletrace()
1553 return salt, script, after, expected
1553 return salt, script, after, expected
1554
1554
1555 def _processoutput(self, exitcode, output, salt, after, expected):
1555 def _processoutput(self, exitcode, output, salt, after, expected):
1556 # Merge the script output back into a unified test.
1556 # Merge the script output back into a unified test.
1557 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1557 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1558 if exitcode != 0:
1558 if exitcode != 0:
1559 warnonly = 3
1559 warnonly = 3
1560
1560
1561 pos = -1
1561 pos = -1
1562 postout = []
1562 postout = []
1563 for l in output:
1563 for l in output:
1564 lout, lcmd = l, None
1564 lout, lcmd = l, None
1565 if salt in l:
1565 if salt in l:
1566 lout, lcmd = l.split(salt, 1)
1566 lout, lcmd = l.split(salt, 1)
1567
1567
1568 while lout:
1568 while lout:
1569 if not lout.endswith(b'\n'):
1569 if not lout.endswith(b'\n'):
1570 lout += b' (no-eol)\n'
1570 lout += b' (no-eol)\n'
1571
1571
1572 # Find the expected output at the current position.
1572 # Find the expected output at the current position.
1573 els = [None]
1573 els = [None]
1574 if expected.get(pos, None):
1574 if expected.get(pos, None):
1575 els = expected[pos]
1575 els = expected[pos]
1576
1576
1577 optional = []
1577 optional = []
1578 for i, el in enumerate(els):
1578 for i, el in enumerate(els):
1579 r = False
1579 r = False
1580 if el:
1580 if el:
1581 r, exact = self.linematch(el, lout)
1581 r, exact = self.linematch(el, lout)
1582 if isinstance(r, str):
1582 if isinstance(r, str):
1583 if r == '-glob':
1583 if r == '-glob':
1584 lout = ''.join(el.rsplit(' (glob)', 1))
1584 lout = ''.join(el.rsplit(' (glob)', 1))
1585 r = '' # Warn only this line.
1585 r = '' # Warn only this line.
1586 elif r == "retry":
1586 elif r == "retry":
1587 postout.append(b' ' + el)
1587 postout.append(b' ' + el)
1588 else:
1588 else:
1589 log('\ninfo, unknown linematch result: %r\n' % r)
1589 log('\ninfo, unknown linematch result: %r\n' % r)
1590 r = False
1590 r = False
1591 if r:
1591 if r:
1592 els.pop(i)
1592 els.pop(i)
1593 break
1593 break
1594 if el:
1594 if el:
1595 if el.endswith(b" (?)\n"):
1595 if el.endswith(b" (?)\n"):
1596 optional.append(i)
1596 optional.append(i)
1597 else:
1597 else:
1598 m = optline.match(el)
1598 m = optline.match(el)
1599 if m:
1599 if m:
1600 conditions = [
1600 conditions = [
1601 c for c in m.group(2).split(b' ')]
1601 c for c in m.group(2).split(b' ')]
1602
1602
1603 if not self._iftest(conditions):
1603 if not self._iftest(conditions):
1604 optional.append(i)
1604 optional.append(i)
1605 if exact:
1605 if exact:
1606 # Don't allow line to be matches against a later
1606 # Don't allow line to be matches against a later
1607 # line in the output
1607 # line in the output
1608 els.pop(i)
1608 els.pop(i)
1609 break
1609 break
1610
1610
1611 if r:
1611 if r:
1612 if r == "retry":
1612 if r == "retry":
1613 continue
1613 continue
1614 # clean up any optional leftovers
1614 # clean up any optional leftovers
1615 for i in optional:
1615 for i in optional:
1616 postout.append(b' ' + els[i])
1616 postout.append(b' ' + els[i])
1617 for i in reversed(optional):
1617 for i in reversed(optional):
1618 del els[i]
1618 del els[i]
1619 postout.append(b' ' + el)
1619 postout.append(b' ' + el)
1620 else:
1620 else:
1621 if self.NEEDESCAPE(lout):
1621 if self.NEEDESCAPE(lout):
1622 lout = TTest._stringescape(b'%s (esc)\n' %
1622 lout = TTest._stringescape(b'%s (esc)\n' %
1623 lout.rstrip(b'\n'))
1623 lout.rstrip(b'\n'))
1624 postout.append(b' ' + lout) # Let diff deal with it.
1624 postout.append(b' ' + lout) # Let diff deal with it.
1625 if r != '': # If line failed.
1625 if r != '': # If line failed.
1626 warnonly = 3 # for sure not
1626 warnonly = 3 # for sure not
1627 elif warnonly == 1: # Is "not yet" and line is warn only.
1627 elif warnonly == 1: # Is "not yet" and line is warn only.
1628 warnonly = 2 # Yes do warn.
1628 warnonly = 2 # Yes do warn.
1629 break
1629 break
1630 else:
1630 else:
1631 # clean up any optional leftovers
1631 # clean up any optional leftovers
1632 while expected.get(pos, None):
1632 while expected.get(pos, None):
1633 el = expected[pos].pop(0)
1633 el = expected[pos].pop(0)
1634 if el:
1634 if el:
1635 if not el.endswith(b" (?)\n"):
1635 if not el.endswith(b" (?)\n"):
1636 m = optline.match(el)
1636 m = optline.match(el)
1637 if m:
1637 if m:
1638 conditions = [c for c in m.group(2).split(b' ')]
1638 conditions = [c for c in m.group(2).split(b' ')]
1639
1639
1640 if self._iftest(conditions):
1640 if self._iftest(conditions):
1641 # Don't append as optional line
1641 # Don't append as optional line
1642 continue
1642 continue
1643 else:
1643 else:
1644 continue
1644 continue
1645 postout.append(b' ' + el)
1645 postout.append(b' ' + el)
1646
1646
1647 if lcmd:
1647 if lcmd:
1648 # Add on last return code.
1648 # Add on last return code.
1649 ret = int(lcmd.split()[1])
1649 ret = int(lcmd.split()[1])
1650 if ret != 0:
1650 if ret != 0:
1651 postout.append(b' [%d]\n' % ret)
1651 postout.append(b' [%d]\n' % ret)
1652 if pos in after:
1652 if pos in after:
1653 # Merge in non-active test bits.
1653 # Merge in non-active test bits.
1654 postout += after.pop(pos)
1654 postout += after.pop(pos)
1655 pos = int(lcmd.split()[0])
1655 pos = int(lcmd.split()[0])
1656
1656
1657 if pos in after:
1657 if pos in after:
1658 postout += after.pop(pos)
1658 postout += after.pop(pos)
1659
1659
1660 if warnonly == 2:
1660 if warnonly == 2:
1661 exitcode = False # Set exitcode to warned.
1661 exitcode = False # Set exitcode to warned.
1662
1662
1663 return exitcode, postout
1663 return exitcode, postout
1664
1664
1665 @staticmethod
1665 @staticmethod
1666 def rematch(el, l):
1666 def rematch(el, l):
1667 try:
1667 try:
1668 el = b'(?:' + el + b')'
1668 el = b'(?:' + el + b')'
1669 # use \Z to ensure that the regex matches to the end of the string
1669 # use \Z to ensure that the regex matches to the end of the string
1670 if os.name == 'nt':
1670 if os.name == 'nt':
1671 return re.match(el + br'\r?\n\Z', l)
1671 return re.match(el + br'\r?\n\Z', l)
1672 return re.match(el + br'\n\Z', l)
1672 return re.match(el + br'\n\Z', l)
1673 except re.error:
1673 except re.error:
1674 # el is an invalid regex
1674 # el is an invalid regex
1675 return False
1675 return False
1676
1676
1677 @staticmethod
1677 @staticmethod
1678 def globmatch(el, l):
1678 def globmatch(el, l):
1679 # The only supported special characters are * and ? plus / which also
1679 # The only supported special characters are * and ? plus / which also
1680 # matches \ on windows. Escaping of these characters is supported.
1680 # matches \ on windows. Escaping of these characters is supported.
1681 if el + b'\n' == l:
1681 if el + b'\n' == l:
1682 if os.altsep:
1682 if os.altsep:
1683 # matching on "/" is not needed for this line
1683 # matching on "/" is not needed for this line
1684 for pat in checkcodeglobpats:
1684 for pat in checkcodeglobpats:
1685 if pat.match(el):
1685 if pat.match(el):
1686 return True
1686 return True
1687 return b'-glob'
1687 return b'-glob'
1688 return True
1688 return True
1689 el = el.replace(b'$LOCALIP', b'*')
1689 el = el.replace(b'$LOCALIP', b'*')
1690 i, n = 0, len(el)
1690 i, n = 0, len(el)
1691 res = b''
1691 res = b''
1692 while i < n:
1692 while i < n:
1693 c = el[i:i + 1]
1693 c = el[i:i + 1]
1694 i += 1
1694 i += 1
1695 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1695 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1696 res += el[i - 1:i + 1]
1696 res += el[i - 1:i + 1]
1697 i += 1
1697 i += 1
1698 elif c == b'*':
1698 elif c == b'*':
1699 res += b'.*'
1699 res += b'.*'
1700 elif c == b'?':
1700 elif c == b'?':
1701 res += b'.'
1701 res += b'.'
1702 elif c == b'/' and os.altsep:
1702 elif c == b'/' and os.altsep:
1703 res += b'[/\\\\]'
1703 res += b'[/\\\\]'
1704 else:
1704 else:
1705 res += re.escape(c)
1705 res += re.escape(c)
1706 return TTest.rematch(res, l)
1706 return TTest.rematch(res, l)
1707
1707
1708 def linematch(self, el, l):
1708 def linematch(self, el, l):
1709 if el == l: # perfect match (fast)
1709 if el == l: # perfect match (fast)
1710 return True, True
1710 return True, True
1711 retry = False
1711 retry = False
1712 if el.endswith(b" (?)\n"):
1712 if el.endswith(b" (?)\n"):
1713 retry = "retry"
1713 retry = "retry"
1714 el = el[:-5] + b"\n"
1714 el = el[:-5] + b"\n"
1715 else:
1715 else:
1716 m = optline.match(el)
1716 m = optline.match(el)
1717 if m:
1717 if m:
1718 conditions = [c for c in m.group(2).split(b' ')]
1718 conditions = [c for c in m.group(2).split(b' ')]
1719
1719
1720 el = m.group(1) + b"\n"
1720 el = m.group(1) + b"\n"
1721 if not self._iftest(conditions):
1721 if not self._iftest(conditions):
1722 retry = "retry" # Not required by listed features
1722 retry = "retry" # Not required by listed features
1723
1723
1724 if el.endswith(b" (esc)\n"):
1724 if el.endswith(b" (esc)\n"):
1725 if PYTHON3:
1725 if PYTHON3:
1726 el = el[:-7].decode('unicode_escape') + '\n'
1726 el = el[:-7].decode('unicode_escape') + '\n'
1727 el = el.encode('utf-8')
1727 el = el.encode('utf-8')
1728 else:
1728 else:
1729 el = el[:-7].decode('string-escape') + '\n'
1729 el = el[:-7].decode('string-escape') + '\n'
1730 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1730 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1731 return True, True
1731 return True, True
1732 if el.endswith(b" (re)\n"):
1732 if el.endswith(b" (re)\n"):
1733 return (TTest.rematch(el[:-6], l) or retry), False
1733 return (TTest.rematch(el[:-6], l) or retry), False
1734 if el.endswith(b" (glob)\n"):
1734 if el.endswith(b" (glob)\n"):
1735 # ignore '(glob)' added to l by 'replacements'
1735 # ignore '(glob)' added to l by 'replacements'
1736 if l.endswith(b" (glob)\n"):
1736 if l.endswith(b" (glob)\n"):
1737 l = l[:-8] + b"\n"
1737 l = l[:-8] + b"\n"
1738 return (TTest.globmatch(el[:-8], l) or retry), False
1738 return (TTest.globmatch(el[:-8], l) or retry), False
1739 if os.altsep:
1739 if os.altsep:
1740 _l = l.replace(b'\\', b'/')
1740 _l = l.replace(b'\\', b'/')
1741 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1741 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1742 return True, True
1742 return True, True
1743 return retry, True
1743 return retry, True
1744
1744
1745 @staticmethod
1745 @staticmethod
1746 def parsehghaveoutput(lines):
1746 def parsehghaveoutput(lines):
1747 '''Parse hghave log lines.
1747 '''Parse hghave log lines.
1748
1748
1749 Return tuple of lists (missing, failed):
1749 Return tuple of lists (missing, failed):
1750 * the missing/unknown features
1750 * the missing/unknown features
1751 * the features for which existence check failed'''
1751 * the features for which existence check failed'''
1752 missing = []
1752 missing = []
1753 failed = []
1753 failed = []
1754 for line in lines:
1754 for line in lines:
1755 if line.startswith(TTest.SKIPPED_PREFIX):
1755 if line.startswith(TTest.SKIPPED_PREFIX):
1756 line = line.splitlines()[0]
1756 line = line.splitlines()[0]
1757 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1757 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1758 elif line.startswith(TTest.FAILED_PREFIX):
1758 elif line.startswith(TTest.FAILED_PREFIX):
1759 line = line.splitlines()[0]
1759 line = line.splitlines()[0]
1760 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1760 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1761
1761
1762 return missing, failed
1762 return missing, failed
1763
1763
1764 @staticmethod
1764 @staticmethod
1765 def _escapef(m):
1765 def _escapef(m):
1766 return TTest.ESCAPEMAP[m.group(0)]
1766 return TTest.ESCAPEMAP[m.group(0)]
1767
1767
1768 @staticmethod
1768 @staticmethod
1769 def _stringescape(s):
1769 def _stringescape(s):
1770 return TTest.ESCAPESUB(TTest._escapef, s)
1770 return TTest.ESCAPESUB(TTest._escapef, s)
1771
1771
1772 iolock = threading.RLock()
1772 iolock = threading.RLock()
1773 firstlock = threading.RLock()
1773 firstlock = threading.RLock()
1774 firsterror = False
1774 firsterror = False
1775
1775
1776 class TestResult(unittest._TextTestResult):
1776 class TestResult(unittest._TextTestResult):
1777 """Holds results when executing via unittest."""
1777 """Holds results when executing via unittest."""
1778 # Don't worry too much about accessing the non-public _TextTestResult.
1778 # Don't worry too much about accessing the non-public _TextTestResult.
1779 # It is relatively common in Python testing tools.
1779 # It is relatively common in Python testing tools.
1780 def __init__(self, options, *args, **kwargs):
1780 def __init__(self, options, *args, **kwargs):
1781 super(TestResult, self).__init__(*args, **kwargs)
1781 super(TestResult, self).__init__(*args, **kwargs)
1782
1782
1783 self._options = options
1783 self._options = options
1784
1784
1785 # unittest.TestResult didn't have skipped until 2.7. We need to
1785 # unittest.TestResult didn't have skipped until 2.7. We need to
1786 # polyfill it.
1786 # polyfill it.
1787 self.skipped = []
1787 self.skipped = []
1788
1788
1789 # We have a custom "ignored" result that isn't present in any Python
1789 # We have a custom "ignored" result that isn't present in any Python
1790 # unittest implementation. It is very similar to skipped. It may make
1790 # unittest implementation. It is very similar to skipped. It may make
1791 # sense to map it into skip some day.
1791 # sense to map it into skip some day.
1792 self.ignored = []
1792 self.ignored = []
1793
1793
1794 self.times = []
1794 self.times = []
1795 self._firststarttime = None
1795 self._firststarttime = None
1796 # Data stored for the benefit of generating xunit reports.
1796 # Data stored for the benefit of generating xunit reports.
1797 self.successes = []
1797 self.successes = []
1798 self.faildata = {}
1798 self.faildata = {}
1799
1799
1800 if options.color == 'auto':
1800 if options.color == 'auto':
1801 self.color = pygmentspresent and self.stream.isatty()
1801 self.color = pygmentspresent and self.stream.isatty()
1802 elif options.color == 'never':
1802 elif options.color == 'never':
1803 self.color = False
1803 self.color = False
1804 else: # 'always', for testing purposes
1804 else: # 'always', for testing purposes
1805 self.color = pygmentspresent
1805 self.color = pygmentspresent
1806
1806
1807 def onStart(self, test):
1807 def onStart(self, test):
1808 """ Can be overriden by custom TestResult
1808 """ Can be overriden by custom TestResult
1809 """
1809 """
1810
1810
1811 def onEnd(self):
1811 def onEnd(self):
1812 """ Can be overriden by custom TestResult
1812 """ Can be overriden by custom TestResult
1813 """
1813 """
1814
1814
1815 def addFailure(self, test, reason):
1815 def addFailure(self, test, reason):
1816 self.failures.append((test, reason))
1816 self.failures.append((test, reason))
1817
1817
1818 if self._options.first:
1818 if self._options.first:
1819 self.stop()
1819 self.stop()
1820 else:
1820 else:
1821 with iolock:
1821 with iolock:
1822 if reason == "timed out":
1822 if reason == "timed out":
1823 self.stream.write('t')
1823 self.stream.write('t')
1824 else:
1824 else:
1825 if not self._options.nodiff:
1825 if not self._options.nodiff:
1826 self.stream.write('\n')
1826 self.stream.write('\n')
1827 # Exclude the '\n' from highlighting to lex correctly
1827 # Exclude the '\n' from highlighting to lex correctly
1828 formatted = 'ERROR: %s output changed\n' % test
1828 formatted = 'ERROR: %s output changed\n' % test
1829 self.stream.write(highlightmsg(formatted, self.color))
1829 self.stream.write(highlightmsg(formatted, self.color))
1830 self.stream.write('!')
1830 self.stream.write('!')
1831
1831
1832 self.stream.flush()
1832 self.stream.flush()
1833
1833
1834 def addSuccess(self, test):
1834 def addSuccess(self, test):
1835 with iolock:
1835 with iolock:
1836 super(TestResult, self).addSuccess(test)
1836 super(TestResult, self).addSuccess(test)
1837 self.successes.append(test)
1837 self.successes.append(test)
1838
1838
1839 def addError(self, test, err):
1839 def addError(self, test, err):
1840 super(TestResult, self).addError(test, err)
1840 super(TestResult, self).addError(test, err)
1841 if self._options.first:
1841 if self._options.first:
1842 self.stop()
1842 self.stop()
1843
1843
1844 # Polyfill.
1844 # Polyfill.
1845 def addSkip(self, test, reason):
1845 def addSkip(self, test, reason):
1846 self.skipped.append((test, reason))
1846 self.skipped.append((test, reason))
1847 with iolock:
1847 with iolock:
1848 if self.showAll:
1848 if self.showAll:
1849 self.stream.writeln('skipped %s' % reason)
1849 self.stream.writeln('skipped %s' % reason)
1850 else:
1850 else:
1851 self.stream.write('s')
1851 self.stream.write('s')
1852 self.stream.flush()
1852 self.stream.flush()
1853
1853
1854 def addIgnore(self, test, reason):
1854 def addIgnore(self, test, reason):
1855 self.ignored.append((test, reason))
1855 self.ignored.append((test, reason))
1856 with iolock:
1856 with iolock:
1857 if self.showAll:
1857 if self.showAll:
1858 self.stream.writeln('ignored %s' % reason)
1858 self.stream.writeln('ignored %s' % reason)
1859 else:
1859 else:
1860 if reason not in ('not retesting', "doesn't match keyword"):
1860 if reason not in ('not retesting', "doesn't match keyword"):
1861 self.stream.write('i')
1861 self.stream.write('i')
1862 else:
1862 else:
1863 self.testsRun += 1
1863 self.testsRun += 1
1864 self.stream.flush()
1864 self.stream.flush()
1865
1865
1866 def addOutputMismatch(self, test, ret, got, expected):
1866 def addOutputMismatch(self, test, ret, got, expected):
1867 """Record a mismatch in test output for a particular test."""
1867 """Record a mismatch in test output for a particular test."""
1868 if self.shouldStop or firsterror:
1868 if self.shouldStop or firsterror:
1869 # don't print, some other test case already failed and
1869 # don't print, some other test case already failed and
1870 # printed, we're just stale and probably failed due to our
1870 # printed, we're just stale and probably failed due to our
1871 # temp dir getting cleaned up.
1871 # temp dir getting cleaned up.
1872 return
1872 return
1873
1873
1874 accepted = False
1874 accepted = False
1875 lines = []
1875 lines = []
1876
1876
1877 with iolock:
1877 with iolock:
1878 if self._options.nodiff:
1878 if self._options.nodiff:
1879 pass
1879 pass
1880 elif self._options.view:
1880 elif self._options.view:
1881 v = self._options.view
1881 v = self._options.view
1882 os.system(r"%s %s %s" %
1882 os.system(r"%s %s %s" %
1883 (v, _strpath(test.refpath), _strpath(test.errpath)))
1883 (v, _strpath(test.refpath), _strpath(test.errpath)))
1884 else:
1884 else:
1885 servefail, lines = getdiff(expected, got,
1885 servefail, lines = getdiff(expected, got,
1886 test.refpath, test.errpath)
1886 test.refpath, test.errpath)
1887 self.stream.write('\n')
1887 self.stream.write('\n')
1888 for line in lines:
1888 for line in lines:
1889 line = highlightdiff(line, self.color)
1889 line = highlightdiff(line, self.color)
1890 if PYTHON3:
1890 if PYTHON3:
1891 self.stream.flush()
1891 self.stream.flush()
1892 self.stream.buffer.write(line)
1892 self.stream.buffer.write(line)
1893 self.stream.buffer.flush()
1893 self.stream.buffer.flush()
1894 else:
1894 else:
1895 self.stream.write(line)
1895 self.stream.write(line)
1896 self.stream.flush()
1896 self.stream.flush()
1897
1897
1898 if servefail:
1898 if servefail:
1899 raise test.failureException(
1899 raise test.failureException(
1900 'server failed to start (HGPORT=%s)' % test._startport)
1900 'server failed to start (HGPORT=%s)' % test._startport)
1901
1901
1902 # handle interactive prompt without releasing iolock
1902 # handle interactive prompt without releasing iolock
1903 if self._options.interactive:
1903 if self._options.interactive:
1904 if test.readrefout() != expected:
1904 if test.readrefout() != expected:
1905 self.stream.write(
1905 self.stream.write(
1906 'Reference output has changed (run again to prompt '
1906 'Reference output has changed (run again to prompt '
1907 'changes)')
1907 'changes)')
1908 else:
1908 else:
1909 self.stream.write('Accept this change? [n] ')
1909 self.stream.write('Accept this change? [n] ')
1910 self.stream.flush()
1910 self.stream.flush()
1911 answer = sys.stdin.readline().strip()
1911 answer = sys.stdin.readline().strip()
1912 if answer.lower() in ('y', 'yes'):
1912 if answer.lower() in ('y', 'yes'):
1913 if test.path.endswith(b'.t'):
1913 if test.path.endswith(b'.t'):
1914 rename(test.errpath, test.path)
1914 rename(test.errpath, test.path)
1915 else:
1915 else:
1916 rename(test.errpath, '%s.out' % test.path)
1916 rename(test.errpath, '%s.out' % test.path)
1917 accepted = True
1917 accepted = True
1918 if not accepted:
1918 if not accepted:
1919 self.faildata[test.name] = b''.join(lines)
1919 self.faildata[test.name] = b''.join(lines)
1920
1920
1921 return accepted
1921 return accepted
1922
1922
1923 def startTest(self, test):
1923 def startTest(self, test):
1924 super(TestResult, self).startTest(test)
1924 super(TestResult, self).startTest(test)
1925
1925
1926 # os.times module computes the user time and system time spent by
1926 # os.times module computes the user time and system time spent by
1927 # child's processes along with real elapsed time taken by a process.
1927 # child's processes along with real elapsed time taken by a process.
1928 # This module has one limitation. It can only work for Linux user
1928 # This module has one limitation. It can only work for Linux user
1929 # and not for Windows.
1929 # and not for Windows.
1930 test.started = os.times()
1930 test.started = os.times()
1931 if self._firststarttime is None: # thread racy but irrelevant
1931 if self._firststarttime is None: # thread racy but irrelevant
1932 self._firststarttime = test.started[4]
1932 self._firststarttime = test.started[4]
1933
1933
1934 def stopTest(self, test, interrupted=False):
1934 def stopTest(self, test, interrupted=False):
1935 super(TestResult, self).stopTest(test)
1935 super(TestResult, self).stopTest(test)
1936
1936
1937 test.stopped = os.times()
1937 test.stopped = os.times()
1938
1938
1939 starttime = test.started
1939 starttime = test.started
1940 endtime = test.stopped
1940 endtime = test.stopped
1941 origin = self._firststarttime
1941 origin = self._firststarttime
1942 self.times.append((test.name,
1942 self.times.append((test.name,
1943 endtime[2] - starttime[2], # user space CPU time
1943 endtime[2] - starttime[2], # user space CPU time
1944 endtime[3] - starttime[3], # sys space CPU time
1944 endtime[3] - starttime[3], # sys space CPU time
1945 endtime[4] - starttime[4], # real time
1945 endtime[4] - starttime[4], # real time
1946 starttime[4] - origin, # start date in run context
1946 starttime[4] - origin, # start date in run context
1947 endtime[4] - origin, # end date in run context
1947 endtime[4] - origin, # end date in run context
1948 ))
1948 ))
1949
1949
1950 if interrupted:
1950 if interrupted:
1951 with iolock:
1951 with iolock:
1952 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1952 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1953 test.name, self.times[-1][3]))
1953 test.name, self.times[-1][3]))
1954
1954
1955 def getTestResult():
1955 def getTestResult():
1956 """
1956 """
1957 Returns the relevant test result
1957 Returns the relevant test result
1958 """
1958 """
1959 if "CUSTOM_TEST_RESULT" in os.environ:
1959 if "CUSTOM_TEST_RESULT" in os.environ:
1960 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
1960 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
1961 return testresultmodule.TestResult
1961 return testresultmodule.TestResult
1962 else:
1962 else:
1963 return TestResult
1963 return TestResult
1964
1964
1965 class TestSuite(unittest.TestSuite):
1965 class TestSuite(unittest.TestSuite):
1966 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1966 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1967
1967
1968 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1968 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1969 retest=False, keywords=None, loop=False, runs_per_test=1,
1969 retest=False, keywords=None, loop=False, runs_per_test=1,
1970 loadtest=None, showchannels=False,
1970 loadtest=None, showchannels=False,
1971 *args, **kwargs):
1971 *args, **kwargs):
1972 """Create a new instance that can run tests with a configuration.
1972 """Create a new instance that can run tests with a configuration.
1973
1973
1974 testdir specifies the directory where tests are executed from. This
1974 testdir specifies the directory where tests are executed from. This
1975 is typically the ``tests`` directory from Mercurial's source
1975 is typically the ``tests`` directory from Mercurial's source
1976 repository.
1976 repository.
1977
1977
1978 jobs specifies the number of jobs to run concurrently. Each test
1978 jobs specifies the number of jobs to run concurrently. Each test
1979 executes on its own thread. Tests actually spawn new processes, so
1979 executes on its own thread. Tests actually spawn new processes, so
1980 state mutation should not be an issue.
1980 state mutation should not be an issue.
1981
1981
1982 If there is only one job, it will use the main thread.
1982 If there is only one job, it will use the main thread.
1983
1983
1984 whitelist and blacklist denote tests that have been whitelisted and
1984 whitelist and blacklist denote tests that have been whitelisted and
1985 blacklisted, respectively. These arguments don't belong in TestSuite.
1985 blacklisted, respectively. These arguments don't belong in TestSuite.
1986 Instead, whitelist and blacklist should be handled by the thing that
1986 Instead, whitelist and blacklist should be handled by the thing that
1987 populates the TestSuite with tests. They are present to preserve
1987 populates the TestSuite with tests. They are present to preserve
1988 backwards compatible behavior which reports skipped tests as part
1988 backwards compatible behavior which reports skipped tests as part
1989 of the results.
1989 of the results.
1990
1990
1991 retest denotes whether to retest failed tests. This arguably belongs
1991 retest denotes whether to retest failed tests. This arguably belongs
1992 outside of TestSuite.
1992 outside of TestSuite.
1993
1993
1994 keywords denotes key words that will be used to filter which tests
1994 keywords denotes key words that will be used to filter which tests
1995 to execute. This arguably belongs outside of TestSuite.
1995 to execute. This arguably belongs outside of TestSuite.
1996
1996
1997 loop denotes whether to loop over tests forever.
1997 loop denotes whether to loop over tests forever.
1998 """
1998 """
1999 super(TestSuite, self).__init__(*args, **kwargs)
1999 super(TestSuite, self).__init__(*args, **kwargs)
2000
2000
2001 self._jobs = jobs
2001 self._jobs = jobs
2002 self._whitelist = whitelist
2002 self._whitelist = whitelist
2003 self._blacklist = blacklist
2003 self._blacklist = blacklist
2004 self._retest = retest
2004 self._retest = retest
2005 self._keywords = keywords
2005 self._keywords = keywords
2006 self._loop = loop
2006 self._loop = loop
2007 self._runs_per_test = runs_per_test
2007 self._runs_per_test = runs_per_test
2008 self._loadtest = loadtest
2008 self._loadtest = loadtest
2009 self._showchannels = showchannels
2009 self._showchannels = showchannels
2010
2010
2011 def run(self, result):
2011 def run(self, result):
2012 # We have a number of filters that need to be applied. We do this
2012 # We have a number of filters that need to be applied. We do this
2013 # here instead of inside Test because it makes the running logic for
2013 # here instead of inside Test because it makes the running logic for
2014 # Test simpler.
2014 # Test simpler.
2015 tests = []
2015 tests = []
2016 num_tests = [0]
2016 num_tests = [0]
2017 for test in self._tests:
2017 for test in self._tests:
2018 def get():
2018 def get():
2019 num_tests[0] += 1
2019 num_tests[0] += 1
2020 if getattr(test, 'should_reload', False):
2020 if getattr(test, 'should_reload', False):
2021 return self._loadtest(test, num_tests[0])
2021 return self._loadtest(test, num_tests[0])
2022 return test
2022 return test
2023 if not os.path.exists(test.path):
2023 if not os.path.exists(test.path):
2024 result.addSkip(test, "Doesn't exist")
2024 result.addSkip(test, "Doesn't exist")
2025 continue
2025 continue
2026
2026
2027 if not (self._whitelist and test.bname in self._whitelist):
2027 if not (self._whitelist and test.bname in self._whitelist):
2028 if self._blacklist and test.bname in self._blacklist:
2028 if self._blacklist and test.bname in self._blacklist:
2029 result.addSkip(test, 'blacklisted')
2029 result.addSkip(test, 'blacklisted')
2030 continue
2030 continue
2031
2031
2032 if self._retest and not os.path.exists(test.errpath):
2032 if self._retest and not os.path.exists(test.errpath):
2033 result.addIgnore(test, 'not retesting')
2033 result.addIgnore(test, 'not retesting')
2034 continue
2034 continue
2035
2035
2036 if self._keywords:
2036 if self._keywords:
2037 with open(test.path, 'rb') as f:
2037 with open(test.path, 'rb') as f:
2038 t = f.read().lower() + test.bname.lower()
2038 t = f.read().lower() + test.bname.lower()
2039 ignored = False
2039 ignored = False
2040 for k in self._keywords.lower().split():
2040 for k in self._keywords.lower().split():
2041 if k not in t:
2041 if k not in t:
2042 result.addIgnore(test, "doesn't match keyword")
2042 result.addIgnore(test, "doesn't match keyword")
2043 ignored = True
2043 ignored = True
2044 break
2044 break
2045
2045
2046 if ignored:
2046 if ignored:
2047 continue
2047 continue
2048 for _ in xrange(self._runs_per_test):
2048 for _ in xrange(self._runs_per_test):
2049 tests.append(get())
2049 tests.append(get())
2050
2050
2051 runtests = list(tests)
2051 runtests = list(tests)
2052 done = queue.Queue()
2052 done = queue.Queue()
2053 running = 0
2053 running = 0
2054
2054
2055 channels = [""] * self._jobs
2055 channels = [""] * self._jobs
2056
2056
2057 def job(test, result):
2057 def job(test, result):
2058 for n, v in enumerate(channels):
2058 for n, v in enumerate(channels):
2059 if not v:
2059 if not v:
2060 channel = n
2060 channel = n
2061 break
2061 break
2062 else:
2062 else:
2063 raise ValueError('Could not find output channel')
2063 raise ValueError('Could not find output channel')
2064 channels[channel] = "=" + test.name[5:].split(".")[0]
2064 channels[channel] = "=" + test.name[5:].split(".")[0]
2065 try:
2065 try:
2066 test(result)
2066 test(result)
2067 done.put(None)
2067 done.put(None)
2068 except KeyboardInterrupt:
2068 except KeyboardInterrupt:
2069 pass
2069 pass
2070 except: # re-raises
2070 except: # re-raises
2071 done.put(('!', test, 'run-test raised an error, see traceback'))
2071 done.put(('!', test, 'run-test raised an error, see traceback'))
2072 raise
2072 raise
2073 finally:
2073 finally:
2074 try:
2074 try:
2075 channels[channel] = ''
2075 channels[channel] = ''
2076 except IndexError:
2076 except IndexError:
2077 pass
2077 pass
2078
2078
2079 def stat():
2079 def stat():
2080 count = 0
2080 count = 0
2081 while channels:
2081 while channels:
2082 d = '\n%03s ' % count
2082 d = '\n%03s ' % count
2083 for n, v in enumerate(channels):
2083 for n, v in enumerate(channels):
2084 if v:
2084 if v:
2085 d += v[0]
2085 d += v[0]
2086 channels[n] = v[1:] or '.'
2086 channels[n] = v[1:] or '.'
2087 else:
2087 else:
2088 d += ' '
2088 d += ' '
2089 d += ' '
2089 d += ' '
2090 with iolock:
2090 with iolock:
2091 sys.stdout.write(d + ' ')
2091 sys.stdout.write(d + ' ')
2092 sys.stdout.flush()
2092 sys.stdout.flush()
2093 for x in xrange(10):
2093 for x in xrange(10):
2094 if channels:
2094 if channels:
2095 time.sleep(.1)
2095 time.sleep(.1)
2096 count += 1
2096 count += 1
2097
2097
2098 stoppedearly = False
2098 stoppedearly = False
2099
2099
2100 if self._showchannels:
2100 if self._showchannels:
2101 statthread = threading.Thread(target=stat, name="stat")
2101 statthread = threading.Thread(target=stat, name="stat")
2102 statthread.start()
2102 statthread.start()
2103
2103
2104 try:
2104 try:
2105 while tests or running:
2105 while tests or running:
2106 if not done.empty() or running == self._jobs or not tests:
2106 if not done.empty() or running == self._jobs or not tests:
2107 try:
2107 try:
2108 done.get(True, 1)
2108 done.get(True, 1)
2109 running -= 1
2109 running -= 1
2110 if result and result.shouldStop:
2110 if result and result.shouldStop:
2111 stoppedearly = True
2111 stoppedearly = True
2112 break
2112 break
2113 except queue.Empty:
2113 except queue.Empty:
2114 continue
2114 continue
2115 if tests and not running == self._jobs:
2115 if tests and not running == self._jobs:
2116 test = tests.pop(0)
2116 test = tests.pop(0)
2117 if self._loop:
2117 if self._loop:
2118 if getattr(test, 'should_reload', False):
2118 if getattr(test, 'should_reload', False):
2119 num_tests[0] += 1
2119 num_tests[0] += 1
2120 tests.append(
2120 tests.append(
2121 self._loadtest(test, num_tests[0]))
2121 self._loadtest(test, num_tests[0]))
2122 else:
2122 else:
2123 tests.append(test)
2123 tests.append(test)
2124 if self._jobs == 1:
2124 if self._jobs == 1:
2125 job(test, result)
2125 job(test, result)
2126 else:
2126 else:
2127 t = threading.Thread(target=job, name=test.name,
2127 t = threading.Thread(target=job, name=test.name,
2128 args=(test, result))
2128 args=(test, result))
2129 t.start()
2129 t.start()
2130 running += 1
2130 running += 1
2131
2131
2132 # If we stop early we still need to wait on started tests to
2132 # If we stop early we still need to wait on started tests to
2133 # finish. Otherwise, there is a race between the test completing
2133 # finish. Otherwise, there is a race between the test completing
2134 # and the test's cleanup code running. This could result in the
2134 # and the test's cleanup code running. This could result in the
2135 # test reporting incorrect.
2135 # test reporting incorrect.
2136 if stoppedearly:
2136 if stoppedearly:
2137 while running:
2137 while running:
2138 try:
2138 try:
2139 done.get(True, 1)
2139 done.get(True, 1)
2140 running -= 1
2140 running -= 1
2141 except queue.Empty:
2141 except queue.Empty:
2142 continue
2142 continue
2143 except KeyboardInterrupt:
2143 except KeyboardInterrupt:
2144 for test in runtests:
2144 for test in runtests:
2145 test.abort()
2145 test.abort()
2146
2146
2147 channels = []
2147 channels = []
2148
2148
2149 return result
2149 return result
2150
2150
2151 # Save the most recent 5 wall-clock runtimes of each test to a
2151 # Save the most recent 5 wall-clock runtimes of each test to a
2152 # human-readable text file named .testtimes. Tests are sorted
2152 # human-readable text file named .testtimes. Tests are sorted
2153 # alphabetically, while times for each test are listed from oldest to
2153 # alphabetically, while times for each test are listed from oldest to
2154 # newest.
2154 # newest.
2155
2155
2156 def loadtimes(outputdir):
2156 def loadtimes(outputdir):
2157 times = []
2157 times = []
2158 try:
2158 try:
2159 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2159 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2160 for line in fp:
2160 for line in fp:
2161 m = re.match('(.*?) ([0-9. ]+)', line)
2161 m = re.match('(.*?) ([0-9. ]+)', line)
2162 times.append((m.group(1),
2162 times.append((m.group(1),
2163 [float(t) for t in m.group(2).split()]))
2163 [float(t) for t in m.group(2).split()]))
2164 except IOError as err:
2164 except IOError as err:
2165 if err.errno != errno.ENOENT:
2165 if err.errno != errno.ENOENT:
2166 raise
2166 raise
2167 return times
2167 return times
2168
2168
2169 def savetimes(outputdir, result):
2169 def savetimes(outputdir, result):
2170 saved = dict(loadtimes(outputdir))
2170 saved = dict(loadtimes(outputdir))
2171 maxruns = 5
2171 maxruns = 5
2172 skipped = set([str(t[0]) for t in result.skipped])
2172 skipped = set([str(t[0]) for t in result.skipped])
2173 for tdata in result.times:
2173 for tdata in result.times:
2174 test, real = tdata[0], tdata[3]
2174 test, real = tdata[0], tdata[3]
2175 if test not in skipped:
2175 if test not in skipped:
2176 ts = saved.setdefault(test, [])
2176 ts = saved.setdefault(test, [])
2177 ts.append(real)
2177 ts.append(real)
2178 ts[:] = ts[-maxruns:]
2178 ts[:] = ts[-maxruns:]
2179
2179
2180 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2180 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2181 dir=outputdir, text=True)
2181 dir=outputdir, text=True)
2182 with os.fdopen(fd, 'w') as fp:
2182 with os.fdopen(fd, 'w') as fp:
2183 for name, ts in sorted(saved.items()):
2183 for name, ts in sorted(saved.items()):
2184 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2184 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2185 timepath = os.path.join(outputdir, b'.testtimes')
2185 timepath = os.path.join(outputdir, b'.testtimes')
2186 try:
2186 try:
2187 os.unlink(timepath)
2187 os.unlink(timepath)
2188 except OSError:
2188 except OSError:
2189 pass
2189 pass
2190 try:
2190 try:
2191 os.rename(tmpname, timepath)
2191 os.rename(tmpname, timepath)
2192 except OSError:
2192 except OSError:
2193 pass
2193 pass
2194
2194
2195 class TextTestRunner(unittest.TextTestRunner):
2195 class TextTestRunner(unittest.TextTestRunner):
2196 """Custom unittest test runner that uses appropriate settings."""
2196 """Custom unittest test runner that uses appropriate settings."""
2197
2197
2198 def __init__(self, runner, *args, **kwargs):
2198 def __init__(self, runner, *args, **kwargs):
2199 super(TextTestRunner, self).__init__(*args, **kwargs)
2199 super(TextTestRunner, self).__init__(*args, **kwargs)
2200
2200
2201 self._runner = runner
2201 self._runner = runner
2202
2202
2203 self._result = getTestResult()(self._runner.options, self.stream,
2203 self._result = getTestResult()(self._runner.options, self.stream,
2204 self.descriptions, self.verbosity)
2204 self.descriptions, self.verbosity)
2205
2205
2206 def listtests(self, test):
2206 def listtests(self, test):
2207 test = sorted(test, key=lambda t: t.name)
2207 test = sorted(test, key=lambda t: t.name)
2208
2208
2209 self._result.onStart(test)
2209 self._result.onStart(test)
2210
2210
2211 for t in test:
2211 for t in test:
2212 print(t.name)
2212 print(t.name)
2213 self._result.addSuccess(t)
2213 self._result.addSuccess(t)
2214
2214
2215 if self._runner.options.xunit:
2215 if self._runner.options.xunit:
2216 with open(self._runner.options.xunit, "wb") as xuf:
2216 with open(self._runner.options.xunit, "wb") as xuf:
2217 self._writexunit(self._result, xuf)
2217 self._writexunit(self._result, xuf)
2218
2218
2219 if self._runner.options.json:
2219 if self._runner.options.json:
2220 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2220 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2221 with open(jsonpath, 'w') as fp:
2221 with open(jsonpath, 'w') as fp:
2222 self._writejson(self._result, fp)
2222 self._writejson(self._result, fp)
2223
2223
2224 return self._result
2224 return self._result
2225
2225
2226 def run(self, test):
2226 def run(self, test):
2227 self._result.onStart(test)
2227 self._result.onStart(test)
2228 test(self._result)
2228 test(self._result)
2229
2229
2230 failed = len(self._result.failures)
2230 failed = len(self._result.failures)
2231 skipped = len(self._result.skipped)
2231 skipped = len(self._result.skipped)
2232 ignored = len(self._result.ignored)
2232 ignored = len(self._result.ignored)
2233
2233
2234 with iolock:
2234 with iolock:
2235 self.stream.writeln('')
2235 self.stream.writeln('')
2236
2236
2237 if not self._runner.options.noskips:
2237 if not self._runner.options.noskips:
2238 for test, msg in self._result.skipped:
2238 for test, msg in self._result.skipped:
2239 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2239 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2240 msg = highlightmsg(formatted, self._result.color)
2240 msg = highlightmsg(formatted, self._result.color)
2241 self.stream.write(msg)
2241 self.stream.write(msg)
2242 for test, msg in self._result.failures:
2242 for test, msg in self._result.failures:
2243 formatted = 'Failed %s: %s\n' % (test.name, msg)
2243 formatted = 'Failed %s: %s\n' % (test.name, msg)
2244 self.stream.write(highlightmsg(formatted, self._result.color))
2244 self.stream.write(highlightmsg(formatted, self._result.color))
2245 for test, msg in self._result.errors:
2245 for test, msg in self._result.errors:
2246 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2246 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2247
2247
2248 if self._runner.options.xunit:
2248 if self._runner.options.xunit:
2249 with open(self._runner.options.xunit, "wb") as xuf:
2249 with open(self._runner.options.xunit, "wb") as xuf:
2250 self._writexunit(self._result, xuf)
2250 self._writexunit(self._result, xuf)
2251
2251
2252 if self._runner.options.json:
2252 if self._runner.options.json:
2253 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2253 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2254 with open(jsonpath, 'w') as fp:
2254 with open(jsonpath, 'w') as fp:
2255 self._writejson(self._result, fp)
2255 self._writejson(self._result, fp)
2256
2256
2257 self._runner._checkhglib('Tested')
2257 self._runner._checkhglib('Tested')
2258
2258
2259 savetimes(self._runner._outputdir, self._result)
2259 savetimes(self._runner._outputdir, self._result)
2260
2260
2261 if failed and self._runner.options.known_good_rev:
2261 if failed and self._runner.options.known_good_rev:
2262 self._bisecttests(t for t, m in self._result.failures)
2262 self._bisecttests(t for t, m in self._result.failures)
2263 self.stream.writeln(
2263 self.stream.writeln(
2264 '# Ran %d tests, %d skipped, %d failed.'
2264 '# Ran %d tests, %d skipped, %d failed.'
2265 % (self._result.testsRun, skipped + ignored, failed))
2265 % (self._result.testsRun, skipped + ignored, failed))
2266 if failed:
2266 if failed:
2267 self.stream.writeln('python hash seed: %s' %
2267 self.stream.writeln('python hash seed: %s' %
2268 os.environ['PYTHONHASHSEED'])
2268 os.environ['PYTHONHASHSEED'])
2269 if self._runner.options.time:
2269 if self._runner.options.time:
2270 self.printtimes(self._result.times)
2270 self.printtimes(self._result.times)
2271
2271
2272 if self._runner.options.exceptions:
2272 if self._runner.options.exceptions:
2273 exceptions = aggregateexceptions(
2273 exceptions = aggregateexceptions(
2274 os.path.join(self._runner._outputdir, b'exceptions'))
2274 os.path.join(self._runner._outputdir, b'exceptions'))
2275
2275
2276 self.stream.writeln('Exceptions Report:')
2276 self.stream.writeln('Exceptions Report:')
2277 self.stream.writeln('%d total from %d frames' %
2277 self.stream.writeln('%d total from %d frames' %
2278 (exceptions['total'],
2278 (exceptions['total'],
2279 len(exceptions['exceptioncounts'])))
2279 len(exceptions['exceptioncounts'])))
2280 combined = exceptions['combined']
2280 combined = exceptions['combined']
2281 for key in sorted(combined, key=combined.get, reverse=True):
2281 for key in sorted(combined, key=combined.get, reverse=True):
2282 frame, line, exc = key
2282 frame, line, exc = key
2283 totalcount, testcount, leastcount, leasttest = combined[key]
2283 totalcount, testcount, leastcount, leasttest = combined[key]
2284
2284
2285 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2285 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2286 % (totalcount,
2286 % (totalcount,
2287 testcount,
2287 testcount,
2288 frame, exc,
2288 frame, exc,
2289 leasttest, leastcount))
2289 leasttest, leastcount))
2290
2290
2291 self.stream.flush()
2291 self.stream.flush()
2292
2292
2293 return self._result
2293 return self._result
2294
2294
2295 def _bisecttests(self, tests):
2295 def _bisecttests(self, tests):
2296 bisectcmd = ['hg', 'bisect']
2296 bisectcmd = ['hg', 'bisect']
2297 bisectrepo = self._runner.options.bisect_repo
2297 bisectrepo = self._runner.options.bisect_repo
2298 if bisectrepo:
2298 if bisectrepo:
2299 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2299 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2300 def pread(args):
2300 def pread(args):
2301 env = os.environ.copy()
2301 env = os.environ.copy()
2302 env['HGPLAIN'] = '1'
2302 env['HGPLAIN'] = '1'
2303 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2303 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2304 stdout=subprocess.PIPE, env=env)
2304 stdout=subprocess.PIPE, env=env)
2305 data = p.stdout.read()
2305 data = p.stdout.read()
2306 p.wait()
2306 p.wait()
2307 return data
2307 return data
2308 for test in tests:
2308 for test in tests:
2309 pread(bisectcmd + ['--reset']),
2309 pread(bisectcmd + ['--reset']),
2310 pread(bisectcmd + ['--bad', '.'])
2310 pread(bisectcmd + ['--bad', '.'])
2311 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2311 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2312 # TODO: we probably need to forward more options
2312 # TODO: we probably need to forward more options
2313 # that alter hg's behavior inside the tests.
2313 # that alter hg's behavior inside the tests.
2314 opts = ''
2314 opts = ''
2315 withhg = self._runner.options.with_hg
2315 withhg = self._runner.options.with_hg
2316 if withhg:
2316 if withhg:
2317 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2317 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2318 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2318 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2319 test)
2319 test)
2320 data = pread(bisectcmd + ['--command', rtc])
2320 data = pread(bisectcmd + ['--command', rtc])
2321 m = re.search(
2321 m = re.search(
2322 (br'\nThe first (?P<goodbad>bad|good) revision '
2322 (br'\nThe first (?P<goodbad>bad|good) revision '
2323 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2323 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2324 br'summary: +(?P<summary>[^\n]+)\n'),
2324 br'summary: +(?P<summary>[^\n]+)\n'),
2325 data, (re.MULTILINE | re.DOTALL))
2325 data, (re.MULTILINE | re.DOTALL))
2326 if m is None:
2326 if m is None:
2327 self.stream.writeln(
2327 self.stream.writeln(
2328 'Failed to identify failure point for %s' % test)
2328 'Failed to identify failure point for %s' % test)
2329 continue
2329 continue
2330 dat = m.groupdict()
2330 dat = m.groupdict()
2331 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2331 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2332 self.stream.writeln(
2332 self.stream.writeln(
2333 '%s %s by %s (%s)' % (
2333 '%s %s by %s (%s)' % (
2334 test, verb, dat['node'].decode('ascii'),
2334 test, verb, dat['node'].decode('ascii'),
2335 dat['summary'].decode('utf8', 'ignore')))
2335 dat['summary'].decode('utf8', 'ignore')))
2336
2336
2337 def printtimes(self, times):
2337 def printtimes(self, times):
2338 # iolock held by run
2338 # iolock held by run
2339 self.stream.writeln('# Producing time report')
2339 self.stream.writeln('# Producing time report')
2340 times.sort(key=lambda t: (t[3]))
2340 times.sort(key=lambda t: (t[3]))
2341 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2341 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2342 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2342 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2343 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2343 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2344 for tdata in times:
2344 for tdata in times:
2345 test = tdata[0]
2345 test = tdata[0]
2346 cuser, csys, real, start, end = tdata[1:6]
2346 cuser, csys, real, start, end = tdata[1:6]
2347 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2347 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2348
2348
2349 @staticmethod
2349 @staticmethod
2350 def _writexunit(result, outf):
2350 def _writexunit(result, outf):
2351 # See http://llg.cubic.org/docs/junit/ for a reference.
2351 # See http://llg.cubic.org/docs/junit/ for a reference.
2352 timesd = dict((t[0], t[3]) for t in result.times)
2352 timesd = dict((t[0], t[3]) for t in result.times)
2353 doc = minidom.Document()
2353 doc = minidom.Document()
2354 s = doc.createElement('testsuite')
2354 s = doc.createElement('testsuite')
2355 s.setAttribute('name', 'run-tests')
2355 s.setAttribute('name', 'run-tests')
2356 s.setAttribute('tests', str(result.testsRun))
2356 s.setAttribute('tests', str(result.testsRun))
2357 s.setAttribute('errors', "0") # TODO
2357 s.setAttribute('errors', "0") # TODO
2358 s.setAttribute('failures', str(len(result.failures)))
2358 s.setAttribute('failures', str(len(result.failures)))
2359 s.setAttribute('skipped', str(len(result.skipped) +
2359 s.setAttribute('skipped', str(len(result.skipped) +
2360 len(result.ignored)))
2360 len(result.ignored)))
2361 doc.appendChild(s)
2361 doc.appendChild(s)
2362 for tc in result.successes:
2362 for tc in result.successes:
2363 t = doc.createElement('testcase')
2363 t = doc.createElement('testcase')
2364 t.setAttribute('name', tc.name)
2364 t.setAttribute('name', tc.name)
2365 tctime = timesd.get(tc.name)
2365 tctime = timesd.get(tc.name)
2366 if tctime is not None:
2366 if tctime is not None:
2367 t.setAttribute('time', '%.3f' % tctime)
2367 t.setAttribute('time', '%.3f' % tctime)
2368 s.appendChild(t)
2368 s.appendChild(t)
2369 for tc, err in sorted(result.faildata.items()):
2369 for tc, err in sorted(result.faildata.items()):
2370 t = doc.createElement('testcase')
2370 t = doc.createElement('testcase')
2371 t.setAttribute('name', tc)
2371 t.setAttribute('name', tc)
2372 tctime = timesd.get(tc)
2372 tctime = timesd.get(tc)
2373 if tctime is not None:
2373 if tctime is not None:
2374 t.setAttribute('time', '%.3f' % tctime)
2374 t.setAttribute('time', '%.3f' % tctime)
2375 # createCDATASection expects a unicode or it will
2375 # createCDATASection expects a unicode or it will
2376 # convert using default conversion rules, which will
2376 # convert using default conversion rules, which will
2377 # fail if string isn't ASCII.
2377 # fail if string isn't ASCII.
2378 err = cdatasafe(err).decode('utf-8', 'replace')
2378 err = cdatasafe(err).decode('utf-8', 'replace')
2379 cd = doc.createCDATASection(err)
2379 cd = doc.createCDATASection(err)
2380 # Use 'failure' here instead of 'error' to match errors = 0,
2380 # Use 'failure' here instead of 'error' to match errors = 0,
2381 # failures = len(result.failures) in the testsuite element.
2381 # failures = len(result.failures) in the testsuite element.
2382 failelem = doc.createElement('failure')
2382 failelem = doc.createElement('failure')
2383 failelem.setAttribute('message', 'output changed')
2383 failelem.setAttribute('message', 'output changed')
2384 failelem.setAttribute('type', 'output-mismatch')
2384 failelem.setAttribute('type', 'output-mismatch')
2385 failelem.appendChild(cd)
2385 failelem.appendChild(cd)
2386 t.appendChild(failelem)
2386 t.appendChild(failelem)
2387 s.appendChild(t)
2387 s.appendChild(t)
2388 for tc, message in result.skipped:
2388 for tc, message in result.skipped:
2389 # According to the schema, 'skipped' has no attributes. So store
2389 # According to the schema, 'skipped' has no attributes. So store
2390 # the skip message as a text node instead.
2390 # the skip message as a text node instead.
2391 t = doc.createElement('testcase')
2391 t = doc.createElement('testcase')
2392 t.setAttribute('name', tc.name)
2392 t.setAttribute('name', tc.name)
2393 binmessage = message.encode('utf-8')
2393 binmessage = message.encode('utf-8')
2394 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2394 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2395 cd = doc.createCDATASection(message)
2395 cd = doc.createCDATASection(message)
2396 skipelem = doc.createElement('skipped')
2396 skipelem = doc.createElement('skipped')
2397 skipelem.appendChild(cd)
2397 skipelem.appendChild(cd)
2398 t.appendChild(skipelem)
2398 t.appendChild(skipelem)
2399 s.appendChild(t)
2399 s.appendChild(t)
2400 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2400 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2401
2401
2402 @staticmethod
2402 @staticmethod
2403 def _writejson(result, outf):
2403 def _writejson(result, outf):
2404 timesd = {}
2404 timesd = {}
2405 for tdata in result.times:
2405 for tdata in result.times:
2406 test = tdata[0]
2406 test = tdata[0]
2407 timesd[test] = tdata[1:]
2407 timesd[test] = tdata[1:]
2408
2408
2409 outcome = {}
2409 outcome = {}
2410 groups = [('success', ((tc, None)
2410 groups = [('success', ((tc, None)
2411 for tc in result.successes)),
2411 for tc in result.successes)),
2412 ('failure', result.failures),
2412 ('failure', result.failures),
2413 ('skip', result.skipped)]
2413 ('skip', result.skipped)]
2414 for res, testcases in groups:
2414 for res, testcases in groups:
2415 for tc, __ in testcases:
2415 for tc, __ in testcases:
2416 if tc.name in timesd:
2416 if tc.name in timesd:
2417 diff = result.faildata.get(tc.name, b'')
2417 diff = result.faildata.get(tc.name, b'')
2418 try:
2418 try:
2419 diff = diff.decode('unicode_escape')
2419 diff = diff.decode('unicode_escape')
2420 except UnicodeDecodeError as e:
2420 except UnicodeDecodeError as e:
2421 diff = '%r decoding diff, sorry' % e
2421 diff = '%r decoding diff, sorry' % e
2422 tres = {'result': res,
2422 tres = {'result': res,
2423 'time': ('%0.3f' % timesd[tc.name][2]),
2423 'time': ('%0.3f' % timesd[tc.name][2]),
2424 'cuser': ('%0.3f' % timesd[tc.name][0]),
2424 'cuser': ('%0.3f' % timesd[tc.name][0]),
2425 'csys': ('%0.3f' % timesd[tc.name][1]),
2425 'csys': ('%0.3f' % timesd[tc.name][1]),
2426 'start': ('%0.3f' % timesd[tc.name][3]),
2426 'start': ('%0.3f' % timesd[tc.name][3]),
2427 'end': ('%0.3f' % timesd[tc.name][4]),
2427 'end': ('%0.3f' % timesd[tc.name][4]),
2428 'diff': diff,
2428 'diff': diff,
2429 }
2429 }
2430 else:
2430 else:
2431 # blacklisted test
2431 # blacklisted test
2432 tres = {'result': res}
2432 tres = {'result': res}
2433
2433
2434 outcome[tc.name] = tres
2434 outcome[tc.name] = tres
2435 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2435 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2436 separators=(',', ': '))
2436 separators=(',', ': '))
2437 outf.writelines(("testreport =", jsonout))
2437 outf.writelines(("testreport =", jsonout))
2438
2438
2439 def sorttests(testdescs, previoustimes, shuffle=False):
2439 def sorttests(testdescs, previoustimes, shuffle=False):
2440 """Do an in-place sort of tests."""
2440 """Do an in-place sort of tests."""
2441 if shuffle:
2441 if shuffle:
2442 random.shuffle(testdescs)
2442 random.shuffle(testdescs)
2443 return
2443 return
2444
2444
2445 if previoustimes:
2445 if previoustimes:
2446 def sortkey(f):
2446 def sortkey(f):
2447 f = f['path']
2447 f = f['path']
2448 if f in previoustimes:
2448 if f in previoustimes:
2449 # Use most recent time as estimate
2449 # Use most recent time as estimate
2450 return -previoustimes[f][-1]
2450 return -previoustimes[f][-1]
2451 else:
2451 else:
2452 # Default to a rather arbitrary value of 1 second for new tests
2452 # Default to a rather arbitrary value of 1 second for new tests
2453 return -1.0
2453 return -1.0
2454 else:
2454 else:
2455 # keywords for slow tests
2455 # keywords for slow tests
2456 slow = {b'svn': 10,
2456 slow = {b'svn': 10,
2457 b'cvs': 10,
2457 b'cvs': 10,
2458 b'hghave': 10,
2458 b'hghave': 10,
2459 b'largefiles-update': 10,
2459 b'largefiles-update': 10,
2460 b'run-tests': 10,
2460 b'run-tests': 10,
2461 b'corruption': 10,
2461 b'corruption': 10,
2462 b'race': 10,
2462 b'race': 10,
2463 b'i18n': 10,
2463 b'i18n': 10,
2464 b'check': 100,
2464 b'check': 100,
2465 b'gendoc': 100,
2465 b'gendoc': 100,
2466 b'contrib-perf': 200,
2466 b'contrib-perf': 200,
2467 }
2467 }
2468 perf = {}
2468 perf = {}
2469
2469
2470 def sortkey(f):
2470 def sortkey(f):
2471 # run largest tests first, as they tend to take the longest
2471 # run largest tests first, as they tend to take the longest
2472 f = f['path']
2472 f = f['path']
2473 try:
2473 try:
2474 return perf[f]
2474 return perf[f]
2475 except KeyError:
2475 except KeyError:
2476 try:
2476 try:
2477 val = -os.stat(f).st_size
2477 val = -os.stat(f).st_size
2478 except OSError as e:
2478 except OSError as e:
2479 if e.errno != errno.ENOENT:
2479 if e.errno != errno.ENOENT:
2480 raise
2480 raise
2481 perf[f] = -1e9 # file does not exist, tell early
2481 perf[f] = -1e9 # file does not exist, tell early
2482 return -1e9
2482 return -1e9
2483 for kw, mul in slow.items():
2483 for kw, mul in slow.items():
2484 if kw in f:
2484 if kw in f:
2485 val *= mul
2485 val *= mul
2486 if f.endswith(b'.py'):
2486 if f.endswith(b'.py'):
2487 val /= 10.0
2487 val /= 10.0
2488 perf[f] = val / 1000.0
2488 perf[f] = val / 1000.0
2489 return perf[f]
2489 return perf[f]
2490
2490
2491 testdescs.sort(key=sortkey)
2491 testdescs.sort(key=sortkey)
2492
2492
2493 class TestRunner(object):
2493 class TestRunner(object):
2494 """Holds context for executing tests.
2494 """Holds context for executing tests.
2495
2495
2496 Tests rely on a lot of state. This object holds it for them.
2496 Tests rely on a lot of state. This object holds it for them.
2497 """
2497 """
2498
2498
2499 # Programs required to run tests.
2499 # Programs required to run tests.
2500 REQUIREDTOOLS = [
2500 REQUIREDTOOLS = [
2501 b'diff',
2501 b'diff',
2502 b'grep',
2502 b'grep',
2503 b'unzip',
2503 b'unzip',
2504 b'gunzip',
2504 b'gunzip',
2505 b'bunzip2',
2505 b'bunzip2',
2506 b'sed',
2506 b'sed',
2507 ]
2507 ]
2508
2508
2509 # Maps file extensions to test class.
2509 # Maps file extensions to test class.
2510 TESTTYPES = [
2510 TESTTYPES = [
2511 (b'.py', PythonTest),
2511 (b'.py', PythonTest),
2512 (b'.t', TTest),
2512 (b'.t', TTest),
2513 ]
2513 ]
2514
2514
2515 def __init__(self):
2515 def __init__(self):
2516 self.options = None
2516 self.options = None
2517 self._hgroot = None
2517 self._hgroot = None
2518 self._testdir = None
2518 self._testdir = None
2519 self._outputdir = None
2519 self._outputdir = None
2520 self._hgtmp = None
2520 self._hgtmp = None
2521 self._installdir = None
2521 self._installdir = None
2522 self._bindir = None
2522 self._bindir = None
2523 self._tmpbinddir = None
2523 self._tmpbinddir = None
2524 self._pythondir = None
2524 self._pythondir = None
2525 self._coveragefile = None
2525 self._coveragefile = None
2526 self._createdfiles = []
2526 self._createdfiles = []
2527 self._hgcommand = None
2527 self._hgcommand = None
2528 self._hgpath = None
2528 self._hgpath = None
2529 self._portoffset = 0
2529 self._portoffset = 0
2530 self._ports = {}
2530 self._ports = {}
2531
2531
2532 def run(self, args, parser=None):
2532 def run(self, args, parser=None):
2533 """Run the test suite."""
2533 """Run the test suite."""
2534 oldmask = os.umask(0o22)
2534 oldmask = os.umask(0o22)
2535 try:
2535 try:
2536 parser = parser or getparser()
2536 parser = parser or getparser()
2537 options = parseargs(args, parser)
2537 options = parseargs(args, parser)
2538 tests = [_bytespath(a) for a in options.tests]
2538 tests = [_bytespath(a) for a in options.tests]
2539 if options.test_list is not None:
2539 if options.test_list is not None:
2540 for listfile in options.test_list:
2540 for listfile in options.test_list:
2541 with open(listfile, 'rb') as f:
2541 with open(listfile, 'rb') as f:
2542 tests.extend(t for t in f.read().splitlines() if t)
2542 tests.extend(t for t in f.read().splitlines() if t)
2543 self.options = options
2543 self.options = options
2544
2544
2545 self._checktools()
2545 self._checktools()
2546 testdescs = self.findtests(tests)
2546 testdescs = self.findtests(tests)
2547 if options.profile_runner:
2547 if options.profile_runner:
2548 import statprof
2548 import statprof
2549 statprof.start()
2549 statprof.start()
2550 result = self._run(testdescs)
2550 result = self._run(testdescs)
2551 if options.profile_runner:
2551 if options.profile_runner:
2552 statprof.stop()
2552 statprof.stop()
2553 statprof.display()
2553 statprof.display()
2554 return result
2554 return result
2555
2555
2556 finally:
2556 finally:
2557 os.umask(oldmask)
2557 os.umask(oldmask)
2558
2558
2559 def _run(self, testdescs):
2559 def _run(self, testdescs):
2560 testdir = getcwdb()
2560 testdir = getcwdb()
2561 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2561 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2562 # assume all tests in same folder for now
2562 # assume all tests in same folder for now
2563 if testdescs:
2563 if testdescs:
2564 pathname = os.path.dirname(testdescs[0]['path'])
2564 pathname = os.path.dirname(testdescs[0]['path'])
2565 if pathname:
2565 if pathname:
2566 testdir = os.path.join(testdir, pathname)
2566 testdir = os.path.join(testdir, pathname)
2567 self._testdir = osenvironb[b'TESTDIR'] = testdir
2567 self._testdir = osenvironb[b'TESTDIR'] = testdir
2568 if self.options.outputdir:
2568 if self.options.outputdir:
2569 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2569 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2570 else:
2570 else:
2571 self._outputdir = getcwdb()
2571 self._outputdir = getcwdb()
2572 if testdescs and pathname:
2572 if testdescs and pathname:
2573 self._outputdir = os.path.join(self._outputdir, pathname)
2573 self._outputdir = os.path.join(self._outputdir, pathname)
2574 previoustimes = {}
2574 previoustimes = {}
2575 if self.options.order_by_runtime:
2575 if self.options.order_by_runtime:
2576 previoustimes = dict(loadtimes(self._outputdir))
2576 previoustimes = dict(loadtimes(self._outputdir))
2577 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2577 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2578
2578
2579 if 'PYTHONHASHSEED' not in os.environ:
2579 if 'PYTHONHASHSEED' not in os.environ:
2580 # use a random python hash seed all the time
2580 # use a random python hash seed all the time
2581 # we do the randomness ourself to know what seed is used
2581 # we do the randomness ourself to know what seed is used
2582 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2582 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2583
2583
2584 if self.options.tmpdir:
2584 if self.options.tmpdir:
2585 self.options.keep_tmpdir = True
2585 self.options.keep_tmpdir = True
2586 tmpdir = _bytespath(self.options.tmpdir)
2586 tmpdir = _bytespath(self.options.tmpdir)
2587 if os.path.exists(tmpdir):
2587 if os.path.exists(tmpdir):
2588 # Meaning of tmpdir has changed since 1.3: we used to create
2588 # Meaning of tmpdir has changed since 1.3: we used to create
2589 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2589 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2590 # tmpdir already exists.
2590 # tmpdir already exists.
2591 print("error: temp dir %r already exists" % tmpdir)
2591 print("error: temp dir %r already exists" % tmpdir)
2592 return 1
2592 return 1
2593
2593
2594 os.makedirs(tmpdir)
2594 os.makedirs(tmpdir)
2595 else:
2595 else:
2596 d = None
2596 d = None
2597 if os.name == 'nt':
2597 if os.name == 'nt':
2598 # without this, we get the default temp dir location, but
2598 # without this, we get the default temp dir location, but
2599 # in all lowercase, which causes troubles with paths (issue3490)
2599 # in all lowercase, which causes troubles with paths (issue3490)
2600 d = osenvironb.get(b'TMP', None)
2600 d = osenvironb.get(b'TMP', None)
2601 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2601 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2602
2602
2603 self._hgtmp = osenvironb[b'HGTMP'] = (
2603 self._hgtmp = osenvironb[b'HGTMP'] = (
2604 os.path.realpath(tmpdir))
2604 os.path.realpath(tmpdir))
2605
2605
2606 if self.options.with_hg:
2606 if self.options.with_hg:
2607 self._installdir = None
2607 self._installdir = None
2608 whg = self.options.with_hg
2608 whg = self.options.with_hg
2609 self._bindir = os.path.dirname(os.path.realpath(whg))
2609 self._bindir = os.path.dirname(os.path.realpath(whg))
2610 assert isinstance(self._bindir, bytes)
2610 assert isinstance(self._bindir, bytes)
2611 self._hgcommand = os.path.basename(whg)
2611 self._hgcommand = os.path.basename(whg)
2612 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2612 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2613 os.makedirs(self._tmpbindir)
2613 os.makedirs(self._tmpbindir)
2614
2614
2615 normbin = os.path.normpath(os.path.abspath(whg))
2615 normbin = os.path.normpath(os.path.abspath(whg))
2616 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2616 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2617
2617
2618 # Other Python scripts in the test harness need to
2618 # Other Python scripts in the test harness need to
2619 # `import mercurial`. If `hg` is a Python script, we assume
2619 # `import mercurial`. If `hg` is a Python script, we assume
2620 # the Mercurial modules are relative to its path and tell the tests
2620 # the Mercurial modules are relative to its path and tell the tests
2621 # to load Python modules from its directory.
2621 # to load Python modules from its directory.
2622 with open(whg, 'rb') as fh:
2622 with open(whg, 'rb') as fh:
2623 initial = fh.read(1024)
2623 initial = fh.read(1024)
2624
2624
2625 if re.match(b'#!.*python', initial):
2625 if re.match(b'#!.*python', initial):
2626 self._pythondir = self._bindir
2626 self._pythondir = self._bindir
2627 # If it looks like our in-repo Rust binary, use the source root.
2627 # If it looks like our in-repo Rust binary, use the source root.
2628 # This is a bit hacky. But rhg is still not supported outside the
2628 # This is a bit hacky. But rhg is still not supported outside the
2629 # source directory. So until it is, do the simple thing.
2629 # source directory. So until it is, do the simple thing.
2630 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2630 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2631 self._pythondir = os.path.dirname(self._testdir)
2631 self._pythondir = os.path.dirname(self._testdir)
2632 # Fall back to the legacy behavior.
2632 # Fall back to the legacy behavior.
2633 else:
2633 else:
2634 self._pythondir = self._bindir
2634 self._pythondir = self._bindir
2635
2635
2636 else:
2636 else:
2637 self._installdir = os.path.join(self._hgtmp, b"install")
2637 self._installdir = os.path.join(self._hgtmp, b"install")
2638 self._bindir = os.path.join(self._installdir, b"bin")
2638 self._bindir = os.path.join(self._installdir, b"bin")
2639 self._hgcommand = b'hg'
2639 self._hgcommand = b'hg'
2640 self._tmpbindir = self._bindir
2640 self._tmpbindir = self._bindir
2641 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2641 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2642
2642
2643 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
2644 self._hgcommand += b'.exe'
2645
2643 # set CHGHG, then replace "hg" command by "chg"
2646 # set CHGHG, then replace "hg" command by "chg"
2644 chgbindir = self._bindir
2647 chgbindir = self._bindir
2645 if self.options.chg or self.options.with_chg:
2648 if self.options.chg or self.options.with_chg:
2646 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2649 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2647 else:
2650 else:
2648 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2651 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2649 if self.options.chg:
2652 if self.options.chg:
2650 self._hgcommand = b'chg'
2653 self._hgcommand = b'chg'
2651 elif self.options.with_chg:
2654 elif self.options.with_chg:
2652 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2655 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2653 self._hgcommand = os.path.basename(self.options.with_chg)
2656 self._hgcommand = os.path.basename(self.options.with_chg)
2654
2657
2655 osenvironb[b"BINDIR"] = self._bindir
2658 osenvironb[b"BINDIR"] = self._bindir
2656 osenvironb[b"PYTHON"] = PYTHON
2659 osenvironb[b"PYTHON"] = PYTHON
2657
2660
2658 fileb = _bytespath(__file__)
2661 fileb = _bytespath(__file__)
2659 runtestdir = os.path.abspath(os.path.dirname(fileb))
2662 runtestdir = os.path.abspath(os.path.dirname(fileb))
2660 osenvironb[b'RUNTESTDIR'] = runtestdir
2663 osenvironb[b'RUNTESTDIR'] = runtestdir
2661 if PYTHON3:
2664 if PYTHON3:
2662 sepb = _bytespath(os.pathsep)
2665 sepb = _bytespath(os.pathsep)
2663 else:
2666 else:
2664 sepb = os.pathsep
2667 sepb = os.pathsep
2665 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2668 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2666 if os.path.islink(__file__):
2669 if os.path.islink(__file__):
2667 # test helper will likely be at the end of the symlink
2670 # test helper will likely be at the end of the symlink
2668 realfile = os.path.realpath(fileb)
2671 realfile = os.path.realpath(fileb)
2669 realdir = os.path.abspath(os.path.dirname(realfile))
2672 realdir = os.path.abspath(os.path.dirname(realfile))
2670 path.insert(2, realdir)
2673 path.insert(2, realdir)
2671 if chgbindir != self._bindir:
2674 if chgbindir != self._bindir:
2672 path.insert(1, chgbindir)
2675 path.insert(1, chgbindir)
2673 if self._testdir != runtestdir:
2676 if self._testdir != runtestdir:
2674 path = [self._testdir] + path
2677 path = [self._testdir] + path
2675 if self._tmpbindir != self._bindir:
2678 if self._tmpbindir != self._bindir:
2676 path = [self._tmpbindir] + path
2679 path = [self._tmpbindir] + path
2677 osenvironb[b"PATH"] = sepb.join(path)
2680 osenvironb[b"PATH"] = sepb.join(path)
2678
2681
2679 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2682 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2680 # can run .../tests/run-tests.py test-foo where test-foo
2683 # can run .../tests/run-tests.py test-foo where test-foo
2681 # adds an extension to HGRC. Also include run-test.py directory to
2684 # adds an extension to HGRC. Also include run-test.py directory to
2682 # import modules like heredoctest.
2685 # import modules like heredoctest.
2683 pypath = [self._pythondir, self._testdir, runtestdir]
2686 pypath = [self._pythondir, self._testdir, runtestdir]
2684 # We have to augment PYTHONPATH, rather than simply replacing
2687 # We have to augment PYTHONPATH, rather than simply replacing
2685 # it, in case external libraries are only available via current
2688 # it, in case external libraries are only available via current
2686 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2689 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2687 # are in /opt/subversion.)
2690 # are in /opt/subversion.)
2688 oldpypath = osenvironb.get(IMPL_PATH)
2691 oldpypath = osenvironb.get(IMPL_PATH)
2689 if oldpypath:
2692 if oldpypath:
2690 pypath.append(oldpypath)
2693 pypath.append(oldpypath)
2691 osenvironb[IMPL_PATH] = sepb.join(pypath)
2694 osenvironb[IMPL_PATH] = sepb.join(pypath)
2692
2695
2693 if self.options.pure:
2696 if self.options.pure:
2694 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2697 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2695 os.environ["HGMODULEPOLICY"] = "py"
2698 os.environ["HGMODULEPOLICY"] = "py"
2696
2699
2697 if self.options.allow_slow_tests:
2700 if self.options.allow_slow_tests:
2698 os.environ["HGTEST_SLOW"] = "slow"
2701 os.environ["HGTEST_SLOW"] = "slow"
2699 elif 'HGTEST_SLOW' in os.environ:
2702 elif 'HGTEST_SLOW' in os.environ:
2700 del os.environ['HGTEST_SLOW']
2703 del os.environ['HGTEST_SLOW']
2701
2704
2702 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2705 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2703
2706
2704 if self.options.exceptions:
2707 if self.options.exceptions:
2705 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2708 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2706 try:
2709 try:
2707 os.makedirs(exceptionsdir)
2710 os.makedirs(exceptionsdir)
2708 except OSError as e:
2711 except OSError as e:
2709 if e.errno != errno.EEXIST:
2712 if e.errno != errno.EEXIST:
2710 raise
2713 raise
2711
2714
2712 # Remove all existing exception reports.
2715 # Remove all existing exception reports.
2713 for f in os.listdir(exceptionsdir):
2716 for f in os.listdir(exceptionsdir):
2714 os.unlink(os.path.join(exceptionsdir, f))
2717 os.unlink(os.path.join(exceptionsdir, f))
2715
2718
2716 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2719 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2717 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2720 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2718 self.options.extra_config_opt.append(
2721 self.options.extra_config_opt.append(
2719 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2722 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2720
2723
2721 vlog("# Using TESTDIR", self._testdir)
2724 vlog("# Using TESTDIR", self._testdir)
2722 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2725 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2723 vlog("# Using HGTMP", self._hgtmp)
2726 vlog("# Using HGTMP", self._hgtmp)
2724 vlog("# Using PATH", os.environ["PATH"])
2727 vlog("# Using PATH", os.environ["PATH"])
2725 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2728 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2726 vlog("# Writing to directory", self._outputdir)
2729 vlog("# Writing to directory", self._outputdir)
2727
2730
2728 try:
2731 try:
2729 return self._runtests(testdescs) or 0
2732 return self._runtests(testdescs) or 0
2730 finally:
2733 finally:
2731 time.sleep(.1)
2734 time.sleep(.1)
2732 self._cleanup()
2735 self._cleanup()
2733
2736
2734 def findtests(self, args):
2737 def findtests(self, args):
2735 """Finds possible test files from arguments.
2738 """Finds possible test files from arguments.
2736
2739
2737 If you wish to inject custom tests into the test harness, this would
2740 If you wish to inject custom tests into the test harness, this would
2738 be a good function to monkeypatch or override in a derived class.
2741 be a good function to monkeypatch or override in a derived class.
2739 """
2742 """
2740 if not args:
2743 if not args:
2741 if self.options.changed:
2744 if self.options.changed:
2742 proc = Popen4('hg st --rev "%s" -man0 .' %
2745 proc = Popen4('hg st --rev "%s" -man0 .' %
2743 self.options.changed, None, 0)
2746 self.options.changed, None, 0)
2744 stdout, stderr = proc.communicate()
2747 stdout, stderr = proc.communicate()
2745 args = stdout.strip(b'\0').split(b'\0')
2748 args = stdout.strip(b'\0').split(b'\0')
2746 else:
2749 else:
2747 args = os.listdir(b'.')
2750 args = os.listdir(b'.')
2748
2751
2749 expanded_args = []
2752 expanded_args = []
2750 for arg in args:
2753 for arg in args:
2751 if os.path.isdir(arg):
2754 if os.path.isdir(arg):
2752 if not arg.endswith(b'/'):
2755 if not arg.endswith(b'/'):
2753 arg += b'/'
2756 arg += b'/'
2754 expanded_args.extend([arg + a for a in os.listdir(arg)])
2757 expanded_args.extend([arg + a for a in os.listdir(arg)])
2755 else:
2758 else:
2756 expanded_args.append(arg)
2759 expanded_args.append(arg)
2757 args = expanded_args
2760 args = expanded_args
2758
2761
2759 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.#]+))')
2762 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.#]+))')
2760 tests = []
2763 tests = []
2761 for t in args:
2764 for t in args:
2762 case = []
2765 case = []
2763
2766
2764 if not (os.path.basename(t).startswith(b'test-')
2767 if not (os.path.basename(t).startswith(b'test-')
2765 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2768 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2766
2769
2767 m = testcasepattern.match(t)
2770 m = testcasepattern.match(t)
2768 if m is not None:
2771 if m is not None:
2769 t, _, casestr = m.groups()
2772 t, _, casestr = m.groups()
2770 if casestr:
2773 if casestr:
2771 case = casestr.split(b'#')
2774 case = casestr.split(b'#')
2772 else:
2775 else:
2773 continue
2776 continue
2774
2777
2775 if t.endswith(b'.t'):
2778 if t.endswith(b'.t'):
2776 # .t file may contain multiple test cases
2779 # .t file may contain multiple test cases
2777 casedimensions = parsettestcases(t)
2780 casedimensions = parsettestcases(t)
2778 if casedimensions:
2781 if casedimensions:
2779 cases = []
2782 cases = []
2780 def addcases(case, casedimensions):
2783 def addcases(case, casedimensions):
2781 if not casedimensions:
2784 if not casedimensions:
2782 cases.append(case)
2785 cases.append(case)
2783 else:
2786 else:
2784 for c in casedimensions[0]:
2787 for c in casedimensions[0]:
2785 addcases(case + [c], casedimensions[1:])
2788 addcases(case + [c], casedimensions[1:])
2786 addcases([], casedimensions)
2789 addcases([], casedimensions)
2787 if case and case in cases:
2790 if case and case in cases:
2788 cases = [case]
2791 cases = [case]
2789 elif case:
2792 elif case:
2790 # Ignore invalid cases
2793 # Ignore invalid cases
2791 cases = []
2794 cases = []
2792 else:
2795 else:
2793 pass
2796 pass
2794 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2797 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2795 else:
2798 else:
2796 tests.append({'path': t})
2799 tests.append({'path': t})
2797 else:
2800 else:
2798 tests.append({'path': t})
2801 tests.append({'path': t})
2799 return tests
2802 return tests
2800
2803
2801 def _runtests(self, testdescs):
2804 def _runtests(self, testdescs):
2802 def _reloadtest(test, i):
2805 def _reloadtest(test, i):
2803 # convert a test back to its description dict
2806 # convert a test back to its description dict
2804 desc = {'path': test.path}
2807 desc = {'path': test.path}
2805 case = getattr(test, '_case', [])
2808 case = getattr(test, '_case', [])
2806 if case:
2809 if case:
2807 desc['case'] = case
2810 desc['case'] = case
2808 return self._gettest(desc, i)
2811 return self._gettest(desc, i)
2809
2812
2810 try:
2813 try:
2811 if self.options.restart:
2814 if self.options.restart:
2812 orig = list(testdescs)
2815 orig = list(testdescs)
2813 while testdescs:
2816 while testdescs:
2814 desc = testdescs[0]
2817 desc = testdescs[0]
2815 # desc['path'] is a relative path
2818 # desc['path'] is a relative path
2816 if 'case' in desc:
2819 if 'case' in desc:
2817 casestr = b'#'.join(desc['case'])
2820 casestr = b'#'.join(desc['case'])
2818 errpath = b'%s#%s.err' % (desc['path'], casestr)
2821 errpath = b'%s#%s.err' % (desc['path'], casestr)
2819 else:
2822 else:
2820 errpath = b'%s.err' % desc['path']
2823 errpath = b'%s.err' % desc['path']
2821 errpath = os.path.join(self._outputdir, errpath)
2824 errpath = os.path.join(self._outputdir, errpath)
2822 if os.path.exists(errpath):
2825 if os.path.exists(errpath):
2823 break
2826 break
2824 testdescs.pop(0)
2827 testdescs.pop(0)
2825 if not testdescs:
2828 if not testdescs:
2826 print("running all tests")
2829 print("running all tests")
2827 testdescs = orig
2830 testdescs = orig
2828
2831
2829 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2832 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2830
2833
2831 jobs = min(len(tests), self.options.jobs)
2834 jobs = min(len(tests), self.options.jobs)
2832
2835
2833 failed = False
2836 failed = False
2834 kws = self.options.keywords
2837 kws = self.options.keywords
2835 if kws is not None and PYTHON3:
2838 if kws is not None and PYTHON3:
2836 kws = kws.encode('utf-8')
2839 kws = kws.encode('utf-8')
2837
2840
2838 suite = TestSuite(self._testdir,
2841 suite = TestSuite(self._testdir,
2839 jobs=jobs,
2842 jobs=jobs,
2840 whitelist=self.options.whitelisted,
2843 whitelist=self.options.whitelisted,
2841 blacklist=self.options.blacklist,
2844 blacklist=self.options.blacklist,
2842 retest=self.options.retest,
2845 retest=self.options.retest,
2843 keywords=kws,
2846 keywords=kws,
2844 loop=self.options.loop,
2847 loop=self.options.loop,
2845 runs_per_test=self.options.runs_per_test,
2848 runs_per_test=self.options.runs_per_test,
2846 showchannels=self.options.showchannels,
2849 showchannels=self.options.showchannels,
2847 tests=tests, loadtest=_reloadtest)
2850 tests=tests, loadtest=_reloadtest)
2848 verbosity = 1
2851 verbosity = 1
2849 if self.options.list_tests:
2852 if self.options.list_tests:
2850 verbosity = 0
2853 verbosity = 0
2851 elif self.options.verbose:
2854 elif self.options.verbose:
2852 verbosity = 2
2855 verbosity = 2
2853 runner = TextTestRunner(self, verbosity=verbosity)
2856 runner = TextTestRunner(self, verbosity=verbosity)
2854
2857
2855 if self.options.list_tests:
2858 if self.options.list_tests:
2856 result = runner.listtests(suite)
2859 result = runner.listtests(suite)
2857 else:
2860 else:
2858 if self._installdir:
2861 if self._installdir:
2859 self._installhg()
2862 self._installhg()
2860 self._checkhglib("Testing")
2863 self._checkhglib("Testing")
2861 else:
2864 else:
2862 self._usecorrectpython()
2865 self._usecorrectpython()
2863 if self.options.chg:
2866 if self.options.chg:
2864 assert self._installdir
2867 assert self._installdir
2865 self._installchg()
2868 self._installchg()
2866
2869
2867 log('running %d tests using %d parallel processes' % (
2870 log('running %d tests using %d parallel processes' % (
2868 len(tests), jobs))
2871 len(tests), jobs))
2869
2872
2870 result = runner.run(suite)
2873 result = runner.run(suite)
2871
2874
2872 if result.failures:
2875 if result.failures:
2873 failed = True
2876 failed = True
2874
2877
2875 result.onEnd()
2878 result.onEnd()
2876
2879
2877 if self.options.anycoverage:
2880 if self.options.anycoverage:
2878 self._outputcoverage()
2881 self._outputcoverage()
2879 except KeyboardInterrupt:
2882 except KeyboardInterrupt:
2880 failed = True
2883 failed = True
2881 print("\ninterrupted!")
2884 print("\ninterrupted!")
2882
2885
2883 if failed:
2886 if failed:
2884 return 1
2887 return 1
2885
2888
2886 def _getport(self, count):
2889 def _getport(self, count):
2887 port = self._ports.get(count) # do we have a cached entry?
2890 port = self._ports.get(count) # do we have a cached entry?
2888 if port is None:
2891 if port is None:
2889 portneeded = 3
2892 portneeded = 3
2890 # above 100 tries we just give up and let test reports failure
2893 # above 100 tries we just give up and let test reports failure
2891 for tries in xrange(100):
2894 for tries in xrange(100):
2892 allfree = True
2895 allfree = True
2893 port = self.options.port + self._portoffset
2896 port = self.options.port + self._portoffset
2894 for idx in xrange(portneeded):
2897 for idx in xrange(portneeded):
2895 if not checkportisavailable(port + idx):
2898 if not checkportisavailable(port + idx):
2896 allfree = False
2899 allfree = False
2897 break
2900 break
2898 self._portoffset += portneeded
2901 self._portoffset += portneeded
2899 if allfree:
2902 if allfree:
2900 break
2903 break
2901 self._ports[count] = port
2904 self._ports[count] = port
2902 return port
2905 return port
2903
2906
2904 def _gettest(self, testdesc, count):
2907 def _gettest(self, testdesc, count):
2905 """Obtain a Test by looking at its filename.
2908 """Obtain a Test by looking at its filename.
2906
2909
2907 Returns a Test instance. The Test may not be runnable if it doesn't
2910 Returns a Test instance. The Test may not be runnable if it doesn't
2908 map to a known type.
2911 map to a known type.
2909 """
2912 """
2910 path = testdesc['path']
2913 path = testdesc['path']
2911 lctest = path.lower()
2914 lctest = path.lower()
2912 testcls = Test
2915 testcls = Test
2913
2916
2914 for ext, cls in self.TESTTYPES:
2917 for ext, cls in self.TESTTYPES:
2915 if lctest.endswith(ext):
2918 if lctest.endswith(ext):
2916 testcls = cls
2919 testcls = cls
2917 break
2920 break
2918
2921
2919 refpath = os.path.join(getcwdb(), path)
2922 refpath = os.path.join(getcwdb(), path)
2920 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2923 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2921
2924
2922 # extra keyword parameters. 'case' is used by .t tests
2925 # extra keyword parameters. 'case' is used by .t tests
2923 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2926 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2924
2927
2925 t = testcls(refpath, self._outputdir, tmpdir,
2928 t = testcls(refpath, self._outputdir, tmpdir,
2926 keeptmpdir=self.options.keep_tmpdir,
2929 keeptmpdir=self.options.keep_tmpdir,
2927 debug=self.options.debug,
2930 debug=self.options.debug,
2928 first=self.options.first,
2931 first=self.options.first,
2929 timeout=self.options.timeout,
2932 timeout=self.options.timeout,
2930 startport=self._getport(count),
2933 startport=self._getport(count),
2931 extraconfigopts=self.options.extra_config_opt,
2934 extraconfigopts=self.options.extra_config_opt,
2932 py3warnings=self.options.py3_warnings,
2935 py3warnings=self.options.py3_warnings,
2933 shell=self.options.shell,
2936 shell=self.options.shell,
2934 hgcommand=self._hgcommand,
2937 hgcommand=self._hgcommand,
2935 usechg=bool(self.options.with_chg or self.options.chg),
2938 usechg=bool(self.options.with_chg or self.options.chg),
2936 useipv6=useipv6, **kwds)
2939 useipv6=useipv6, **kwds)
2937 t.should_reload = True
2940 t.should_reload = True
2938 return t
2941 return t
2939
2942
2940 def _cleanup(self):
2943 def _cleanup(self):
2941 """Clean up state from this test invocation."""
2944 """Clean up state from this test invocation."""
2942 if self.options.keep_tmpdir:
2945 if self.options.keep_tmpdir:
2943 return
2946 return
2944
2947
2945 vlog("# Cleaning up HGTMP", self._hgtmp)
2948 vlog("# Cleaning up HGTMP", self._hgtmp)
2946 shutil.rmtree(self._hgtmp, True)
2949 shutil.rmtree(self._hgtmp, True)
2947 for f in self._createdfiles:
2950 for f in self._createdfiles:
2948 try:
2951 try:
2949 os.remove(f)
2952 os.remove(f)
2950 except OSError:
2953 except OSError:
2951 pass
2954 pass
2952
2955
2953 def _usecorrectpython(self):
2956 def _usecorrectpython(self):
2954 """Configure the environment to use the appropriate Python in tests."""
2957 """Configure the environment to use the appropriate Python in tests."""
2955 # Tests must use the same interpreter as us or bad things will happen.
2958 # Tests must use the same interpreter as us or bad things will happen.
2956 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2959 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2957
2960
2958 # os.symlink() is a thing with py3 on Windows, but it requires
2961 # os.symlink() is a thing with py3 on Windows, but it requires
2959 # Administrator rights.
2962 # Administrator rights.
2960 if getattr(os, 'symlink', None) and os.name != 'nt':
2963 if getattr(os, 'symlink', None) and os.name != 'nt':
2961 vlog("# Making python executable in test path a symlink to '%s'" %
2964 vlog("# Making python executable in test path a symlink to '%s'" %
2962 sys.executable)
2965 sys.executable)
2963 mypython = os.path.join(self._tmpbindir, pyexename)
2966 mypython = os.path.join(self._tmpbindir, pyexename)
2964 try:
2967 try:
2965 if os.readlink(mypython) == sys.executable:
2968 if os.readlink(mypython) == sys.executable:
2966 return
2969 return
2967 os.unlink(mypython)
2970 os.unlink(mypython)
2968 except OSError as err:
2971 except OSError as err:
2969 if err.errno != errno.ENOENT:
2972 if err.errno != errno.ENOENT:
2970 raise
2973 raise
2971 if self._findprogram(pyexename) != sys.executable:
2974 if self._findprogram(pyexename) != sys.executable:
2972 try:
2975 try:
2973 os.symlink(sys.executable, mypython)
2976 os.symlink(sys.executable, mypython)
2974 self._createdfiles.append(mypython)
2977 self._createdfiles.append(mypython)
2975 except OSError as err:
2978 except OSError as err:
2976 # child processes may race, which is harmless
2979 # child processes may race, which is harmless
2977 if err.errno != errno.EEXIST:
2980 if err.errno != errno.EEXIST:
2978 raise
2981 raise
2979 else:
2982 else:
2980 exedir, exename = os.path.split(sys.executable)
2983 exedir, exename = os.path.split(sys.executable)
2981 vlog("# Modifying search path to find %s as %s in '%s'" %
2984 vlog("# Modifying search path to find %s as %s in '%s'" %
2982 (exename, pyexename, exedir))
2985 (exename, pyexename, exedir))
2983 path = os.environ['PATH'].split(os.pathsep)
2986 path = os.environ['PATH'].split(os.pathsep)
2984 while exedir in path:
2987 while exedir in path:
2985 path.remove(exedir)
2988 path.remove(exedir)
2986 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2989 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2987 if not self._findprogram(pyexename):
2990 if not self._findprogram(pyexename):
2988 print("WARNING: Cannot find %s in search path" % pyexename)
2991 print("WARNING: Cannot find %s in search path" % pyexename)
2989
2992
2990 def _installhg(self):
2993 def _installhg(self):
2991 """Install hg into the test environment.
2994 """Install hg into the test environment.
2992
2995
2993 This will also configure hg with the appropriate testing settings.
2996 This will also configure hg with the appropriate testing settings.
2994 """
2997 """
2995 vlog("# Performing temporary installation of HG")
2998 vlog("# Performing temporary installation of HG")
2996 installerrs = os.path.join(self._hgtmp, b"install.err")
2999 installerrs = os.path.join(self._hgtmp, b"install.err")
2997 compiler = ''
3000 compiler = ''
2998 if self.options.compiler:
3001 if self.options.compiler:
2999 compiler = '--compiler ' + self.options.compiler
3002 compiler = '--compiler ' + self.options.compiler
3000 if self.options.pure:
3003 if self.options.pure:
3001 pure = b"--pure"
3004 pure = b"--pure"
3002 else:
3005 else:
3003 pure = b""
3006 pure = b""
3004
3007
3005 # Run installer in hg root
3008 # Run installer in hg root
3006 script = os.path.realpath(sys.argv[0])
3009 script = os.path.realpath(sys.argv[0])
3007 exe = sys.executable
3010 exe = sys.executable
3008 if PYTHON3:
3011 if PYTHON3:
3009 compiler = _bytespath(compiler)
3012 compiler = _bytespath(compiler)
3010 script = _bytespath(script)
3013 script = _bytespath(script)
3011 exe = _bytespath(exe)
3014 exe = _bytespath(exe)
3012 hgroot = os.path.dirname(os.path.dirname(script))
3015 hgroot = os.path.dirname(os.path.dirname(script))
3013 self._hgroot = hgroot
3016 self._hgroot = hgroot
3014 os.chdir(hgroot)
3017 os.chdir(hgroot)
3015 nohome = b'--home=""'
3018 nohome = b'--home=""'
3016 if os.name == 'nt':
3019 if os.name == 'nt':
3017 # The --home="" trick works only on OS where os.sep == '/'
3020 # The --home="" trick works only on OS where os.sep == '/'
3018 # because of a distutils convert_path() fast-path. Avoid it at
3021 # because of a distutils convert_path() fast-path. Avoid it at
3019 # least on Windows for now, deal with .pydistutils.cfg bugs
3022 # least on Windows for now, deal with .pydistutils.cfg bugs
3020 # when they happen.
3023 # when they happen.
3021 nohome = b''
3024 nohome = b''
3022 cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
3025 cmd = (b'"%(exe)s" setup.py %(pure)s clean --all'
3023 b' build %(compiler)s --build-base="%(base)s"'
3026 b' build %(compiler)s --build-base="%(base)s"'
3024 b' install --force --prefix="%(prefix)s"'
3027 b' install --force --prefix="%(prefix)s"'
3025 b' --install-lib="%(libdir)s"'
3028 b' --install-lib="%(libdir)s"'
3026 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3029 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3027 % {b'exe': exe, b'pure': pure,
3030 % {b'exe': exe, b'pure': pure,
3028 b'compiler': compiler,
3031 b'compiler': compiler,
3029 b'base': os.path.join(self._hgtmp, b"build"),
3032 b'base': os.path.join(self._hgtmp, b"build"),
3030 b'prefix': self._installdir, b'libdir': self._pythondir,
3033 b'prefix': self._installdir, b'libdir': self._pythondir,
3031 b'bindir': self._bindir,
3034 b'bindir': self._bindir,
3032 b'nohome': nohome, b'logfile': installerrs})
3035 b'nohome': nohome, b'logfile': installerrs})
3033
3036
3034 # setuptools requires install directories to exist.
3037 # setuptools requires install directories to exist.
3035 def makedirs(p):
3038 def makedirs(p):
3036 try:
3039 try:
3037 os.makedirs(p)
3040 os.makedirs(p)
3038 except OSError as e:
3041 except OSError as e:
3039 if e.errno != errno.EEXIST:
3042 if e.errno != errno.EEXIST:
3040 raise
3043 raise
3041 makedirs(self._pythondir)
3044 makedirs(self._pythondir)
3042 makedirs(self._bindir)
3045 makedirs(self._bindir)
3043
3046
3044 vlog("# Running", cmd)
3047 vlog("# Running", cmd)
3045 if subprocess.call(_strpath(cmd), shell=True) == 0:
3048 if subprocess.call(_strpath(cmd), shell=True) == 0:
3046 if not self.options.verbose:
3049 if not self.options.verbose:
3047 try:
3050 try:
3048 os.remove(installerrs)
3051 os.remove(installerrs)
3049 except OSError as e:
3052 except OSError as e:
3050 if e.errno != errno.ENOENT:
3053 if e.errno != errno.ENOENT:
3051 raise
3054 raise
3052 else:
3055 else:
3053 with open(installerrs, 'rb') as f:
3056 with open(installerrs, 'rb') as f:
3054 for line in f:
3057 for line in f:
3055 if PYTHON3:
3058 if PYTHON3:
3056 sys.stdout.buffer.write(line)
3059 sys.stdout.buffer.write(line)
3057 else:
3060 else:
3058 sys.stdout.write(line)
3061 sys.stdout.write(line)
3059 sys.exit(1)
3062 sys.exit(1)
3060 os.chdir(self._testdir)
3063 os.chdir(self._testdir)
3061
3064
3062 self._usecorrectpython()
3065 self._usecorrectpython()
3063
3066
3064 if self.options.py3_warnings and not self.options.anycoverage:
3067 if self.options.py3_warnings and not self.options.anycoverage:
3065 vlog("# Updating hg command to enable Py3k Warnings switch")
3068 vlog("# Updating hg command to enable Py3k Warnings switch")
3066 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3069 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
3067 lines = [line.rstrip() for line in f]
3070 lines = [line.rstrip() for line in f]
3068 lines[0] += ' -3'
3071 lines[0] += ' -3'
3069 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3072 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
3070 for line in lines:
3073 for line in lines:
3071 f.write(line + '\n')
3074 f.write(line + '\n')
3072
3075
3073 hgbat = os.path.join(self._bindir, b'hg.bat')
3076 hgbat = os.path.join(self._bindir, b'hg.bat')
3074 if os.path.isfile(hgbat):
3077 if os.path.isfile(hgbat):
3075 # hg.bat expects to be put in bin/scripts while run-tests.py
3078 # hg.bat expects to be put in bin/scripts while run-tests.py
3076 # installation layout put it in bin/ directly. Fix it
3079 # installation layout put it in bin/ directly. Fix it
3077 with open(hgbat, 'rb') as f:
3080 with open(hgbat, 'rb') as f:
3078 data = f.read()
3081 data = f.read()
3079 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
3082 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
3080 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
3083 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
3081 b'"%~dp0python" "%~dp0hg" %*')
3084 b'"%~dp0python" "%~dp0hg" %*')
3082 with open(hgbat, 'wb') as f:
3085 with open(hgbat, 'wb') as f:
3083 f.write(data)
3086 f.write(data)
3084 else:
3087 else:
3085 print('WARNING: cannot fix hg.bat reference to python.exe')
3088 print('WARNING: cannot fix hg.bat reference to python.exe')
3086
3089
3087 if self.options.anycoverage:
3090 if self.options.anycoverage:
3088 custom = os.path.join(self._testdir, 'sitecustomize.py')
3091 custom = os.path.join(self._testdir, 'sitecustomize.py')
3089 target = os.path.join(self._pythondir, 'sitecustomize.py')
3092 target = os.path.join(self._pythondir, 'sitecustomize.py')
3090 vlog('# Installing coverage trigger to %s' % target)
3093 vlog('# Installing coverage trigger to %s' % target)
3091 shutil.copyfile(custom, target)
3094 shutil.copyfile(custom, target)
3092 rc = os.path.join(self._testdir, '.coveragerc')
3095 rc = os.path.join(self._testdir, '.coveragerc')
3093 vlog('# Installing coverage rc to %s' % rc)
3096 vlog('# Installing coverage rc to %s' % rc)
3094 os.environ['COVERAGE_PROCESS_START'] = rc
3097 os.environ['COVERAGE_PROCESS_START'] = rc
3095 covdir = os.path.join(self._installdir, '..', 'coverage')
3098 covdir = os.path.join(self._installdir, '..', 'coverage')
3096 try:
3099 try:
3097 os.mkdir(covdir)
3100 os.mkdir(covdir)
3098 except OSError as e:
3101 except OSError as e:
3099 if e.errno != errno.EEXIST:
3102 if e.errno != errno.EEXIST:
3100 raise
3103 raise
3101
3104
3102 os.environ['COVERAGE_DIR'] = covdir
3105 os.environ['COVERAGE_DIR'] = covdir
3103
3106
3104 def _checkhglib(self, verb):
3107 def _checkhglib(self, verb):
3105 """Ensure that the 'mercurial' package imported by python is
3108 """Ensure that the 'mercurial' package imported by python is
3106 the one we expect it to be. If not, print a warning to stderr."""
3109 the one we expect it to be. If not, print a warning to stderr."""
3107 if ((self._bindir == self._pythondir) and
3110 if ((self._bindir == self._pythondir) and
3108 (self._bindir != self._tmpbindir)):
3111 (self._bindir != self._tmpbindir)):
3109 # The pythondir has been inferred from --with-hg flag.
3112 # The pythondir has been inferred from --with-hg flag.
3110 # We cannot expect anything sensible here.
3113 # We cannot expect anything sensible here.
3111 return
3114 return
3112 expecthg = os.path.join(self._pythondir, b'mercurial')
3115 expecthg = os.path.join(self._pythondir, b'mercurial')
3113 actualhg = self._gethgpath()
3116 actualhg = self._gethgpath()
3114 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3117 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3115 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3118 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
3116 ' (expected %s)\n'
3119 ' (expected %s)\n'
3117 % (verb, actualhg, expecthg))
3120 % (verb, actualhg, expecthg))
3118 def _gethgpath(self):
3121 def _gethgpath(self):
3119 """Return the path to the mercurial package that is actually found by
3122 """Return the path to the mercurial package that is actually found by
3120 the current Python interpreter."""
3123 the current Python interpreter."""
3121 if self._hgpath is not None:
3124 if self._hgpath is not None:
3122 return self._hgpath
3125 return self._hgpath
3123
3126
3124 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3127 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3125 cmd = cmd % PYTHON
3128 cmd = cmd % PYTHON
3126 if PYTHON3:
3129 if PYTHON3:
3127 cmd = _strpath(cmd)
3130 cmd = _strpath(cmd)
3128
3131
3129 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3132 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3130 out, err = p.communicate()
3133 out, err = p.communicate()
3131
3134
3132 self._hgpath = out.strip()
3135 self._hgpath = out.strip()
3133
3136
3134 return self._hgpath
3137 return self._hgpath
3135
3138
3136 def _installchg(self):
3139 def _installchg(self):
3137 """Install chg into the test environment"""
3140 """Install chg into the test environment"""
3138 vlog('# Performing temporary installation of CHG')
3141 vlog('# Performing temporary installation of CHG')
3139 assert os.path.dirname(self._bindir) == self._installdir
3142 assert os.path.dirname(self._bindir) == self._installdir
3140 assert self._hgroot, 'must be called after _installhg()'
3143 assert self._hgroot, 'must be called after _installhg()'
3141 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3144 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3142 % {b'make': 'make', # TODO: switch by option or environment?
3145 % {b'make': 'make', # TODO: switch by option or environment?
3143 b'prefix': self._installdir})
3146 b'prefix': self._installdir})
3144 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3147 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3145 vlog("# Running", cmd)
3148 vlog("# Running", cmd)
3146 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3149 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3147 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3150 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3148 stderr=subprocess.STDOUT)
3151 stderr=subprocess.STDOUT)
3149 out, _err = proc.communicate()
3152 out, _err = proc.communicate()
3150 if proc.returncode != 0:
3153 if proc.returncode != 0:
3151 if PYTHON3:
3154 if PYTHON3:
3152 sys.stdout.buffer.write(out)
3155 sys.stdout.buffer.write(out)
3153 else:
3156 else:
3154 sys.stdout.write(out)
3157 sys.stdout.write(out)
3155 sys.exit(1)
3158 sys.exit(1)
3156
3159
3157 def _outputcoverage(self):
3160 def _outputcoverage(self):
3158 """Produce code coverage output."""
3161 """Produce code coverage output."""
3159 import coverage
3162 import coverage
3160 coverage = coverage.coverage
3163 coverage = coverage.coverage
3161
3164
3162 vlog('# Producing coverage report')
3165 vlog('# Producing coverage report')
3163 # chdir is the easiest way to get short, relative paths in the
3166 # chdir is the easiest way to get short, relative paths in the
3164 # output.
3167 # output.
3165 os.chdir(self._hgroot)
3168 os.chdir(self._hgroot)
3166 covdir = os.path.join(self._installdir, '..', 'coverage')
3169 covdir = os.path.join(self._installdir, '..', 'coverage')
3167 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3170 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3168
3171
3169 # Map install directory paths back to source directory.
3172 # Map install directory paths back to source directory.
3170 cov.config.paths['srcdir'] = ['.', self._pythondir]
3173 cov.config.paths['srcdir'] = ['.', self._pythondir]
3171
3174
3172 cov.combine()
3175 cov.combine()
3173
3176
3174 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3177 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3175 cov.report(ignore_errors=True, omit=omit)
3178 cov.report(ignore_errors=True, omit=omit)
3176
3179
3177 if self.options.htmlcov:
3180 if self.options.htmlcov:
3178 htmldir = os.path.join(self._outputdir, 'htmlcov')
3181 htmldir = os.path.join(self._outputdir, 'htmlcov')
3179 cov.html_report(directory=htmldir, omit=omit)
3182 cov.html_report(directory=htmldir, omit=omit)
3180 if self.options.annotate:
3183 if self.options.annotate:
3181 adir = os.path.join(self._outputdir, 'annotated')
3184 adir = os.path.join(self._outputdir, 'annotated')
3182 if not os.path.isdir(adir):
3185 if not os.path.isdir(adir):
3183 os.mkdir(adir)
3186 os.mkdir(adir)
3184 cov.annotate(directory=adir, omit=omit)
3187 cov.annotate(directory=adir, omit=omit)
3185
3188
3186 def _findprogram(self, program):
3189 def _findprogram(self, program):
3187 """Search PATH for a executable program"""
3190 """Search PATH for a executable program"""
3188 dpb = _bytespath(os.defpath)
3191 dpb = _bytespath(os.defpath)
3189 sepb = _bytespath(os.pathsep)
3192 sepb = _bytespath(os.pathsep)
3190 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3193 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3191 name = os.path.join(p, program)
3194 name = os.path.join(p, program)
3192 if os.name == 'nt' or os.access(name, os.X_OK):
3195 if os.name == 'nt' or os.access(name, os.X_OK):
3193 return name
3196 return name
3194 return None
3197 return None
3195
3198
3196 def _checktools(self):
3199 def _checktools(self):
3197 """Ensure tools required to run tests are present."""
3200 """Ensure tools required to run tests are present."""
3198 for p in self.REQUIREDTOOLS:
3201 for p in self.REQUIREDTOOLS:
3199 if os.name == 'nt' and not p.endswith(b'.exe'):
3202 if os.name == 'nt' and not p.endswith(b'.exe'):
3200 p += b'.exe'
3203 p += b'.exe'
3201 found = self._findprogram(p)
3204 found = self._findprogram(p)
3202 if found:
3205 if found:
3203 vlog("# Found prerequisite", p, "at", found)
3206 vlog("# Found prerequisite", p, "at", found)
3204 else:
3207 else:
3205 print("WARNING: Did not find prerequisite tool: %s " %
3208 print("WARNING: Did not find prerequisite tool: %s " %
3206 p.decode("utf-8"))
3209 p.decode("utf-8"))
3207
3210
3208 def aggregateexceptions(path):
3211 def aggregateexceptions(path):
3209 exceptioncounts = collections.Counter()
3212 exceptioncounts = collections.Counter()
3210 testsbyfailure = collections.defaultdict(set)
3213 testsbyfailure = collections.defaultdict(set)
3211 failuresbytest = collections.defaultdict(set)
3214 failuresbytest = collections.defaultdict(set)
3212
3215
3213 for f in os.listdir(path):
3216 for f in os.listdir(path):
3214 with open(os.path.join(path, f), 'rb') as fh:
3217 with open(os.path.join(path, f), 'rb') as fh:
3215 data = fh.read().split(b'\0')
3218 data = fh.read().split(b'\0')
3216 if len(data) != 5:
3219 if len(data) != 5:
3217 continue
3220 continue
3218
3221
3219 exc, mainframe, hgframe, hgline, testname = data
3222 exc, mainframe, hgframe, hgline, testname = data
3220 exc = exc.decode('utf-8')
3223 exc = exc.decode('utf-8')
3221 mainframe = mainframe.decode('utf-8')
3224 mainframe = mainframe.decode('utf-8')
3222 hgframe = hgframe.decode('utf-8')
3225 hgframe = hgframe.decode('utf-8')
3223 hgline = hgline.decode('utf-8')
3226 hgline = hgline.decode('utf-8')
3224 testname = testname.decode('utf-8')
3227 testname = testname.decode('utf-8')
3225
3228
3226 key = (hgframe, hgline, exc)
3229 key = (hgframe, hgline, exc)
3227 exceptioncounts[key] += 1
3230 exceptioncounts[key] += 1
3228 testsbyfailure[key].add(testname)
3231 testsbyfailure[key].add(testname)
3229 failuresbytest[testname].add(key)
3232 failuresbytest[testname].add(key)
3230
3233
3231 # Find test having fewest failures for each failure.
3234 # Find test having fewest failures for each failure.
3232 leastfailing = {}
3235 leastfailing = {}
3233 for key, tests in testsbyfailure.items():
3236 for key, tests in testsbyfailure.items():
3234 fewesttest = None
3237 fewesttest = None
3235 fewestcount = 99999999
3238 fewestcount = 99999999
3236 for test in sorted(tests):
3239 for test in sorted(tests):
3237 if len(failuresbytest[test]) < fewestcount:
3240 if len(failuresbytest[test]) < fewestcount:
3238 fewesttest = test
3241 fewesttest = test
3239 fewestcount = len(failuresbytest[test])
3242 fewestcount = len(failuresbytest[test])
3240
3243
3241 leastfailing[key] = (fewestcount, fewesttest)
3244 leastfailing[key] = (fewestcount, fewesttest)
3242
3245
3243 # Create a combined counter so we can sort by total occurrences and
3246 # Create a combined counter so we can sort by total occurrences and
3244 # impacted tests.
3247 # impacted tests.
3245 combined = {}
3248 combined = {}
3246 for key in exceptioncounts:
3249 for key in exceptioncounts:
3247 combined[key] = (exceptioncounts[key],
3250 combined[key] = (exceptioncounts[key],
3248 len(testsbyfailure[key]),
3251 len(testsbyfailure[key]),
3249 leastfailing[key][0],
3252 leastfailing[key][0],
3250 leastfailing[key][1])
3253 leastfailing[key][1])
3251
3254
3252 return {
3255 return {
3253 'exceptioncounts': exceptioncounts,
3256 'exceptioncounts': exceptioncounts,
3254 'total': sum(exceptioncounts.values()),
3257 'total': sum(exceptioncounts.values()),
3255 'combined': combined,
3258 'combined': combined,
3256 'leastfailing': leastfailing,
3259 'leastfailing': leastfailing,
3257 'byfailure': testsbyfailure,
3260 'byfailure': testsbyfailure,
3258 'bytest': failuresbytest,
3261 'bytest': failuresbytest,
3259 }
3262 }
3260
3263
3261 if __name__ == '__main__':
3264 if __name__ == '__main__':
3262 runner = TestRunner()
3265 runner = TestRunner()
3263
3266
3264 try:
3267 try:
3265 import msvcrt
3268 import msvcrt
3266 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3269 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3267 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3270 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3268 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3271 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3269 except ImportError:
3272 except ImportError:
3270 pass
3273 pass
3271
3274
3272 sys.exit(runner.run(sys.argv[1:]))
3275 sys.exit(runner.run(sys.argv[1:]))
@@ -1,266 +1,266 b''
1 hg debuginstall
1 hg debuginstall
2 $ hg debuginstall
2 $ hg debuginstall
3 checking encoding (ascii)...
3 checking encoding (ascii)...
4 checking Python executable (*) (glob)
4 checking Python executable (*) (glob)
5 checking Python version (2.*) (glob) (no-py3 !)
5 checking Python version (2.*) (glob) (no-py3 !)
6 checking Python version (3.*) (glob) (py3 !)
6 checking Python version (3.*) (glob) (py3 !)
7 checking Python lib (*lib*)... (glob)
7 checking Python lib (.*[Ll]ib.*)... (re)
8 checking Python security support (*) (glob)
8 checking Python security support (*) (glob)
9 TLS 1.2 not supported by Python install; network connections lack modern security (?)
9 TLS 1.2 not supported by Python install; network connections lack modern security (?)
10 SNI not supported by Python install; may have connectivity issues with some servers (?)
10 SNI not supported by Python install; may have connectivity issues with some servers (?)
11 checking Mercurial version (*) (glob)
11 checking Mercurial version (*) (glob)
12 checking Mercurial custom build (*) (glob)
12 checking Mercurial custom build (*) (glob)
13 checking module policy (*) (glob)
13 checking module policy (*) (glob)
14 checking installed modules (*mercurial)... (glob)
14 checking installed modules (*mercurial)... (glob)
15 checking registered compression engines (*zlib*) (glob)
15 checking registered compression engines (*zlib*) (glob)
16 checking available compression engines (*zlib*) (glob)
16 checking available compression engines (*zlib*) (glob)
17 checking available compression engines for wire protocol (*zlib*) (glob)
17 checking available compression engines for wire protocol (*zlib*) (glob)
18 checking "re2" regexp engine \((available|missing)\) (re)
18 checking "re2" regexp engine \((available|missing)\) (re)
19 checking templates (*mercurial?templates)... (glob)
19 checking templates (*mercurial?templates)... (glob)
20 checking default template (*mercurial?templates?map-cmdline.default) (glob)
20 checking default template (*mercurial?templates?map-cmdline.default) (glob)
21 checking commit editor... (*) (glob)
21 checking commit editor... (*) (glob)
22 checking username (test)
22 checking username (test)
23 no problems detected
23 no problems detected
24
24
25 hg debuginstall JSON
25 hg debuginstall JSON
26 $ hg debuginstall -Tjson | sed 's|\\\\|\\|g'
26 $ hg debuginstall -Tjson | sed 's|\\\\|\\|g'
27 [
27 [
28 {
28 {
29 "compengines": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
29 "compengines": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
30 "compenginesavail": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
30 "compenginesavail": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
31 "compenginesserver": [*"zlib"*], (glob)
31 "compenginesserver": [*"zlib"*], (glob)
32 "defaulttemplate": "*mercurial?templates?map-cmdline.default", (glob)
32 "defaulttemplate": "*mercurial?templates?map-cmdline.default", (glob)
33 "defaulttemplateerror": null,
33 "defaulttemplateerror": null,
34 "defaulttemplatenotfound": "default",
34 "defaulttemplatenotfound": "default",
35 "editor": "*", (glob)
35 "editor": "*", (glob)
36 "editornotfound": false,
36 "editornotfound": false,
37 "encoding": "ascii",
37 "encoding": "ascii",
38 "encodingerror": null,
38 "encodingerror": null,
39 "extensionserror": null, (no-pure !)
39 "extensionserror": null, (no-pure !)
40 "hgmodulepolicy": "*", (glob)
40 "hgmodulepolicy": "*", (glob)
41 "hgmodules": "*mercurial", (glob)
41 "hgmodules": "*mercurial", (glob)
42 "hgver": "*", (glob)
42 "hgver": "*", (glob)
43 "hgverextra": "*", (glob)
43 "hgverextra": "*", (glob)
44 "problems": 0,
44 "problems": 0,
45 "pythonexe": "*", (glob)
45 "pythonexe": "*", (glob)
46 "pythonlib": "*", (glob)
46 "pythonlib": "*", (glob)
47 "pythonsecurity": [*], (glob)
47 "pythonsecurity": [*], (glob)
48 "pythonver": "*.*.*", (glob)
48 "pythonver": "*.*.*", (glob)
49 "re2": (true|false), (re)
49 "re2": (true|false), (re)
50 "templatedirs": "*mercurial?templates", (glob)
50 "templatedirs": "*mercurial?templates", (glob)
51 "username": "test",
51 "username": "test",
52 "usernameerror": null,
52 "usernameerror": null,
53 "vinotfound": false
53 "vinotfound": false
54 }
54 }
55 ]
55 ]
56
56
57 hg debuginstall with no username
57 hg debuginstall with no username
58 $ HGUSER= hg debuginstall
58 $ HGUSER= hg debuginstall
59 checking encoding (ascii)...
59 checking encoding (ascii)...
60 checking Python executable (*) (glob)
60 checking Python executable (*) (glob)
61 checking Python version (2.*) (glob) (no-py3 !)
61 checking Python version (2.*) (glob) (no-py3 !)
62 checking Python version (3.*) (glob) (py3 !)
62 checking Python version (3.*) (glob) (py3 !)
63 checking Python lib (*lib*)... (glob)
63 checking Python lib (.*[Ll]ib.*)... (re)
64 checking Python security support (*) (glob)
64 checking Python security support (*) (glob)
65 TLS 1.2 not supported by Python install; network connections lack modern security (?)
65 TLS 1.2 not supported by Python install; network connections lack modern security (?)
66 SNI not supported by Python install; may have connectivity issues with some servers (?)
66 SNI not supported by Python install; may have connectivity issues with some servers (?)
67 checking Mercurial version (*) (glob)
67 checking Mercurial version (*) (glob)
68 checking Mercurial custom build (*) (glob)
68 checking Mercurial custom build (*) (glob)
69 checking module policy (*) (glob)
69 checking module policy (*) (glob)
70 checking installed modules (*mercurial)... (glob)
70 checking installed modules (*mercurial)... (glob)
71 checking registered compression engines (*zlib*) (glob)
71 checking registered compression engines (*zlib*) (glob)
72 checking available compression engines (*zlib*) (glob)
72 checking available compression engines (*zlib*) (glob)
73 checking available compression engines for wire protocol (*zlib*) (glob)
73 checking available compression engines for wire protocol (*zlib*) (glob)
74 checking "re2" regexp engine \((available|missing)\) (re)
74 checking "re2" regexp engine \((available|missing)\) (re)
75 checking templates (*mercurial?templates)... (glob)
75 checking templates (*mercurial?templates)... (glob)
76 checking default template (*mercurial?templates?map-cmdline.default) (glob)
76 checking default template (*mercurial?templates?map-cmdline.default) (glob)
77 checking commit editor... (*) (glob)
77 checking commit editor... (*) (glob)
78 checking username...
78 checking username...
79 no username supplied
79 no username supplied
80 (specify a username in your configuration file)
80 (specify a username in your configuration file)
81 1 problems detected, please check your install!
81 1 problems detected, please check your install!
82 [1]
82 [1]
83
83
84 hg debuginstall with invalid encoding
84 hg debuginstall with invalid encoding
85 $ HGENCODING=invalidenc hg debuginstall | grep encoding
85 $ HGENCODING=invalidenc hg debuginstall | grep encoding
86 checking encoding (invalidenc)...
86 checking encoding (invalidenc)...
87 unknown encoding: invalidenc
87 unknown encoding: invalidenc
88
88
89 exception message in JSON
89 exception message in JSON
90
90
91 $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
91 $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
92 "defaulttemplateerror": null,
92 "defaulttemplateerror": null,
93 "encodingerror": "unknown encoding: invalidenc",
93 "encodingerror": "unknown encoding: invalidenc",
94 "extensionserror": null, (no-pure !)
94 "extensionserror": null, (no-pure !)
95 "usernameerror": "no username supplied",
95 "usernameerror": "no username supplied",
96
96
97 path variables are expanded (~ is the same as $TESTTMP)
97 path variables are expanded (~ is the same as $TESTTMP)
98 $ mkdir tools
98 $ mkdir tools
99 $ touch tools/testeditor.exe
99 $ touch tools/testeditor.exe
100 #if execbit
100 #if execbit
101 $ chmod 755 tools/testeditor.exe
101 $ chmod 755 tools/testeditor.exe
102 #endif
102 #endif
103 $ HGEDITOR="~/tools/testeditor.exe" hg debuginstall
103 $ HGEDITOR="~/tools/testeditor.exe" hg debuginstall
104 checking encoding (ascii)...
104 checking encoding (ascii)...
105 checking Python executable (*) (glob)
105 checking Python executable (*) (glob)
106 checking Python version (2.*) (glob) (no-py3 !)
106 checking Python version (2.*) (glob) (no-py3 !)
107 checking Python version (3.*) (glob) (py3 !)
107 checking Python version (3.*) (glob) (py3 !)
108 checking Python lib (*lib*)... (glob)
108 checking Python lib (.*[Ll]ib.*)... (re)
109 checking Python security support (*) (glob)
109 checking Python security support (*) (glob)
110 TLS 1.2 not supported by Python install; network connections lack modern security (?)
110 TLS 1.2 not supported by Python install; network connections lack modern security (?)
111 SNI not supported by Python install; may have connectivity issues with some servers (?)
111 SNI not supported by Python install; may have connectivity issues with some servers (?)
112 checking Mercurial version (*) (glob)
112 checking Mercurial version (*) (glob)
113 checking Mercurial custom build (*) (glob)
113 checking Mercurial custom build (*) (glob)
114 checking module policy (*) (glob)
114 checking module policy (*) (glob)
115 checking installed modules (*mercurial)... (glob)
115 checking installed modules (*mercurial)... (glob)
116 checking registered compression engines (*zlib*) (glob)
116 checking registered compression engines (*zlib*) (glob)
117 checking available compression engines (*zlib*) (glob)
117 checking available compression engines (*zlib*) (glob)
118 checking available compression engines for wire protocol (*zlib*) (glob)
118 checking available compression engines for wire protocol (*zlib*) (glob)
119 checking "re2" regexp engine \((available|missing)\) (re)
119 checking "re2" regexp engine \((available|missing)\) (re)
120 checking templates (*mercurial?templates)... (glob)
120 checking templates (*mercurial?templates)... (glob)
121 checking default template (*mercurial?templates?map-cmdline.default) (glob)
121 checking default template (*mercurial?templates?map-cmdline.default) (glob)
122 checking commit editor... ($TESTTMP/tools/testeditor.exe)
122 checking commit editor... ($TESTTMP/tools/testeditor.exe)
123 checking username (test)
123 checking username (test)
124 no problems detected
124 no problems detected
125
125
126 print out the binary post-shlexsplit in the error message when commit editor is
126 print out the binary post-shlexsplit in the error message when commit editor is
127 not found (this is intentionally using backslashes to mimic a windows usecase).
127 not found (this is intentionally using backslashes to mimic a windows usecase).
128 $ HGEDITOR="c:\foo\bar\baz.exe -y -z" hg debuginstall
128 $ HGEDITOR="c:\foo\bar\baz.exe -y -z" hg debuginstall
129 checking encoding (ascii)...
129 checking encoding (ascii)...
130 checking Python executable (*) (glob)
130 checking Python executable (*) (glob)
131 checking Python version (2.*) (glob) (no-py3 !)
131 checking Python version (2.*) (glob) (no-py3 !)
132 checking Python version (3.*) (glob) (py3 !)
132 checking Python version (3.*) (glob) (py3 !)
133 checking Python lib (*lib*)... (glob)
133 checking Python lib (.*[Ll]ib.*)... (re)
134 checking Python security support (*) (glob)
134 checking Python security support (*) (glob)
135 TLS 1.2 not supported by Python install; network connections lack modern security (?)
135 TLS 1.2 not supported by Python install; network connections lack modern security (?)
136 SNI not supported by Python install; may have connectivity issues with some servers (?)
136 SNI not supported by Python install; may have connectivity issues with some servers (?)
137 checking Mercurial version (*) (glob)
137 checking Mercurial version (*) (glob)
138 checking Mercurial custom build (*) (glob)
138 checking Mercurial custom build (*) (glob)
139 checking module policy (*) (glob)
139 checking module policy (*) (glob)
140 checking installed modules (*mercurial)... (glob)
140 checking installed modules (*mercurial)... (glob)
141 checking registered compression engines (*zlib*) (glob)
141 checking registered compression engines (*zlib*) (glob)
142 checking available compression engines (*zlib*) (glob)
142 checking available compression engines (*zlib*) (glob)
143 checking available compression engines for wire protocol (*zlib*) (glob)
143 checking available compression engines for wire protocol (*zlib*) (glob)
144 checking "re2" regexp engine \((available|missing)\) (re)
144 checking "re2" regexp engine \((available|missing)\) (re)
145 checking templates (*mercurial?templates)... (glob)
145 checking templates (*mercurial?templates)... (glob)
146 checking default template (*mercurial?templates?map-cmdline.default) (glob)
146 checking default template (*mercurial?templates?map-cmdline.default) (glob)
147 checking commit editor... (c:\foo\bar\baz.exe) (windows !)
147 checking commit editor... (c:\foo\bar\baz.exe) (windows !)
148 Can't find editor 'c:\foo\bar\baz.exe' in PATH (windows !)
148 Can't find editor 'c:\foo\bar\baz.exe' in PATH (windows !)
149 checking commit editor... (c:foobarbaz.exe) (no-windows !)
149 checking commit editor... (c:foobarbaz.exe) (no-windows !)
150 Can't find editor 'c:foobarbaz.exe' in PATH (no-windows !)
150 Can't find editor 'c:foobarbaz.exe' in PATH (no-windows !)
151 (specify a commit editor in your configuration file)
151 (specify a commit editor in your configuration file)
152 checking username (test)
152 checking username (test)
153 1 problems detected, please check your install!
153 1 problems detected, please check your install!
154 [1]
154 [1]
155
155
156 #if test-repo
156 #if test-repo
157 $ . "$TESTDIR/helpers-testrepo.sh"
157 $ . "$TESTDIR/helpers-testrepo.sh"
158
158
159 $ cat >> wixxml.py << EOF
159 $ cat >> wixxml.py << EOF
160 > import os
160 > import os
161 > import subprocess
161 > import subprocess
162 > import sys
162 > import sys
163 > import xml.etree.ElementTree as ET
163 > import xml.etree.ElementTree as ET
164 >
164 >
165 > # MSYS mangles the path if it expands $TESTDIR
165 > # MSYS mangles the path if it expands $TESTDIR
166 > testdir = os.environ['TESTDIR']
166 > testdir = os.environ['TESTDIR']
167 > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
167 > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
168 >
168 >
169 > def directory(node, relpath):
169 > def directory(node, relpath):
170 > '''generator of files in the xml node, rooted at relpath'''
170 > '''generator of files in the xml node, rooted at relpath'''
171 > dirs = node.findall('./{%(wix)s}Directory' % ns)
171 > dirs = node.findall('./{%(wix)s}Directory' % ns)
172 >
172 >
173 > for d in dirs:
173 > for d in dirs:
174 > for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
174 > for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
175 > yield subfile
175 > yield subfile
176 >
176 >
177 > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
177 > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
178 >
178 >
179 > for f in files:
179 > for f in files:
180 > yield relpath + f.attrib['Name']
180 > yield relpath + f.attrib['Name']
181 >
181 >
182 > def hgdirectory(relpath):
182 > def hgdirectory(relpath):
183 > '''generator of tracked files, rooted at relpath'''
183 > '''generator of tracked files, rooted at relpath'''
184 > hgdir = "%s/../mercurial" % (testdir)
184 > hgdir = "%s/../mercurial" % (testdir)
185 > args = ['hg', '--cwd', hgdir, 'files', relpath]
185 > args = ['hg', '--cwd', hgdir, 'files', relpath]
186 > proc = subprocess.Popen(args, stdout=subprocess.PIPE,
186 > proc = subprocess.Popen(args, stdout=subprocess.PIPE,
187 > stderr=subprocess.PIPE)
187 > stderr=subprocess.PIPE)
188 > output = proc.communicate()[0]
188 > output = proc.communicate()[0]
189 >
189 >
190 > slash = '/'
190 > slash = '/'
191 > for line in output.splitlines():
191 > for line in output.splitlines():
192 > if os.name == 'nt':
192 > if os.name == 'nt':
193 > yield line.replace(os.sep, slash)
193 > yield line.replace(os.sep, slash)
194 > else:
194 > else:
195 > yield line
195 > yield line
196 >
196 >
197 > tracked = [f for f in hgdirectory(sys.argv[1])]
197 > tracked = [f for f in hgdirectory(sys.argv[1])]
198 >
198 >
199 > xml = ET.parse("%s/../contrib/wix/%s.wxs" % (testdir, sys.argv[1]))
199 > xml = ET.parse("%s/../contrib/wix/%s.wxs" % (testdir, sys.argv[1]))
200 > root = xml.getroot()
200 > root = xml.getroot()
201 > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
201 > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
202 >
202 >
203 > installed = [f for f in directory(dir, '')]
203 > installed = [f for f in directory(dir, '')]
204 >
204 >
205 > print('Not installed:')
205 > print('Not installed:')
206 > for f in sorted(set(tracked) - set(installed)):
206 > for f in sorted(set(tracked) - set(installed)):
207 > print(' %s' % f)
207 > print(' %s' % f)
208 >
208 >
209 > print('Not tracked:')
209 > print('Not tracked:')
210 > for f in sorted(set(installed) - set(tracked)):
210 > for f in sorted(set(installed) - set(tracked)):
211 > print(' %s' % f)
211 > print(' %s' % f)
212 > EOF
212 > EOF
213
213
214 $ ( testrepohgenv; "$PYTHON" wixxml.py help )
214 $ ( testrepohgenv; "$PYTHON" wixxml.py help )
215 Not installed:
215 Not installed:
216 help/common.txt
216 help/common.txt
217 help/hg-ssh.8.txt
217 help/hg-ssh.8.txt
218 help/hg.1.txt
218 help/hg.1.txt
219 help/hgignore.5.txt
219 help/hgignore.5.txt
220 help/hgrc.5.txt
220 help/hgrc.5.txt
221 Not tracked:
221 Not tracked:
222
222
223 $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
223 $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
224 Not installed:
224 Not installed:
225 Not tracked:
225 Not tracked:
226
226
227 #endif
227 #endif
228
228
229 #if virtualenv
229 #if virtualenv
230
230
231 Verify that Mercurial is installable with pip. Note that this MUST be
231 Verify that Mercurial is installable with pip. Note that this MUST be
232 the last test in this file, because we do some nasty things to the
232 the last test in this file, because we do some nasty things to the
233 shell environment in order to make the virtualenv work reliably.
233 shell environment in order to make the virtualenv work reliably.
234
234
235 $ cd $TESTTMP
235 $ cd $TESTTMP
236 Note: --no-site-packages is deprecated, but some places have an
236 Note: --no-site-packages is deprecated, but some places have an
237 ancient virtualenv from their linux distro or similar and it's not yet
237 ancient virtualenv from their linux distro or similar and it's not yet
238 the default for them.
238 the default for them.
239 $ unset PYTHONPATH
239 $ unset PYTHONPATH
240 $ "$PYTHON" -m virtualenv --no-site-packages --never-download installenv >> pip.log
240 $ "$PYTHON" -m virtualenv --no-site-packages --never-download installenv >> pip.log
241 Note: we use this weird path to run pip and hg to avoid platform differences,
241 Note: we use this weird path to run pip and hg to avoid platform differences,
242 since it's bin on most platforms but Scripts on Windows.
242 since it's bin on most platforms but Scripts on Windows.
243 $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log
243 $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log
244 $ ./installenv/*/hg debuginstall || cat pip.log
244 $ ./installenv/*/hg debuginstall || cat pip.log
245 checking encoding (ascii)...
245 checking encoding (ascii)...
246 checking Python executable (*) (glob)
246 checking Python executable (*) (glob)
247 checking Python version (2.*) (glob) (no-py3 !)
247 checking Python version (2.*) (glob) (no-py3 !)
248 checking Python version (3.*) (glob) (py3 !)
248 checking Python version (3.*) (glob) (py3 !)
249 checking Python lib (*)... (glob)
249 checking Python lib (*)... (glob)
250 checking Python security support (*) (glob)
250 checking Python security support (*) (glob)
251 TLS 1.2 not supported by Python install; network connections lack modern security (?)
251 TLS 1.2 not supported by Python install; network connections lack modern security (?)
252 SNI not supported by Python install; may have connectivity issues with some servers (?)
252 SNI not supported by Python install; may have connectivity issues with some servers (?)
253 checking Mercurial version (*) (glob)
253 checking Mercurial version (*) (glob)
254 checking Mercurial custom build (*) (glob)
254 checking Mercurial custom build (*) (glob)
255 checking module policy (*) (glob)
255 checking module policy (*) (glob)
256 checking installed modules (*/mercurial)... (glob)
256 checking installed modules (*/mercurial)... (glob)
257 checking registered compression engines (*) (glob)
257 checking registered compression engines (*) (glob)
258 checking available compression engines (*) (glob)
258 checking available compression engines (*) (glob)
259 checking available compression engines for wire protocol (*) (glob)
259 checking available compression engines for wire protocol (*) (glob)
260 checking "re2" regexp engine \((available|missing)\) (re)
260 checking "re2" regexp engine \((available|missing)\) (re)
261 checking templates ($TESTTMP/installenv/*/site-packages/mercurial/templates)... (glob)
261 checking templates ($TESTTMP/installenv/*/site-packages/mercurial/templates)... (glob)
262 checking default template ($TESTTMP/installenv/*/site-packages/mercurial/templates/map-cmdline.default) (glob)
262 checking default template ($TESTTMP/installenv/*/site-packages/mercurial/templates/map-cmdline.default) (glob)
263 checking commit editor... (*) (glob)
263 checking commit editor... (*) (glob)
264 checking username (test)
264 checking username (test)
265 no problems detected
265 no problems detected
266 #endif
266 #endif
@@ -1,1930 +1,1932 b''
1 This file tests the behavior of run-tests.py itself.
1 This file tests the behavior of run-tests.py itself.
2
2
3 Avoid interference from actual test env:
3 Avoid interference from actual test env:
4
4
5 $ . "$TESTDIR/helper-runtests.sh"
5 $ . "$TESTDIR/helper-runtests.sh"
6
6
7 Smoke test with install
7 Smoke test with install
8 ============
8 ============
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
9 $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
10 running 0 tests using 0 parallel processes
10 running 0 tests using 0 parallel processes
11
11
12 # Ran 0 tests, 0 skipped, 0 failed.
12 # Ran 0 tests, 0 skipped, 0 failed.
13
13
14 Define a helper to avoid the install step
14 Define a helper to avoid the install step
15 =============
15 =============
16 $ rt()
16 $ rt()
17 > {
17 > {
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@"
18 > "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@"
19 > }
19 > }
20
20
21 error paths
21 error paths
22
22
23 #if symlink
23 #if symlink
24 $ ln -s `which true` hg
24 $ ln -s `which true` hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
25 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
26 warning: --with-hg should specify an hg script
26 warning: --with-hg should specify an hg script
27 running 0 tests using 0 parallel processes
27 running 0 tests using 0 parallel processes
28
28
29 # Ran 0 tests, 0 skipped, 0 failed.
29 # Ran 0 tests, 0 skipped, 0 failed.
30 $ rm hg
30 $ rm hg
31 #endif
31 #endif
32
32
33 #if execbit
33 #if execbit
34 $ touch hg
34 $ touch hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
35 $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
36 usage: run-tests.py [options] [tests]
36 usage: run-tests.py [options] [tests]
37 run-tests.py: error: --with-hg must specify an executable hg script
37 run-tests.py: error: --with-hg must specify an executable hg script
38 [2]
38 [2]
39 $ rm hg
39 $ rm hg
40 #endif
40 #endif
41
41
42 Features for testing optional lines
42 Features for testing optional lines
43 ===================================
43 ===================================
44
44
45 $ cat > hghaveaddon.py <<EOF
45 $ cat > hghaveaddon.py <<EOF
46 > import hghave
46 > import hghave
47 > @hghave.check("custom", "custom hghave feature")
47 > @hghave.check("custom", "custom hghave feature")
48 > def has_custom():
48 > def has_custom():
49 > return True
49 > return True
50 > @hghave.check("missing", "missing hghave feature")
50 > @hghave.check("missing", "missing hghave feature")
51 > def has_missing():
51 > def has_missing():
52 > return False
52 > return False
53 > EOF
53 > EOF
54
54
55 an empty test
55 an empty test
56 =======================
56 =======================
57
57
58 $ touch test-empty.t
58 $ touch test-empty.t
59 $ rt
59 $ rt
60 running 1 tests using 1 parallel processes
60 running 1 tests using 1 parallel processes
61 .
61 .
62 # Ran 1 tests, 0 skipped, 0 failed.
62 # Ran 1 tests, 0 skipped, 0 failed.
63 $ rm test-empty.t
63 $ rm test-empty.t
64
64
65 a succesful test
65 a succesful test
66 =======================
66 =======================
67
67
68 $ cat > test-success.t << EOF
68 $ cat > test-success.t << EOF
69 > $ echo babar
69 > $ echo babar
70 > babar
70 > babar
71 > $ echo xyzzy
71 > $ echo xyzzy
72 > dont_print (?)
72 > dont_print (?)
73 > nothing[42]line (re) (?)
73 > nothing[42]line (re) (?)
74 > never*happens (glob) (?)
74 > never*happens (glob) (?)
75 > more_nothing (?)
75 > more_nothing (?)
76 > xyzzy
76 > xyzzy
77 > nor this (?)
77 > nor this (?)
78 > $ printf 'abc\ndef\nxyz\n'
78 > $ printf 'abc\ndef\nxyz\n'
79 > 123 (?)
79 > 123 (?)
80 > abc
80 > abc
81 > def (?)
81 > def (?)
82 > 456 (?)
82 > 456 (?)
83 > xyz
83 > xyz
84 > $ printf 'zyx\nwvu\ntsr\n'
84 > $ printf 'zyx\nwvu\ntsr\n'
85 > abc (?)
85 > abc (?)
86 > zyx (custom !)
86 > zyx (custom !)
87 > wvu
87 > wvu
88 > no_print (no-custom !)
88 > no_print (no-custom !)
89 > tsr (no-missing !)
89 > tsr (no-missing !)
90 > missing (missing !)
90 > missing (missing !)
91 > EOF
91 > EOF
92
92
93 $ rt
93 $ rt
94 running 1 tests using 1 parallel processes
94 running 1 tests using 1 parallel processes
95 .
95 .
96 # Ran 1 tests, 0 skipped, 0 failed.
96 # Ran 1 tests, 0 skipped, 0 failed.
97
97
98 failing test
98 failing test
99 ==================
99 ==================
100
100
101 test churn with globs
101 test churn with globs
102 $ cat > test-failure.t <<EOF
102 $ cat > test-failure.t <<EOF
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
103 > $ echo "bar-baz"; echo "bar-bad"; echo foo
104 > bar*bad (glob)
104 > bar*bad (glob)
105 > bar*baz (glob)
105 > bar*baz (glob)
106 > | fo (re)
106 > | fo (re)
107 > EOF
107 > EOF
108 $ rt test-failure.t
108 $ rt test-failure.t
109 running 1 tests using 1 parallel processes
109 running 1 tests using 1 parallel processes
110
110
111 --- $TESTTMP/test-failure.t
111 --- $TESTTMP/test-failure.t
112 +++ $TESTTMP/test-failure.t.err
112 +++ $TESTTMP/test-failure.t.err
113 @@ -1,4 +1,4 @@
113 @@ -1,4 +1,4 @@
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
114 $ echo "bar-baz"; echo "bar-bad"; echo foo
115 + bar*baz (glob)
115 + bar*baz (glob)
116 bar*bad (glob)
116 bar*bad (glob)
117 - bar*baz (glob)
117 - bar*baz (glob)
118 - | fo (re)
118 - | fo (re)
119 + foo
119 + foo
120
120
121 ERROR: test-failure.t output changed
121 ERROR: test-failure.t output changed
122 !
122 !
123 Failed test-failure.t: output changed
123 Failed test-failure.t: output changed
124 # Ran 1 tests, 0 skipped, 1 failed.
124 # Ran 1 tests, 0 skipped, 1 failed.
125 python hash seed: * (glob)
125 python hash seed: * (glob)
126 [1]
126 [1]
127
127
128 test how multiple globs gets matched with lines in output
128 test how multiple globs gets matched with lines in output
129 $ cat > test-failure-globs.t <<EOF
129 $ cat > test-failure-globs.t <<EOF
130 > $ echo "context"; echo "context"; \
130 > $ echo "context"; echo "context"; \
131 > echo "key: 1"; echo "value: not a"; \
131 > echo "key: 1"; echo "value: not a"; \
132 > echo "key: 2"; echo "value: not b"; \
132 > echo "key: 2"; echo "value: not b"; \
133 > echo "key: 3"; echo "value: c"; \
133 > echo "key: 3"; echo "value: c"; \
134 > echo "key: 4"; echo "value: d"
134 > echo "key: 4"; echo "value: d"
135 > context
135 > context
136 > context
136 > context
137 > key: 1
137 > key: 1
138 > value: a
138 > value: a
139 > key: 2
139 > key: 2
140 > value: b
140 > value: b
141 > key: 3
141 > key: 3
142 > value: * (glob)
142 > value: * (glob)
143 > key: 4
143 > key: 4
144 > value: * (glob)
144 > value: * (glob)
145 > EOF
145 > EOF
146 $ rt test-failure-globs.t
146 $ rt test-failure-globs.t
147 running 1 tests using 1 parallel processes
147 running 1 tests using 1 parallel processes
148
148
149 --- $TESTTMP/test-failure-globs.t
149 --- $TESTTMP/test-failure-globs.t
150 +++ $TESTTMP/test-failure-globs.t.err
150 +++ $TESTTMP/test-failure-globs.t.err
151 @@ -2,9 +2,9 @@
151 @@ -2,9 +2,9 @@
152 context
152 context
153 context
153 context
154 key: 1
154 key: 1
155 - value: a
155 - value: a
156 + value: not a
156 + value: not a
157 key: 2
157 key: 2
158 - value: b
158 - value: b
159 + value: not b
159 + value: not b
160 key: 3
160 key: 3
161 value: * (glob)
161 value: * (glob)
162 key: 4
162 key: 4
163
163
164 ERROR: test-failure-globs.t output changed
164 ERROR: test-failure-globs.t output changed
165 !
165 !
166 Failed test-failure-globs.t: output changed
166 Failed test-failure-globs.t: output changed
167 # Ran 1 tests, 0 skipped, 1 failed.
167 # Ran 1 tests, 0 skipped, 1 failed.
168 python hash seed: * (glob)
168 python hash seed: * (glob)
169 [1]
169 [1]
170 $ rm test-failure-globs.t
170 $ rm test-failure-globs.t
171
171
172 test diff colorisation
172 test diff colorisation
173
173
174 #if no-windows pygments
174 #if no-windows pygments
175 $ rt test-failure.t --color always
175 $ rt test-failure.t --color always
176 running 1 tests using 1 parallel processes
176 running 1 tests using 1 parallel processes
177
177
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
178 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
179 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
180 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
181 $ echo "bar-baz"; echo "bar-bad"; echo foo
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
182 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
183 bar*bad (glob)
183 bar*bad (glob)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
184 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
185 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
186 \x1b[38;5;34m+ foo\x1b[39m (esc)
187
187
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
188 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
189 !
189 !
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
190 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
191 # Ran 1 tests, 0 skipped, 1 failed.
191 # Ran 1 tests, 0 skipped, 1 failed.
192 python hash seed: * (glob)
192 python hash seed: * (glob)
193 [1]
193 [1]
194
194
195 $ rt test-failure.t 2> tmp.log
195 $ rt test-failure.t 2> tmp.log
196 running 1 tests using 1 parallel processes
196 running 1 tests using 1 parallel processes
197 [1]
197 [1]
198 $ cat tmp.log
198 $ cat tmp.log
199
199
200 --- $TESTTMP/test-failure.t
200 --- $TESTTMP/test-failure.t
201 +++ $TESTTMP/test-failure.t.err
201 +++ $TESTTMP/test-failure.t.err
202 @@ -1,4 +1,4 @@
202 @@ -1,4 +1,4 @@
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
203 $ echo "bar-baz"; echo "bar-bad"; echo foo
204 + bar*baz (glob)
204 + bar*baz (glob)
205 bar*bad (glob)
205 bar*bad (glob)
206 - bar*baz (glob)
206 - bar*baz (glob)
207 - | fo (re)
207 - | fo (re)
208 + foo
208 + foo
209
209
210 ERROR: test-failure.t output changed
210 ERROR: test-failure.t output changed
211 !
211 !
212 Failed test-failure.t: output changed
212 Failed test-failure.t: output changed
213 # Ran 1 tests, 0 skipped, 1 failed.
213 # Ran 1 tests, 0 skipped, 1 failed.
214 python hash seed: * (glob)
214 python hash seed: * (glob)
215 #endif
215 #endif
216
216
217 $ cat > test-failure.t << EOF
217 $ cat > test-failure.t << EOF
218 > $ true
218 > $ true
219 > should go away (true !)
219 > should go away (true !)
220 > $ true
220 > $ true
221 > should stay (false !)
221 > should stay (false !)
222 >
222 >
223 > Should remove first line, not second or third
223 > Should remove first line, not second or third
224 > $ echo 'testing'
224 > $ echo 'testing'
225 > baz*foo (glob) (true !)
225 > baz*foo (glob) (true !)
226 > foobar*foo (glob) (false !)
226 > foobar*foo (glob) (false !)
227 > te*ting (glob) (true !)
227 > te*ting (glob) (true !)
228 >
228 >
229 > Should keep first two lines, remove third and last
229 > Should keep first two lines, remove third and last
230 > $ echo 'testing'
230 > $ echo 'testing'
231 > test.ng (re) (true !)
231 > test.ng (re) (true !)
232 > foo.ar (re) (false !)
232 > foo.ar (re) (false !)
233 > b.r (re) (true !)
233 > b.r (re) (true !)
234 > missing (?)
234 > missing (?)
235 > awol (true !)
235 > awol (true !)
236 >
236 >
237 > The "missing" line should stay, even though awol is dropped
237 > The "missing" line should stay, even though awol is dropped
238 > $ echo 'testing'
238 > $ echo 'testing'
239 > test.ng (re) (true !)
239 > test.ng (re) (true !)
240 > foo.ar (?)
240 > foo.ar (?)
241 > awol
241 > awol
242 > missing (?)
242 > missing (?)
243 > EOF
243 > EOF
244 $ rt test-failure.t
244 $ rt test-failure.t
245 running 1 tests using 1 parallel processes
245 running 1 tests using 1 parallel processes
246
246
247 --- $TESTTMP/test-failure.t
247 --- $TESTTMP/test-failure.t
248 +++ $TESTTMP/test-failure.t.err
248 +++ $TESTTMP/test-failure.t.err
249 @@ -1,11 +1,9 @@
249 @@ -1,11 +1,9 @@
250 $ true
250 $ true
251 - should go away (true !)
251 - should go away (true !)
252 $ true
252 $ true
253 should stay (false !)
253 should stay (false !)
254
254
255 Should remove first line, not second or third
255 Should remove first line, not second or third
256 $ echo 'testing'
256 $ echo 'testing'
257 - baz*foo (glob) (true !)
257 - baz*foo (glob) (true !)
258 foobar*foo (glob) (false !)
258 foobar*foo (glob) (false !)
259 te*ting (glob) (true !)
259 te*ting (glob) (true !)
260
260
261 foo.ar (re) (false !)
261 foo.ar (re) (false !)
262 missing (?)
262 missing (?)
263 @@ -13,13 +11,10 @@
263 @@ -13,13 +11,10 @@
264 $ echo 'testing'
264 $ echo 'testing'
265 test.ng (re) (true !)
265 test.ng (re) (true !)
266 foo.ar (re) (false !)
266 foo.ar (re) (false !)
267 - b.r (re) (true !)
267 - b.r (re) (true !)
268 missing (?)
268 missing (?)
269 - awol (true !)
269 - awol (true !)
270
270
271 The "missing" line should stay, even though awol is dropped
271 The "missing" line should stay, even though awol is dropped
272 $ echo 'testing'
272 $ echo 'testing'
273 test.ng (re) (true !)
273 test.ng (re) (true !)
274 foo.ar (?)
274 foo.ar (?)
275 - awol
275 - awol
276 missing (?)
276 missing (?)
277
277
278 ERROR: test-failure.t output changed
278 ERROR: test-failure.t output changed
279 !
279 !
280 Failed test-failure.t: output changed
280 Failed test-failure.t: output changed
281 # Ran 1 tests, 0 skipped, 1 failed.
281 # Ran 1 tests, 0 skipped, 1 failed.
282 python hash seed: * (glob)
282 python hash seed: * (glob)
283 [1]
283 [1]
284
284
285 basic failing test
285 basic failing test
286 $ cat > test-failure.t << EOF
286 $ cat > test-failure.t << EOF
287 > $ echo babar
287 > $ echo babar
288 > rataxes
288 > rataxes
289 > This is a noop statement so that
289 > This is a noop statement so that
290 > this test is still more bytes than success.
290 > this test is still more bytes than success.
291 > pad pad pad pad............................................................
291 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
292 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
293 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
294 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
295 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
296 > pad pad pad pad............................................................
297 > EOF
297 > EOF
298
298
299 >>> fh = open('test-failure-unicode.t', 'wb')
299 >>> fh = open('test-failure-unicode.t', 'wb')
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
300 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
301 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
302
302
303 $ rt
303 $ rt
304 running 3 tests using 1 parallel processes
304 running 3 tests using 1 parallel processes
305
305
306 --- $TESTTMP/test-failure.t
306 --- $TESTTMP/test-failure.t
307 +++ $TESTTMP/test-failure.t.err
307 +++ $TESTTMP/test-failure.t.err
308 @@ -1,5 +1,5 @@
308 @@ -1,5 +1,5 @@
309 $ echo babar
309 $ echo babar
310 - rataxes
310 - rataxes
311 + babar
311 + babar
312 This is a noop statement so that
312 This is a noop statement so that
313 this test is still more bytes than success.
313 this test is still more bytes than success.
314 pad pad pad pad............................................................
314 pad pad pad pad............................................................
315
315
316 ERROR: test-failure.t output changed
316 ERROR: test-failure.t output changed
317 !.
317 !.
318 --- $TESTTMP/test-failure-unicode.t
318 --- $TESTTMP/test-failure-unicode.t
319 +++ $TESTTMP/test-failure-unicode.t.err
319 +++ $TESTTMP/test-failure-unicode.t.err
320 @@ -1,2 +1,2 @@
320 @@ -1,2 +1,2 @@
321 $ echo babar\xce\xb1 (esc)
321 $ echo babar\xce\xb1 (esc)
322 - l\xce\xb5\xce\xb5t (esc)
322 - l\xce\xb5\xce\xb5t (esc)
323 + babar\xce\xb1 (esc)
323 + babar\xce\xb1 (esc)
324
324
325 ERROR: test-failure-unicode.t output changed
325 ERROR: test-failure-unicode.t output changed
326 !
326 !
327 Failed test-failure.t: output changed
327 Failed test-failure.t: output changed
328 Failed test-failure-unicode.t: output changed
328 Failed test-failure-unicode.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.t: output changed
359 Failed test-failure.t: output changed
360 Failed test-failure-unicode.t: output changed
360 Failed test-failure-unicode.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.t: output changed
396 Failed test-failure.t: output changed
397 Failed test-failure-unicode.t: output changed
397 Failed test-failure-unicode.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">
406 <failure message="output changed" type="output-mismatch">
407 <![CDATA[--- $TESTTMP/test-failure-unicode.t
407 <![CDATA[--- $TESTTMP/test-failure-unicode.t
408 +++ $TESTTMP/test-failure-unicode.t.err
408 +++ $TESTTMP/test-failure-unicode.t.err
409 @@ -1,2 +1,2 @@
409 @@ -1,2 +1,2 @@
410 $ echo babar\xce\xb1 (esc)
410 $ echo babar\xce\xb1 (esc)
411 - l\xce\xb5\xce\xb5t (esc)
411 - l\xce\xb5\xce\xb5t (esc)
412 + babar\xce\xb1 (esc)
412 + babar\xce\xb1 (esc)
413 ]]> </failure>
413 ]]> </failure>
414 </testcase>
414 </testcase>
415 <testcase name="test-failure.t" time="*"> (glob)
415 <testcase name="test-failure.t" time="*"> (glob)
416 <failure message="output changed" type="output-mismatch">
416 <failure message="output changed" type="output-mismatch">
417 <![CDATA[--- $TESTTMP/test-failure.t
417 <![CDATA[--- $TESTTMP/test-failure.t
418 +++ $TESTTMP/test-failure.t.err
418 +++ $TESTTMP/test-failure.t.err
419 @@ -1,5 +1,5 @@
419 @@ -1,5 +1,5 @@
420 $ echo babar
420 $ echo babar
421 - rataxes
421 - rataxes
422 + babar
422 + babar
423 This is a noop statement so that
423 This is a noop statement so that
424 this test is still more bytes than success.
424 this test is still more bytes than success.
425 pad pad pad pad............................................................
425 pad pad pad pad............................................................
426 ]]> </failure>
426 ]]> </failure>
427 </testcase>
427 </testcase>
428 </testsuite>
428 </testsuite>
429
429
430 $ cat .testtimes
430 $ cat .testtimes
431 test-empty.t * (glob)
431 test-empty.t * (glob)
432 test-failure-globs.t * (glob)
432 test-failure-globs.t * (glob)
433 test-failure-unicode.t * (glob)
433 test-failure-unicode.t * (glob)
434 test-failure.t * (glob)
434 test-failure.t * (glob)
435 test-success.t * (glob)
435 test-success.t * (glob)
436
436
437 $ rt --list-tests
437 $ rt --list-tests
438 test-failure-unicode.t
438 test-failure-unicode.t
439 test-failure.t
439 test-failure.t
440 test-success.t
440 test-success.t
441
441
442 $ rt --list-tests --json
442 $ rt --list-tests --json
443 test-failure-unicode.t
443 test-failure-unicode.t
444 test-failure.t
444 test-failure.t
445 test-success.t
445 test-success.t
446 $ cat report.json
446 $ cat report.json
447 testreport ={
447 testreport ={
448 "test-failure-unicode.t": {
448 "test-failure-unicode.t": {
449 "result": "success"
449 "result": "success"
450 },
450 },
451 "test-failure.t": {
451 "test-failure.t": {
452 "result": "success"
452 "result": "success"
453 },
453 },
454 "test-success.t": {
454 "test-success.t": {
455 "result": "success"
455 "result": "success"
456 }
456 }
457 } (no-eol)
457 } (no-eol)
458
458
459 $ rt --list-tests --xunit=xunit.xml
459 $ rt --list-tests --xunit=xunit.xml
460 test-failure-unicode.t
460 test-failure-unicode.t
461 test-failure.t
461 test-failure.t
462 test-success.t
462 test-success.t
463 $ cat xunit.xml
463 $ cat xunit.xml
464 <?xml version="1.0" encoding="utf-8"?>
464 <?xml version="1.0" encoding="utf-8"?>
465 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
465 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
466 <testcase name="test-failure-unicode.t"/>
466 <testcase name="test-failure-unicode.t"/>
467 <testcase name="test-failure.t"/>
467 <testcase name="test-failure.t"/>
468 <testcase name="test-success.t"/>
468 <testcase name="test-success.t"/>
469 </testsuite>
469 </testsuite>
470
470
471 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
471 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
472 test-failure-unicode.t
472 test-failure-unicode.t
473 test-failure.t
473 test-failure.t
474 $ cat output/report.json
474 $ cat output/report.json
475 testreport ={
475 testreport ={
476 "test-failure-unicode.t": {
476 "test-failure-unicode.t": {
477 "result": "success"
477 "result": "success"
478 },
478 },
479 "test-failure.t": {
479 "test-failure.t": {
480 "result": "success"
480 "result": "success"
481 }
481 }
482 } (no-eol)
482 } (no-eol)
483 $ cat xunit.xml
483 $ cat xunit.xml
484 <?xml version="1.0" encoding="utf-8"?>
484 <?xml version="1.0" encoding="utf-8"?>
485 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
485 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
486 <testcase name="test-failure-unicode.t"/>
486 <testcase name="test-failure-unicode.t"/>
487 <testcase name="test-failure.t"/>
487 <testcase name="test-failure.t"/>
488 </testsuite>
488 </testsuite>
489
489
490 $ rm test-failure-unicode.t
490 $ rm test-failure-unicode.t
491
491
492 test for --retest
492 test for --retest
493 ====================
493 ====================
494
494
495 $ rt --retest
495 $ rt --retest
496 running 2 tests using 1 parallel processes
496 running 2 tests using 1 parallel processes
497
497
498 --- $TESTTMP/test-failure.t
498 --- $TESTTMP/test-failure.t
499 +++ $TESTTMP/test-failure.t.err
499 +++ $TESTTMP/test-failure.t.err
500 @@ -1,5 +1,5 @@
500 @@ -1,5 +1,5 @@
501 $ echo babar
501 $ echo babar
502 - rataxes
502 - rataxes
503 + babar
503 + babar
504 This is a noop statement so that
504 This is a noop statement so that
505 this test is still more bytes than success.
505 this test is still more bytes than success.
506 pad pad pad pad............................................................
506 pad pad pad pad............................................................
507
507
508 ERROR: test-failure.t output changed
508 ERROR: test-failure.t output changed
509 !
509 !
510 Failed test-failure.t: output changed
510 Failed test-failure.t: output changed
511 # Ran 2 tests, 1 skipped, 1 failed.
511 # Ran 2 tests, 1 skipped, 1 failed.
512 python hash seed: * (glob)
512 python hash seed: * (glob)
513 [1]
513 [1]
514
514
515 --retest works with --outputdir
515 --retest works with --outputdir
516 $ rm -r output
516 $ rm -r output
517 $ mkdir output
517 $ mkdir output
518 $ mv test-failure.t.err output
518 $ mv test-failure.t.err output
519 $ rt --retest --outputdir output
519 $ rt --retest --outputdir output
520 running 2 tests using 1 parallel processes
520 running 2 tests using 1 parallel processes
521
521
522 --- $TESTTMP/test-failure.t
522 --- $TESTTMP/test-failure.t
523 +++ $TESTTMP/output/test-failure.t.err
523 +++ $TESTTMP/output/test-failure.t.err
524 @@ -1,5 +1,5 @@
524 @@ -1,5 +1,5 @@
525 $ echo babar
525 $ echo babar
526 - rataxes
526 - rataxes
527 + babar
527 + babar
528 This is a noop statement so that
528 This is a noop statement so that
529 this test is still more bytes than success.
529 this test is still more bytes than success.
530 pad pad pad pad............................................................
530 pad pad pad pad............................................................
531
531
532 ERROR: test-failure.t output changed
532 ERROR: test-failure.t output changed
533 !
533 !
534 Failed test-failure.t: output changed
534 Failed test-failure.t: output changed
535 # Ran 2 tests, 1 skipped, 1 failed.
535 # Ran 2 tests, 1 skipped, 1 failed.
536 python hash seed: * (glob)
536 python hash seed: * (glob)
537 [1]
537 [1]
538
538
539 Selecting Tests To Run
539 Selecting Tests To Run
540 ======================
540 ======================
541
541
542 successful
542 successful
543
543
544 $ rt test-success.t
544 $ rt test-success.t
545 running 1 tests using 1 parallel processes
545 running 1 tests using 1 parallel processes
546 .
546 .
547 # Ran 1 tests, 0 skipped, 0 failed.
547 # Ran 1 tests, 0 skipped, 0 failed.
548
548
549 success w/ keyword
549 success w/ keyword
550 $ rt -k xyzzy
550 $ rt -k xyzzy
551 running 2 tests using 1 parallel processes
551 running 2 tests using 1 parallel processes
552 .
552 .
553 # Ran 2 tests, 1 skipped, 0 failed.
553 # Ran 2 tests, 1 skipped, 0 failed.
554
554
555 failed
555 failed
556
556
557 $ rt test-failure.t
557 $ rt test-failure.t
558 running 1 tests using 1 parallel processes
558 running 1 tests using 1 parallel processes
559
559
560 --- $TESTTMP/test-failure.t
560 --- $TESTTMP/test-failure.t
561 +++ $TESTTMP/test-failure.t.err
561 +++ $TESTTMP/test-failure.t.err
562 @@ -1,5 +1,5 @@
562 @@ -1,5 +1,5 @@
563 $ echo babar
563 $ echo babar
564 - rataxes
564 - rataxes
565 + babar
565 + babar
566 This is a noop statement so that
566 This is a noop statement so that
567 this test is still more bytes than success.
567 this test is still more bytes than success.
568 pad pad pad pad............................................................
568 pad pad pad pad............................................................
569
569
570 ERROR: test-failure.t output changed
570 ERROR: test-failure.t output changed
571 !
571 !
572 Failed test-failure.t: output changed
572 Failed test-failure.t: output changed
573 # Ran 1 tests, 0 skipped, 1 failed.
573 # Ran 1 tests, 0 skipped, 1 failed.
574 python hash seed: * (glob)
574 python hash seed: * (glob)
575 [1]
575 [1]
576
576
577 failure w/ keyword
577 failure w/ keyword
578 $ rt -k rataxes
578 $ rt -k rataxes
579 running 2 tests using 1 parallel processes
579 running 2 tests using 1 parallel processes
580
580
581 --- $TESTTMP/test-failure.t
581 --- $TESTTMP/test-failure.t
582 +++ $TESTTMP/test-failure.t.err
582 +++ $TESTTMP/test-failure.t.err
583 @@ -1,5 +1,5 @@
583 @@ -1,5 +1,5 @@
584 $ echo babar
584 $ echo babar
585 - rataxes
585 - rataxes
586 + babar
586 + babar
587 This is a noop statement so that
587 This is a noop statement so that
588 this test is still more bytes than success.
588 this test is still more bytes than success.
589 pad pad pad pad............................................................
589 pad pad pad pad............................................................
590
590
591 ERROR: test-failure.t output changed
591 ERROR: test-failure.t output changed
592 !
592 !
593 Failed test-failure.t: output changed
593 Failed test-failure.t: output changed
594 # Ran 2 tests, 1 skipped, 1 failed.
594 # Ran 2 tests, 1 skipped, 1 failed.
595 python hash seed: * (glob)
595 python hash seed: * (glob)
596 [1]
596 [1]
597
597
598 Verify that when a process fails to start we show a useful message
598 Verify that when a process fails to start we show a useful message
599 ==================================================================
599 ==================================================================
600
600
601 $ cat > test-serve-fail.t <<EOF
601 $ cat > test-serve-fail.t <<EOF
602 > $ echo 'abort: child process failed to start blah'
602 > $ echo 'abort: child process failed to start blah'
603 > EOF
603 > EOF
604 $ rt test-serve-fail.t
604 $ rt test-serve-fail.t
605 running 1 tests using 1 parallel processes
605 running 1 tests using 1 parallel processes
606
606
607 --- $TESTTMP/test-serve-fail.t
607 --- $TESTTMP/test-serve-fail.t
608 +++ $TESTTMP/test-serve-fail.t.err
608 +++ $TESTTMP/test-serve-fail.t.err
609 @@ -1* +1,2 @@ (glob)
609 @@ -1* +1,2 @@ (glob)
610 $ echo 'abort: child process failed to start blah'
610 $ echo 'abort: child process failed to start blah'
611 + abort: child process failed to start blah
611 + abort: child process failed to start blah
612
612
613 ERROR: test-serve-fail.t output changed
613 ERROR: test-serve-fail.t output changed
614 !
614 !
615 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
615 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
616 # Ran 1 tests, 0 skipped, 1 failed.
616 # Ran 1 tests, 0 skipped, 1 failed.
617 python hash seed: * (glob)
617 python hash seed: * (glob)
618 [1]
618 [1]
619 $ rm test-serve-fail.t
619 $ rm test-serve-fail.t
620
620
621 Verify that we can try other ports
621 Verify that we can try other ports
622 ===================================
622 ===================================
623
623
624 Extensions aren't inherited by the invoked run-tests.py. An extension
624 Extensions aren't inherited by the invoked run-tests.py. An extension
625 introducing a repository requirement could cause this to fail. So we force
625 introducing a repository requirement could cause this to fail. So we force
626 HGRCPATH to get a clean environment.
626 HGRCPATH to get a clean environment.
627
627
628 $ HGRCPATH= hg init inuse
628 $ HGRCPATH= hg init inuse
629 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
629 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
630 $ cat blocks.pid >> $DAEMON_PIDS
630 $ cat blocks.pid >> $DAEMON_PIDS
631 $ cat > test-serve-inuse.t <<EOF
631 $ cat > test-serve-inuse.t <<EOF
632 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
632 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
633 > $ cat hg.pid >> \$DAEMON_PIDS
633 > $ cat hg.pid >> \$DAEMON_PIDS
634 > EOF
634 > EOF
635 $ rt test-serve-inuse.t
635 $ rt test-serve-inuse.t
636 running 1 tests using 1 parallel processes
636 running 1 tests using 1 parallel processes
637 .
637 .
638 # Ran 1 tests, 0 skipped, 0 failed.
638 # Ran 1 tests, 0 skipped, 0 failed.
639 $ rm test-serve-inuse.t
639 $ rm test-serve-inuse.t
640 $ killdaemons.py $DAEMON_PIDS
640 $ killdaemons.py $DAEMON_PIDS
641
641
642 Running In Debug Mode
642 Running In Debug Mode
643 ======================
643 ======================
644
644
645 $ rt --debug 2>&1 | grep -v pwd
645 $ rt --debug 2>&1 | grep -v pwd
646 running 2 tests using 1 parallel processes
646 running 2 tests using 1 parallel processes
647 + alias hg=hg.exe (windows !)
647 + echo *SALT* 0 0 (glob)
648 + echo *SALT* 0 0 (glob)
648 *SALT* 0 0 (glob)
649 *SALT* 0 0 (glob)
649 + echo babar
650 + echo babar
650 babar
651 babar
651 + echo *SALT* 10 0 (glob)
652 + echo *SALT* 10 0 (glob)
652 *SALT* 10 0 (glob)
653 *SALT* 10 0 (glob)
654 .+ alias hg=hg.exe (windows !)
653 *+ echo *SALT* 0 0 (glob)
655 *+ echo *SALT* 0 0 (glob)
654 *SALT* 0 0 (glob)
656 *SALT* 0 0 (glob)
655 + echo babar
657 + echo babar
656 babar
658 babar
657 + echo *SALT* 2 0 (glob)
659 + echo *SALT* 2 0 (glob)
658 *SALT* 2 0 (glob)
660 *SALT* 2 0 (glob)
659 + echo xyzzy
661 + echo xyzzy
660 xyzzy
662 xyzzy
661 + echo *SALT* 9 0 (glob)
663 + echo *SALT* 9 0 (glob)
662 *SALT* 9 0 (glob)
664 *SALT* 9 0 (glob)
663 + printf *abc\ndef\nxyz\n* (glob)
665 + printf *abc\ndef\nxyz\n* (glob)
664 abc
666 abc
665 def
667 def
666 xyz
668 xyz
667 + echo *SALT* 15 0 (glob)
669 + echo *SALT* 15 0 (glob)
668 *SALT* 15 0 (glob)
670 *SALT* 15 0 (glob)
669 + printf *zyx\nwvu\ntsr\n* (glob)
671 + printf *zyx\nwvu\ntsr\n* (glob)
670 zyx
672 zyx
671 wvu
673 wvu
672 tsr
674 tsr
673 + echo *SALT* 22 0 (glob)
675 + echo *SALT* 22 0 (glob)
674 *SALT* 22 0 (glob)
676 *SALT* 22 0 (glob)
675 .
677 .
676 # Ran 2 tests, 0 skipped, 0 failed.
678 # Ran 2 tests, 0 skipped, 0 failed.
677
679
678 Parallel runs
680 Parallel runs
679 ==============
681 ==============
680
682
681 (duplicate the failing test to get predictable output)
683 (duplicate the failing test to get predictable output)
682 $ cp test-failure.t test-failure-copy.t
684 $ cp test-failure.t test-failure-copy.t
683
685
684 $ rt --jobs 2 test-failure*.t -n
686 $ rt --jobs 2 test-failure*.t -n
685 running 2 tests using 2 parallel processes
687 running 2 tests using 2 parallel processes
686 !!
688 !!
687 Failed test-failure*.t: output changed (glob)
689 Failed test-failure*.t: output changed (glob)
688 Failed test-failure*.t: output changed (glob)
690 Failed test-failure*.t: output changed (glob)
689 # Ran 2 tests, 0 skipped, 2 failed.
691 # Ran 2 tests, 0 skipped, 2 failed.
690 python hash seed: * (glob)
692 python hash seed: * (glob)
691 [1]
693 [1]
692
694
693 failures in parallel with --first should only print one failure
695 failures in parallel with --first should only print one failure
694 $ rt --jobs 2 --first test-failure*.t
696 $ rt --jobs 2 --first test-failure*.t
695 running 2 tests using 2 parallel processes
697 running 2 tests using 2 parallel processes
696
698
697 --- $TESTTMP/test-failure*.t (glob)
699 --- $TESTTMP/test-failure*.t (glob)
698 +++ $TESTTMP/test-failure*.t.err (glob)
700 +++ $TESTTMP/test-failure*.t.err (glob)
699 @@ -1,5 +1,5 @@
701 @@ -1,5 +1,5 @@
700 $ echo babar
702 $ echo babar
701 - rataxes
703 - rataxes
702 + babar
704 + babar
703 This is a noop statement so that
705 This is a noop statement so that
704 this test is still more bytes than success.
706 this test is still more bytes than success.
705 pad pad pad pad............................................................
707 pad pad pad pad............................................................
706
708
707 Failed test-failure*.t: output changed (glob)
709 Failed test-failure*.t: output changed (glob)
708 Failed test-failure*.t: output changed (glob)
710 Failed test-failure*.t: output changed (glob)
709 # Ran 2 tests, 0 skipped, 2 failed.
711 # Ran 2 tests, 0 skipped, 2 failed.
710 python hash seed: * (glob)
712 python hash seed: * (glob)
711 [1]
713 [1]
712
714
713
715
714 (delete the duplicated test file)
716 (delete the duplicated test file)
715 $ rm test-failure-copy.t
717 $ rm test-failure-copy.t
716
718
717
719
718 Interactive run
720 Interactive run
719 ===============
721 ===============
720
722
721 (backup the failing test)
723 (backup the failing test)
722 $ cp test-failure.t backup
724 $ cp test-failure.t backup
723
725
724 Refuse the fix
726 Refuse the fix
725
727
726 $ echo 'n' | rt -i
728 $ echo 'n' | rt -i
727 running 2 tests using 1 parallel processes
729 running 2 tests using 1 parallel processes
728
730
729 --- $TESTTMP/test-failure.t
731 --- $TESTTMP/test-failure.t
730 +++ $TESTTMP/test-failure.t.err
732 +++ $TESTTMP/test-failure.t.err
731 @@ -1,5 +1,5 @@
733 @@ -1,5 +1,5 @@
732 $ echo babar
734 $ echo babar
733 - rataxes
735 - rataxes
734 + babar
736 + babar
735 This is a noop statement so that
737 This is a noop statement so that
736 this test is still more bytes than success.
738 this test is still more bytes than success.
737 pad pad pad pad............................................................
739 pad pad pad pad............................................................
738 Accept this change? [n]
740 Accept this change? [n]
739 ERROR: test-failure.t output changed
741 ERROR: test-failure.t output changed
740 !.
742 !.
741 Failed test-failure.t: output changed
743 Failed test-failure.t: output changed
742 # Ran 2 tests, 0 skipped, 1 failed.
744 # Ran 2 tests, 0 skipped, 1 failed.
743 python hash seed: * (glob)
745 python hash seed: * (glob)
744 [1]
746 [1]
745
747
746 $ cat test-failure.t
748 $ cat test-failure.t
747 $ echo babar
749 $ echo babar
748 rataxes
750 rataxes
749 This is a noop statement so that
751 This is a noop statement so that
750 this test is still more bytes than success.
752 this test is still more bytes than success.
751 pad pad pad pad............................................................
753 pad pad pad pad............................................................
752 pad pad pad pad............................................................
754 pad pad pad pad............................................................
753 pad pad pad pad............................................................
755 pad pad pad pad............................................................
754 pad pad pad pad............................................................
756 pad pad pad pad............................................................
755 pad pad pad pad............................................................
757 pad pad pad pad............................................................
756 pad pad pad pad............................................................
758 pad pad pad pad............................................................
757
759
758 Interactive with custom view
760 Interactive with custom view
759
761
760 $ echo 'n' | rt -i --view echo
762 $ echo 'n' | rt -i --view echo
761 running 2 tests using 1 parallel processes
763 running 2 tests using 1 parallel processes
762 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
764 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
763 Accept this change? [n]* (glob)
765 Accept this change? [n]* (glob)
764 ERROR: test-failure.t output changed
766 ERROR: test-failure.t output changed
765 !.
767 !.
766 Failed test-failure.t: output changed
768 Failed test-failure.t: output changed
767 # Ran 2 tests, 0 skipped, 1 failed.
769 # Ran 2 tests, 0 skipped, 1 failed.
768 python hash seed: * (glob)
770 python hash seed: * (glob)
769 [1]
771 [1]
770
772
771 View the fix
773 View the fix
772
774
773 $ echo 'y' | rt --view echo
775 $ echo 'y' | rt --view echo
774 running 2 tests using 1 parallel processes
776 running 2 tests using 1 parallel processes
775 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
777 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
776
778
777 ERROR: test-failure.t output changed
779 ERROR: test-failure.t output changed
778 !.
780 !.
779 Failed test-failure.t: output changed
781 Failed test-failure.t: output changed
780 # Ran 2 tests, 0 skipped, 1 failed.
782 # Ran 2 tests, 0 skipped, 1 failed.
781 python hash seed: * (glob)
783 python hash seed: * (glob)
782 [1]
784 [1]
783
785
784 Accept the fix
786 Accept the fix
785
787
786 $ cat >> test-failure.t <<EOF
788 $ cat >> test-failure.t <<EOF
787 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
789 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
788 > saved backup bundle to \$TESTTMP/foo.hg
790 > saved backup bundle to \$TESTTMP/foo.hg
789 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
791 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
790 > saved backup bundle to $TESTTMP\\foo.hg
792 > saved backup bundle to $TESTTMP\\foo.hg
791 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
793 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
792 > saved backup bundle to \$TESTTMP/*.hg (glob)
794 > saved backup bundle to \$TESTTMP/*.hg (glob)
793 > EOF
795 > EOF
794 $ echo 'y' | rt -i 2>&1
796 $ echo 'y' | rt -i 2>&1
795 running 2 tests using 1 parallel processes
797 running 2 tests using 1 parallel processes
796
798
797 --- $TESTTMP/test-failure.t
799 --- $TESTTMP/test-failure.t
798 +++ $TESTTMP/test-failure.t.err
800 +++ $TESTTMP/test-failure.t.err
799 @@ -1,5 +1,5 @@
801 @@ -1,5 +1,5 @@
800 $ echo babar
802 $ echo babar
801 - rataxes
803 - rataxes
802 + babar
804 + babar
803 This is a noop statement so that
805 This is a noop statement so that
804 this test is still more bytes than success.
806 this test is still more bytes than success.
805 pad pad pad pad............................................................
807 pad pad pad pad............................................................
806 @@ -11,6 +11,6 @@
808 @@ -11,6 +11,6 @@
807 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
809 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
808 saved backup bundle to $TESTTMP/foo.hg
810 saved backup bundle to $TESTTMP/foo.hg
809 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
811 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
810 - saved backup bundle to $TESTTMP\foo.hg
812 - saved backup bundle to $TESTTMP\foo.hg
811 + saved backup bundle to $TESTTMP/foo.hg
813 + saved backup bundle to $TESTTMP/foo.hg
812 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
814 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
813 saved backup bundle to $TESTTMP/*.hg (glob)
815 saved backup bundle to $TESTTMP/*.hg (glob)
814 Accept this change? [n] ..
816 Accept this change? [n] ..
815 # Ran 2 tests, 0 skipped, 0 failed.
817 # Ran 2 tests, 0 skipped, 0 failed.
816
818
817 $ sed -e 's,(glob)$,&<,g' test-failure.t
819 $ sed -e 's,(glob)$,&<,g' test-failure.t
818 $ echo babar
820 $ echo babar
819 babar
821 babar
820 This is a noop statement so that
822 This is a noop statement so that
821 this test is still more bytes than success.
823 this test is still more bytes than success.
822 pad pad pad pad............................................................
824 pad pad pad pad............................................................
823 pad pad pad pad............................................................
825 pad pad pad pad............................................................
824 pad pad pad pad............................................................
826 pad pad pad pad............................................................
825 pad pad pad pad............................................................
827 pad pad pad pad............................................................
826 pad pad pad pad............................................................
828 pad pad pad pad............................................................
827 pad pad pad pad............................................................
829 pad pad pad pad............................................................
828 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
830 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
829 saved backup bundle to $TESTTMP/foo.hg
831 saved backup bundle to $TESTTMP/foo.hg
830 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
832 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
831 saved backup bundle to $TESTTMP/foo.hg
833 saved backup bundle to $TESTTMP/foo.hg
832 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
834 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
833 saved backup bundle to $TESTTMP/*.hg (glob)<
835 saved backup bundle to $TESTTMP/*.hg (glob)<
834
836
835 Race condition - test file was modified when test is running
837 Race condition - test file was modified when test is running
836
838
837 $ TESTRACEDIR=`pwd`
839 $ TESTRACEDIR=`pwd`
838 $ export TESTRACEDIR
840 $ export TESTRACEDIR
839 $ cat > test-race.t <<EOF
841 $ cat > test-race.t <<EOF
840 > $ echo 1
842 > $ echo 1
841 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
843 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
842 > EOF
844 > EOF
843
845
844 $ rt -i test-race.t
846 $ rt -i test-race.t
845 running 1 tests using 1 parallel processes
847 running 1 tests using 1 parallel processes
846
848
847 --- $TESTTMP/test-race.t
849 --- $TESTTMP/test-race.t
848 +++ $TESTTMP/test-race.t.err
850 +++ $TESTTMP/test-race.t.err
849 @@ -1,2 +1,3 @@
851 @@ -1,2 +1,3 @@
850 $ echo 1
852 $ echo 1
851 + 1
853 + 1
852 $ echo "# a new line" >> $TESTTMP/test-race.t
854 $ echo "# a new line" >> $TESTTMP/test-race.t
853 Reference output has changed (run again to prompt changes)
855 Reference output has changed (run again to prompt changes)
854 ERROR: test-race.t output changed
856 ERROR: test-race.t output changed
855 !
857 !
856 Failed test-race.t: output changed
858 Failed test-race.t: output changed
857 # Ran 1 tests, 0 skipped, 1 failed.
859 # Ran 1 tests, 0 skipped, 1 failed.
858 python hash seed: * (glob)
860 python hash seed: * (glob)
859 [1]
861 [1]
860
862
861 $ rm test-race.t
863 $ rm test-race.t
862
864
863 When "#testcases" is used in .t files
865 When "#testcases" is used in .t files
864
866
865 $ cat >> test-cases.t <<EOF
867 $ cat >> test-cases.t <<EOF
866 > #testcases a b
868 > #testcases a b
867 > #if a
869 > #if a
868 > $ echo 1
870 > $ echo 1
869 > #endif
871 > #endif
870 > #if b
872 > #if b
871 > $ echo 2
873 > $ echo 2
872 > #endif
874 > #endif
873 > EOF
875 > EOF
874
876
875 $ cat <<EOF | rt -i test-cases.t 2>&1
877 $ cat <<EOF | rt -i test-cases.t 2>&1
876 > y
878 > y
877 > y
879 > y
878 > EOF
880 > EOF
879 running 2 tests using 1 parallel processes
881 running 2 tests using 1 parallel processes
880
882
881 --- $TESTTMP/test-cases.t
883 --- $TESTTMP/test-cases.t
882 +++ $TESTTMP/test-cases.t#a.err
884 +++ $TESTTMP/test-cases.t#a.err
883 @@ -1,6 +1,7 @@
885 @@ -1,6 +1,7 @@
884 #testcases a b
886 #testcases a b
885 #if a
887 #if a
886 $ echo 1
888 $ echo 1
887 + 1
889 + 1
888 #endif
890 #endif
889 #if b
891 #if b
890 $ echo 2
892 $ echo 2
891 Accept this change? [n] .
893 Accept this change? [n] .
892 --- $TESTTMP/test-cases.t
894 --- $TESTTMP/test-cases.t
893 +++ $TESTTMP/test-cases.t#b.err
895 +++ $TESTTMP/test-cases.t#b.err
894 @@ -5,4 +5,5 @@
896 @@ -5,4 +5,5 @@
895 #endif
897 #endif
896 #if b
898 #if b
897 $ echo 2
899 $ echo 2
898 + 2
900 + 2
899 #endif
901 #endif
900 Accept this change? [n] .
902 Accept this change? [n] .
901 # Ran 2 tests, 0 skipped, 0 failed.
903 # Ran 2 tests, 0 skipped, 0 failed.
902
904
903 $ cat test-cases.t
905 $ cat test-cases.t
904 #testcases a b
906 #testcases a b
905 #if a
907 #if a
906 $ echo 1
908 $ echo 1
907 1
909 1
908 #endif
910 #endif
909 #if b
911 #if b
910 $ echo 2
912 $ echo 2
911 2
913 2
912 #endif
914 #endif
913
915
914 $ cat >> test-cases.t <<'EOF'
916 $ cat >> test-cases.t <<'EOF'
915 > #if a
917 > #if a
916 > $ NAME=A
918 > $ NAME=A
917 > #else
919 > #else
918 > $ NAME=B
920 > $ NAME=B
919 > #endif
921 > #endif
920 > $ echo $NAME
922 > $ echo $NAME
921 > A (a !)
923 > A (a !)
922 > B (b !)
924 > B (b !)
923 > EOF
925 > EOF
924 $ rt test-cases.t
926 $ rt test-cases.t
925 running 2 tests using 1 parallel processes
927 running 2 tests using 1 parallel processes
926 ..
928 ..
927 # Ran 2 tests, 0 skipped, 0 failed.
929 # Ran 2 tests, 0 skipped, 0 failed.
928
930
929 When using multiple dimensions of "#testcases" in .t files
931 When using multiple dimensions of "#testcases" in .t files
930
932
931 $ cat > test-cases.t <<'EOF'
933 $ cat > test-cases.t <<'EOF'
932 > #testcases a b
934 > #testcases a b
933 > #testcases c d
935 > #testcases c d
934 > #if a d
936 > #if a d
935 > $ echo $TESTCASE
937 > $ echo $TESTCASE
936 > a#d
938 > a#d
937 > #endif
939 > #endif
938 > #if b c
940 > #if b c
939 > $ echo yes
941 > $ echo yes
940 > no
942 > no
941 > #endif
943 > #endif
942 > EOF
944 > EOF
943 $ rt test-cases.t
945 $ rt test-cases.t
944 running 4 tests using 1 parallel processes
946 running 4 tests using 1 parallel processes
945 ..
947 ..
946 --- $TESTTMP/test-cases.t
948 --- $TESTTMP/test-cases.t
947 +++ $TESTTMP/test-cases.t#b#c.err
949 +++ $TESTTMP/test-cases.t#b#c.err
948 @@ -6,5 +6,5 @@
950 @@ -6,5 +6,5 @@
949 #endif
951 #endif
950 #if b c
952 #if b c
951 $ echo yes
953 $ echo yes
952 - no
954 - no
953 + yes
955 + yes
954 #endif
956 #endif
955
957
956 ERROR: test-cases.t#b#c output changed
958 ERROR: test-cases.t#b#c output changed
957 !.
959 !.
958 Failed test-cases.t#b#c: output changed
960 Failed test-cases.t#b#c: output changed
959 # Ran 4 tests, 0 skipped, 1 failed.
961 # Ran 4 tests, 0 skipped, 1 failed.
960 python hash seed: * (glob)
962 python hash seed: * (glob)
961 [1]
963 [1]
962
964
963 $ rm test-cases.t#b#c.err
965 $ rm test-cases.t#b#c.err
964 $ rm test-cases.t
966 $ rm test-cases.t
965
967
966 (reinstall)
968 (reinstall)
967 $ mv backup test-failure.t
969 $ mv backup test-failure.t
968
970
969 No Diff
971 No Diff
970 ===============
972 ===============
971
973
972 $ rt --nodiff
974 $ rt --nodiff
973 running 2 tests using 1 parallel processes
975 running 2 tests using 1 parallel processes
974 !.
976 !.
975 Failed test-failure.t: output changed
977 Failed test-failure.t: output changed
976 # Ran 2 tests, 0 skipped, 1 failed.
978 # Ran 2 tests, 0 skipped, 1 failed.
977 python hash seed: * (glob)
979 python hash seed: * (glob)
978 [1]
980 [1]
979
981
980 test --tmpdir support
982 test --tmpdir support
981 $ rt --tmpdir=$TESTTMP/keep test-success.t
983 $ rt --tmpdir=$TESTTMP/keep test-success.t
982 running 1 tests using 1 parallel processes
984 running 1 tests using 1 parallel processes
983
985
984 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
986 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
985 Keeping threadtmp dir: $TESTTMP/keep/child1
987 Keeping threadtmp dir: $TESTTMP/keep/child1
986 .
988 .
987 # Ran 1 tests, 0 skipped, 0 failed.
989 # Ran 1 tests, 0 skipped, 0 failed.
988
990
989 timeouts
991 timeouts
990 ========
992 ========
991 $ cat > test-timeout.t <<EOF
993 $ cat > test-timeout.t <<EOF
992 > $ sleep 2
994 > $ sleep 2
993 > $ echo pass
995 > $ echo pass
994 > pass
996 > pass
995 > EOF
997 > EOF
996 > echo '#require slow' > test-slow-timeout.t
998 > echo '#require slow' > test-slow-timeout.t
997 > cat test-timeout.t >> test-slow-timeout.t
999 > cat test-timeout.t >> test-slow-timeout.t
998 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
1000 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
999 running 2 tests using 1 parallel processes
1001 running 2 tests using 1 parallel processes
1000 st
1002 st
1001 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1003 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
1002 Failed test-timeout.t: timed out
1004 Failed test-timeout.t: timed out
1003 # Ran 1 tests, 1 skipped, 1 failed.
1005 # Ran 1 tests, 1 skipped, 1 failed.
1004 python hash seed: * (glob)
1006 python hash seed: * (glob)
1005 [1]
1007 [1]
1006 $ rt --timeout=1 --slowtimeout=3 \
1008 $ rt --timeout=1 --slowtimeout=3 \
1007 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1009 > test-timeout.t test-slow-timeout.t --allow-slow-tests
1008 running 2 tests using 1 parallel processes
1010 running 2 tests using 1 parallel processes
1009 .t
1011 .t
1010 Failed test-timeout.t: timed out
1012 Failed test-timeout.t: timed out
1011 # Ran 2 tests, 0 skipped, 1 failed.
1013 # Ran 2 tests, 0 skipped, 1 failed.
1012 python hash seed: * (glob)
1014 python hash seed: * (glob)
1013 [1]
1015 [1]
1014 $ rm test-timeout.t test-slow-timeout.t
1016 $ rm test-timeout.t test-slow-timeout.t
1015
1017
1016 test for --time
1018 test for --time
1017 ==================
1019 ==================
1018
1020
1019 $ rt test-success.t --time
1021 $ rt test-success.t --time
1020 running 1 tests using 1 parallel processes
1022 running 1 tests using 1 parallel processes
1021 .
1023 .
1022 # Ran 1 tests, 0 skipped, 0 failed.
1024 # Ran 1 tests, 0 skipped, 0 failed.
1023 # Producing time report
1025 # Producing time report
1024 start end cuser csys real Test
1026 start end cuser csys real Test
1025 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
1027 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
1026
1028
1027 test for --time with --job enabled
1029 test for --time with --job enabled
1028 ====================================
1030 ====================================
1029
1031
1030 $ rt test-success.t --time --jobs 2
1032 $ rt test-success.t --time --jobs 2
1031 running 1 tests using 1 parallel processes
1033 running 1 tests using 1 parallel processes
1032 .
1034 .
1033 # Ran 1 tests, 0 skipped, 0 failed.
1035 # Ran 1 tests, 0 skipped, 0 failed.
1034 # Producing time report
1036 # Producing time report
1035 start end cuser csys real Test
1037 start end cuser csys real Test
1036 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
1038 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
1037
1039
1038 Skips
1040 Skips
1039 ================
1041 ================
1040 $ cat > test-skip.t <<EOF
1042 $ cat > test-skip.t <<EOF
1041 > $ echo xyzzy
1043 > $ echo xyzzy
1042 > #if true
1044 > #if true
1043 > #require false
1045 > #require false
1044 > #end
1046 > #end
1045 > EOF
1047 > EOF
1046 $ cat > test-noskip.t <<EOF
1048 $ cat > test-noskip.t <<EOF
1047 > #if false
1049 > #if false
1048 > #require false
1050 > #require false
1049 > #endif
1051 > #endif
1050 > EOF
1052 > EOF
1051 $ rt --nodiff
1053 $ rt --nodiff
1052 running 4 tests using 1 parallel processes
1054 running 4 tests using 1 parallel processes
1053 !.s.
1055 !.s.
1054 Skipped test-skip.t: missing feature: nail clipper
1056 Skipped test-skip.t: missing feature: nail clipper
1055 Failed test-failure.t: output changed
1057 Failed test-failure.t: output changed
1056 # Ran 3 tests, 1 skipped, 1 failed.
1058 # Ran 3 tests, 1 skipped, 1 failed.
1057 python hash seed: * (glob)
1059 python hash seed: * (glob)
1058 [1]
1060 [1]
1059
1061
1060 $ rm test-noskip.t
1062 $ rm test-noskip.t
1061 $ rt --keyword xyzzy
1063 $ rt --keyword xyzzy
1062 running 3 tests using 1 parallel processes
1064 running 3 tests using 1 parallel processes
1063 .s
1065 .s
1064 Skipped test-skip.t: missing feature: nail clipper
1066 Skipped test-skip.t: missing feature: nail clipper
1065 # Ran 2 tests, 2 skipped, 0 failed.
1067 # Ran 2 tests, 2 skipped, 0 failed.
1066
1068
1067 Skips with xml
1069 Skips with xml
1068 $ rt --keyword xyzzy \
1070 $ rt --keyword xyzzy \
1069 > --xunit=xunit.xml
1071 > --xunit=xunit.xml
1070 running 3 tests using 1 parallel processes
1072 running 3 tests using 1 parallel processes
1071 .s
1073 .s
1072 Skipped test-skip.t: missing feature: nail clipper
1074 Skipped test-skip.t: missing feature: nail clipper
1073 # Ran 2 tests, 2 skipped, 0 failed.
1075 # Ran 2 tests, 2 skipped, 0 failed.
1074 $ cat xunit.xml
1076 $ cat xunit.xml
1075 <?xml version="1.0" encoding="utf-8"?>
1077 <?xml version="1.0" encoding="utf-8"?>
1076 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1078 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1077 <testcase name="test-success.t" time="*"/> (glob)
1079 <testcase name="test-success.t" time="*"/> (glob)
1078 <testcase name="test-skip.t">
1080 <testcase name="test-skip.t">
1079 <skipped>
1081 <skipped>
1080 <![CDATA[missing feature: nail clipper]]> </skipped>
1082 <![CDATA[missing feature: nail clipper]]> </skipped>
1081 </testcase>
1083 </testcase>
1082 </testsuite>
1084 </testsuite>
1083
1085
1084 Missing skips or blacklisted skips don't count as executed:
1086 Missing skips or blacklisted skips don't count as executed:
1085 $ echo test-failure.t > blacklist
1087 $ echo test-failure.t > blacklist
1086 $ rt --blacklist=blacklist --json\
1088 $ rt --blacklist=blacklist --json\
1087 > test-failure.t test-bogus.t
1089 > test-failure.t test-bogus.t
1088 running 2 tests using 1 parallel processes
1090 running 2 tests using 1 parallel processes
1089 ss
1091 ss
1090 Skipped test-bogus.t: Doesn't exist
1092 Skipped test-bogus.t: Doesn't exist
1091 Skipped test-failure.t: blacklisted
1093 Skipped test-failure.t: blacklisted
1092 # Ran 0 tests, 2 skipped, 0 failed.
1094 # Ran 0 tests, 2 skipped, 0 failed.
1093 $ cat report.json
1095 $ cat report.json
1094 testreport ={
1096 testreport ={
1095 "test-bogus.t": {
1097 "test-bogus.t": {
1096 "result": "skip"
1098 "result": "skip"
1097 },
1099 },
1098 "test-failure.t": {
1100 "test-failure.t": {
1099 "result": "skip"
1101 "result": "skip"
1100 }
1102 }
1101 } (no-eol)
1103 } (no-eol)
1102
1104
1103 Whitelist trumps blacklist
1105 Whitelist trumps blacklist
1104 $ echo test-failure.t > whitelist
1106 $ echo test-failure.t > whitelist
1105 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1107 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1106 > test-failure.t test-bogus.t
1108 > test-failure.t test-bogus.t
1107 running 2 tests using 1 parallel processes
1109 running 2 tests using 1 parallel processes
1108 s
1110 s
1109 --- $TESTTMP/test-failure.t
1111 --- $TESTTMP/test-failure.t
1110 +++ $TESTTMP/test-failure.t.err
1112 +++ $TESTTMP/test-failure.t.err
1111 @@ -1,5 +1,5 @@
1113 @@ -1,5 +1,5 @@
1112 $ echo babar
1114 $ echo babar
1113 - rataxes
1115 - rataxes
1114 + babar
1116 + babar
1115 This is a noop statement so that
1117 This is a noop statement so that
1116 this test is still more bytes than success.
1118 this test is still more bytes than success.
1117 pad pad pad pad............................................................
1119 pad pad pad pad............................................................
1118
1120
1119 ERROR: test-failure.t output changed
1121 ERROR: test-failure.t output changed
1120 !
1122 !
1121 Skipped test-bogus.t: Doesn't exist
1123 Skipped test-bogus.t: Doesn't exist
1122 Failed test-failure.t: output changed
1124 Failed test-failure.t: output changed
1123 # Ran 1 tests, 1 skipped, 1 failed.
1125 # Ran 1 tests, 1 skipped, 1 failed.
1124 python hash seed: * (glob)
1126 python hash seed: * (glob)
1125 [1]
1127 [1]
1126
1128
1127 Ensure that --test-list causes only the tests listed in that file to
1129 Ensure that --test-list causes only the tests listed in that file to
1128 be executed.
1130 be executed.
1129 $ echo test-success.t >> onlytest
1131 $ echo test-success.t >> onlytest
1130 $ rt --test-list=onlytest
1132 $ rt --test-list=onlytest
1131 running 1 tests using 1 parallel processes
1133 running 1 tests using 1 parallel processes
1132 .
1134 .
1133 # Ran 1 tests, 0 skipped, 0 failed.
1135 # Ran 1 tests, 0 skipped, 0 failed.
1134 $ echo test-bogus.t >> anothertest
1136 $ echo test-bogus.t >> anothertest
1135 $ rt --test-list=onlytest --test-list=anothertest
1137 $ rt --test-list=onlytest --test-list=anothertest
1136 running 2 tests using 1 parallel processes
1138 running 2 tests using 1 parallel processes
1137 s.
1139 s.
1138 Skipped test-bogus.t: Doesn't exist
1140 Skipped test-bogus.t: Doesn't exist
1139 # Ran 1 tests, 1 skipped, 0 failed.
1141 # Ran 1 tests, 1 skipped, 0 failed.
1140 $ rm onlytest anothertest
1142 $ rm onlytest anothertest
1141
1143
1142 test for --json
1144 test for --json
1143 ==================
1145 ==================
1144
1146
1145 $ rt --json
1147 $ rt --json
1146 running 3 tests using 1 parallel processes
1148 running 3 tests using 1 parallel processes
1147
1149
1148 --- $TESTTMP/test-failure.t
1150 --- $TESTTMP/test-failure.t
1149 +++ $TESTTMP/test-failure.t.err
1151 +++ $TESTTMP/test-failure.t.err
1150 @@ -1,5 +1,5 @@
1152 @@ -1,5 +1,5 @@
1151 $ echo babar
1153 $ echo babar
1152 - rataxes
1154 - rataxes
1153 + babar
1155 + babar
1154 This is a noop statement so that
1156 This is a noop statement so that
1155 this test is still more bytes than success.
1157 this test is still more bytes than success.
1156 pad pad pad pad............................................................
1158 pad pad pad pad............................................................
1157
1159
1158 ERROR: test-failure.t output changed
1160 ERROR: test-failure.t output changed
1159 !.s
1161 !.s
1160 Skipped test-skip.t: missing feature: nail clipper
1162 Skipped test-skip.t: missing feature: nail clipper
1161 Failed test-failure.t: output changed
1163 Failed test-failure.t: output changed
1162 # Ran 2 tests, 1 skipped, 1 failed.
1164 # Ran 2 tests, 1 skipped, 1 failed.
1163 python hash seed: * (glob)
1165 python hash seed: * (glob)
1164 [1]
1166 [1]
1165
1167
1166 $ cat report.json
1168 $ cat report.json
1167 testreport ={
1169 testreport ={
1168 "test-failure.t": [\{] (re)
1170 "test-failure.t": [\{] (re)
1169 "csys": "\s*[\d\.]{4,5}", ? (re)
1171 "csys": "\s*[\d\.]{4,5}", ? (re)
1170 "cuser": "\s*[\d\.]{4,5}", ? (re)
1172 "cuser": "\s*[\d\.]{4,5}", ? (re)
1171 "diff": "---.+\+\+\+.+", ? (re)
1173 "diff": "---.+\+\+\+.+", ? (re)
1172 "end": "\s*[\d\.]{4,5}", ? (re)
1174 "end": "\s*[\d\.]{4,5}", ? (re)
1173 "result": "failure", ? (re)
1175 "result": "failure", ? (re)
1174 "start": "\s*[\d\.]{4,5}", ? (re)
1176 "start": "\s*[\d\.]{4,5}", ? (re)
1175 "time": "\s*[\d\.]{4,5}" (re)
1177 "time": "\s*[\d\.]{4,5}" (re)
1176 }, ? (re)
1178 }, ? (re)
1177 "test-skip.t": {
1179 "test-skip.t": {
1178 "csys": "\s*[\d\.]{4,5}", ? (re)
1180 "csys": "\s*[\d\.]{4,5}", ? (re)
1179 "cuser": "\s*[\d\.]{4,5}", ? (re)
1181 "cuser": "\s*[\d\.]{4,5}", ? (re)
1180 "diff": "", ? (re)
1182 "diff": "", ? (re)
1181 "end": "\s*[\d\.]{4,5}", ? (re)
1183 "end": "\s*[\d\.]{4,5}", ? (re)
1182 "result": "skip", ? (re)
1184 "result": "skip", ? (re)
1183 "start": "\s*[\d\.]{4,5}", ? (re)
1185 "start": "\s*[\d\.]{4,5}", ? (re)
1184 "time": "\s*[\d\.]{4,5}" (re)
1186 "time": "\s*[\d\.]{4,5}" (re)
1185 }, ? (re)
1187 }, ? (re)
1186 "test-success.t": [\{] (re)
1188 "test-success.t": [\{] (re)
1187 "csys": "\s*[\d\.]{4,5}", ? (re)
1189 "csys": "\s*[\d\.]{4,5}", ? (re)
1188 "cuser": "\s*[\d\.]{4,5}", ? (re)
1190 "cuser": "\s*[\d\.]{4,5}", ? (re)
1189 "diff": "", ? (re)
1191 "diff": "", ? (re)
1190 "end": "\s*[\d\.]{4,5}", ? (re)
1192 "end": "\s*[\d\.]{4,5}", ? (re)
1191 "result": "success", ? (re)
1193 "result": "success", ? (re)
1192 "start": "\s*[\d\.]{4,5}", ? (re)
1194 "start": "\s*[\d\.]{4,5}", ? (re)
1193 "time": "\s*[\d\.]{4,5}" (re)
1195 "time": "\s*[\d\.]{4,5}" (re)
1194 }
1196 }
1195 } (no-eol)
1197 } (no-eol)
1196 --json with --outputdir
1198 --json with --outputdir
1197
1199
1198 $ rm report.json
1200 $ rm report.json
1199 $ rm -r output
1201 $ rm -r output
1200 $ mkdir output
1202 $ mkdir output
1201 $ rt --json --outputdir output
1203 $ rt --json --outputdir output
1202 running 3 tests using 1 parallel processes
1204 running 3 tests using 1 parallel processes
1203
1205
1204 --- $TESTTMP/test-failure.t
1206 --- $TESTTMP/test-failure.t
1205 +++ $TESTTMP/output/test-failure.t.err
1207 +++ $TESTTMP/output/test-failure.t.err
1206 @@ -1,5 +1,5 @@
1208 @@ -1,5 +1,5 @@
1207 $ echo babar
1209 $ echo babar
1208 - rataxes
1210 - rataxes
1209 + babar
1211 + babar
1210 This is a noop statement so that
1212 This is a noop statement so that
1211 this test is still more bytes than success.
1213 this test is still more bytes than success.
1212 pad pad pad pad............................................................
1214 pad pad pad pad............................................................
1213
1215
1214 ERROR: test-failure.t output changed
1216 ERROR: test-failure.t output changed
1215 !.s
1217 !.s
1216 Skipped test-skip.t: missing feature: nail clipper
1218 Skipped test-skip.t: missing feature: nail clipper
1217 Failed test-failure.t: output changed
1219 Failed test-failure.t: output changed
1218 # Ran 2 tests, 1 skipped, 1 failed.
1220 # Ran 2 tests, 1 skipped, 1 failed.
1219 python hash seed: * (glob)
1221 python hash seed: * (glob)
1220 [1]
1222 [1]
1221 $ f report.json
1223 $ f report.json
1222 report.json: file not found
1224 report.json: file not found
1223 $ cat output/report.json
1225 $ cat output/report.json
1224 testreport ={
1226 testreport ={
1225 "test-failure.t": [\{] (re)
1227 "test-failure.t": [\{] (re)
1226 "csys": "\s*[\d\.]{4,5}", ? (re)
1228 "csys": "\s*[\d\.]{4,5}", ? (re)
1227 "cuser": "\s*[\d\.]{4,5}", ? (re)
1229 "cuser": "\s*[\d\.]{4,5}", ? (re)
1228 "diff": "---.+\+\+\+.+", ? (re)
1230 "diff": "---.+\+\+\+.+", ? (re)
1229 "end": "\s*[\d\.]{4,5}", ? (re)
1231 "end": "\s*[\d\.]{4,5}", ? (re)
1230 "result": "failure", ? (re)
1232 "result": "failure", ? (re)
1231 "start": "\s*[\d\.]{4,5}", ? (re)
1233 "start": "\s*[\d\.]{4,5}", ? (re)
1232 "time": "\s*[\d\.]{4,5}" (re)
1234 "time": "\s*[\d\.]{4,5}" (re)
1233 }, ? (re)
1235 }, ? (re)
1234 "test-skip.t": {
1236 "test-skip.t": {
1235 "csys": "\s*[\d\.]{4,5}", ? (re)
1237 "csys": "\s*[\d\.]{4,5}", ? (re)
1236 "cuser": "\s*[\d\.]{4,5}", ? (re)
1238 "cuser": "\s*[\d\.]{4,5}", ? (re)
1237 "diff": "", ? (re)
1239 "diff": "", ? (re)
1238 "end": "\s*[\d\.]{4,5}", ? (re)
1240 "end": "\s*[\d\.]{4,5}", ? (re)
1239 "result": "skip", ? (re)
1241 "result": "skip", ? (re)
1240 "start": "\s*[\d\.]{4,5}", ? (re)
1242 "start": "\s*[\d\.]{4,5}", ? (re)
1241 "time": "\s*[\d\.]{4,5}" (re)
1243 "time": "\s*[\d\.]{4,5}" (re)
1242 }, ? (re)
1244 }, ? (re)
1243 "test-success.t": [\{] (re)
1245 "test-success.t": [\{] (re)
1244 "csys": "\s*[\d\.]{4,5}", ? (re)
1246 "csys": "\s*[\d\.]{4,5}", ? (re)
1245 "cuser": "\s*[\d\.]{4,5}", ? (re)
1247 "cuser": "\s*[\d\.]{4,5}", ? (re)
1246 "diff": "", ? (re)
1248 "diff": "", ? (re)
1247 "end": "\s*[\d\.]{4,5}", ? (re)
1249 "end": "\s*[\d\.]{4,5}", ? (re)
1248 "result": "success", ? (re)
1250 "result": "success", ? (re)
1249 "start": "\s*[\d\.]{4,5}", ? (re)
1251 "start": "\s*[\d\.]{4,5}", ? (re)
1250 "time": "\s*[\d\.]{4,5}" (re)
1252 "time": "\s*[\d\.]{4,5}" (re)
1251 }
1253 }
1252 } (no-eol)
1254 } (no-eol)
1253 $ ls -a output
1255 $ ls -a output
1254 .
1256 .
1255 ..
1257 ..
1256 .testtimes
1258 .testtimes
1257 report.json
1259 report.json
1258 test-failure.t.err
1260 test-failure.t.err
1259
1261
1260 Test that failed test accepted through interactive are properly reported:
1262 Test that failed test accepted through interactive are properly reported:
1261
1263
1262 $ cp test-failure.t backup
1264 $ cp test-failure.t backup
1263 $ echo y | rt --json -i
1265 $ echo y | rt --json -i
1264 running 3 tests using 1 parallel processes
1266 running 3 tests using 1 parallel processes
1265
1267
1266 --- $TESTTMP/test-failure.t
1268 --- $TESTTMP/test-failure.t
1267 +++ $TESTTMP/test-failure.t.err
1269 +++ $TESTTMP/test-failure.t.err
1268 @@ -1,5 +1,5 @@
1270 @@ -1,5 +1,5 @@
1269 $ echo babar
1271 $ echo babar
1270 - rataxes
1272 - rataxes
1271 + babar
1273 + babar
1272 This is a noop statement so that
1274 This is a noop statement so that
1273 this test is still more bytes than success.
1275 this test is still more bytes than success.
1274 pad pad pad pad............................................................
1276 pad pad pad pad............................................................
1275 Accept this change? [n] ..s
1277 Accept this change? [n] ..s
1276 Skipped test-skip.t: missing feature: nail clipper
1278 Skipped test-skip.t: missing feature: nail clipper
1277 # Ran 2 tests, 1 skipped, 0 failed.
1279 # Ran 2 tests, 1 skipped, 0 failed.
1278
1280
1279 $ cat report.json
1281 $ cat report.json
1280 testreport ={
1282 testreport ={
1281 "test-failure.t": [\{] (re)
1283 "test-failure.t": [\{] (re)
1282 "csys": "\s*[\d\.]{4,5}", ? (re)
1284 "csys": "\s*[\d\.]{4,5}", ? (re)
1283 "cuser": "\s*[\d\.]{4,5}", ? (re)
1285 "cuser": "\s*[\d\.]{4,5}", ? (re)
1284 "diff": "", ? (re)
1286 "diff": "", ? (re)
1285 "end": "\s*[\d\.]{4,5}", ? (re)
1287 "end": "\s*[\d\.]{4,5}", ? (re)
1286 "result": "success", ? (re)
1288 "result": "success", ? (re)
1287 "start": "\s*[\d\.]{4,5}", ? (re)
1289 "start": "\s*[\d\.]{4,5}", ? (re)
1288 "time": "\s*[\d\.]{4,5}" (re)
1290 "time": "\s*[\d\.]{4,5}" (re)
1289 }, ? (re)
1291 }, ? (re)
1290 "test-skip.t": {
1292 "test-skip.t": {
1291 "csys": "\s*[\d\.]{4,5}", ? (re)
1293 "csys": "\s*[\d\.]{4,5}", ? (re)
1292 "cuser": "\s*[\d\.]{4,5}", ? (re)
1294 "cuser": "\s*[\d\.]{4,5}", ? (re)
1293 "diff": "", ? (re)
1295 "diff": "", ? (re)
1294 "end": "\s*[\d\.]{4,5}", ? (re)
1296 "end": "\s*[\d\.]{4,5}", ? (re)
1295 "result": "skip", ? (re)
1297 "result": "skip", ? (re)
1296 "start": "\s*[\d\.]{4,5}", ? (re)
1298 "start": "\s*[\d\.]{4,5}", ? (re)
1297 "time": "\s*[\d\.]{4,5}" (re)
1299 "time": "\s*[\d\.]{4,5}" (re)
1298 }, ? (re)
1300 }, ? (re)
1299 "test-success.t": [\{] (re)
1301 "test-success.t": [\{] (re)
1300 "csys": "\s*[\d\.]{4,5}", ? (re)
1302 "csys": "\s*[\d\.]{4,5}", ? (re)
1301 "cuser": "\s*[\d\.]{4,5}", ? (re)
1303 "cuser": "\s*[\d\.]{4,5}", ? (re)
1302 "diff": "", ? (re)
1304 "diff": "", ? (re)
1303 "end": "\s*[\d\.]{4,5}", ? (re)
1305 "end": "\s*[\d\.]{4,5}", ? (re)
1304 "result": "success", ? (re)
1306 "result": "success", ? (re)
1305 "start": "\s*[\d\.]{4,5}", ? (re)
1307 "start": "\s*[\d\.]{4,5}", ? (re)
1306 "time": "\s*[\d\.]{4,5}" (re)
1308 "time": "\s*[\d\.]{4,5}" (re)
1307 }
1309 }
1308 } (no-eol)
1310 } (no-eol)
1309 $ mv backup test-failure.t
1311 $ mv backup test-failure.t
1310
1312
1311 backslash on end of line with glob matching is handled properly
1313 backslash on end of line with glob matching is handled properly
1312
1314
1313 $ cat > test-glob-backslash.t << EOF
1315 $ cat > test-glob-backslash.t << EOF
1314 > $ echo 'foo bar \\'
1316 > $ echo 'foo bar \\'
1315 > foo * \ (glob)
1317 > foo * \ (glob)
1316 > EOF
1318 > EOF
1317
1319
1318 $ rt test-glob-backslash.t
1320 $ rt test-glob-backslash.t
1319 running 1 tests using 1 parallel processes
1321 running 1 tests using 1 parallel processes
1320 .
1322 .
1321 # Ran 1 tests, 0 skipped, 0 failed.
1323 # Ran 1 tests, 0 skipped, 0 failed.
1322
1324
1323 $ rm -f test-glob-backslash.t
1325 $ rm -f test-glob-backslash.t
1324
1326
1325 Test globbing of local IP addresses
1327 Test globbing of local IP addresses
1326 $ echo 172.16.18.1
1328 $ echo 172.16.18.1
1327 $LOCALIP (glob)
1329 $LOCALIP (glob)
1328 $ echo dead:beef::1
1330 $ echo dead:beef::1
1329 $LOCALIP (glob)
1331 $LOCALIP (glob)
1330
1332
1331 Add support for external test formatter
1333 Add support for external test formatter
1332 =======================================
1334 =======================================
1333
1335
1334 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@" test-success.t test-failure.t
1336 $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` -j1 "$@" test-success.t test-failure.t
1335 running 2 tests using 1 parallel processes
1337 running 2 tests using 1 parallel processes
1336
1338
1337 # Ran 2 tests, 0 skipped, 0 failed.
1339 # Ran 2 tests, 0 skipped, 0 failed.
1338 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1340 ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
1339 FAILURE! test-failure.t output changed
1341 FAILURE! test-failure.t output changed
1340 SUCCESS! test-success.t
1342 SUCCESS! test-success.t
1341 ON_END!
1343 ON_END!
1342
1344
1343 Test reusability for third party tools
1345 Test reusability for third party tools
1344 ======================================
1346 ======================================
1345
1347
1346 $ mkdir "$TESTTMP"/anothertests
1348 $ mkdir "$TESTTMP"/anothertests
1347 $ cd "$TESTTMP"/anothertests
1349 $ cd "$TESTTMP"/anothertests
1348
1350
1349 test that `run-tests.py` can execute hghave, even if it runs not in
1351 test that `run-tests.py` can execute hghave, even if it runs not in
1350 Mercurial source tree.
1352 Mercurial source tree.
1351
1353
1352 $ cat > test-hghave.t <<EOF
1354 $ cat > test-hghave.t <<EOF
1353 > #require true
1355 > #require true
1354 > $ echo foo
1356 > $ echo foo
1355 > foo
1357 > foo
1356 > EOF
1358 > EOF
1357 $ rt test-hghave.t
1359 $ rt test-hghave.t
1358 running 1 tests using 1 parallel processes
1360 running 1 tests using 1 parallel processes
1359 .
1361 .
1360 # Ran 1 tests, 0 skipped, 0 failed.
1362 # Ran 1 tests, 0 skipped, 0 failed.
1361
1363
1362 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1364 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1363 running is placed.
1365 running is placed.
1364
1366
1365 $ cat > test-runtestdir.t <<EOF
1367 $ cat > test-runtestdir.t <<EOF
1366 > - $TESTDIR, in which test-run-tests.t is placed
1368 > - $TESTDIR, in which test-run-tests.t is placed
1367 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1369 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1368 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1370 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1369 >
1371 >
1370 > #if windows
1372 > #if windows
1371 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1373 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1372 > #else
1374 > #else
1373 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1375 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1374 > #endif
1376 > #endif
1375 > If this prints a path, that means RUNTESTDIR didn't equal
1377 > If this prints a path, that means RUNTESTDIR didn't equal
1376 > TESTDIR as it should have.
1378 > TESTDIR as it should have.
1377 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1379 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1378 > This should print the start of check-code. If this passes but the
1380 > This should print the start of check-code. If this passes but the
1379 > previous check failed, that means we found a copy of check-code at whatever
1381 > previous check failed, that means we found a copy of check-code at whatever
1380 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1382 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1381 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1383 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1382 > #!USRBINENVPY
1384 > #!USRBINENVPY
1383 > #
1385 > #
1384 > # check-code - a style and portability checker for Mercurial
1386 > # check-code - a style and portability checker for Mercurial
1385 > EOF
1387 > EOF
1386 $ rt test-runtestdir.t
1388 $ rt test-runtestdir.t
1387 running 1 tests using 1 parallel processes
1389 running 1 tests using 1 parallel processes
1388 .
1390 .
1389 # Ran 1 tests, 0 skipped, 0 failed.
1391 # Ran 1 tests, 0 skipped, 0 failed.
1390
1392
1391 #if execbit
1393 #if execbit
1392
1394
1393 test that TESTDIR is referred in PATH
1395 test that TESTDIR is referred in PATH
1394
1396
1395 $ cat > custom-command.sh <<EOF
1397 $ cat > custom-command.sh <<EOF
1396 > #!/bin/sh
1398 > #!/bin/sh
1397 > echo "hello world"
1399 > echo "hello world"
1398 > EOF
1400 > EOF
1399 $ chmod +x custom-command.sh
1401 $ chmod +x custom-command.sh
1400 $ cat > test-testdir-path.t <<EOF
1402 $ cat > test-testdir-path.t <<EOF
1401 > $ custom-command.sh
1403 > $ custom-command.sh
1402 > hello world
1404 > hello world
1403 > EOF
1405 > EOF
1404 $ rt test-testdir-path.t
1406 $ rt test-testdir-path.t
1405 running 1 tests using 1 parallel processes
1407 running 1 tests using 1 parallel processes
1406 .
1408 .
1407 # Ran 1 tests, 0 skipped, 0 failed.
1409 # Ran 1 tests, 0 skipped, 0 failed.
1408
1410
1409 #endif
1411 #endif
1410
1412
1411 test support for --allow-slow-tests
1413 test support for --allow-slow-tests
1412 $ cat > test-very-slow-test.t <<EOF
1414 $ cat > test-very-slow-test.t <<EOF
1413 > #require slow
1415 > #require slow
1414 > $ echo pass
1416 > $ echo pass
1415 > pass
1417 > pass
1416 > EOF
1418 > EOF
1417 $ rt test-very-slow-test.t
1419 $ rt test-very-slow-test.t
1418 running 1 tests using 1 parallel processes
1420 running 1 tests using 1 parallel processes
1419 s
1421 s
1420 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1422 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1421 # Ran 0 tests, 1 skipped, 0 failed.
1423 # Ran 0 tests, 1 skipped, 0 failed.
1422 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1424 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1423 running 1 tests using 1 parallel processes
1425 running 1 tests using 1 parallel processes
1424 .
1426 .
1425 # Ran 1 tests, 0 skipped, 0 failed.
1427 # Ran 1 tests, 0 skipped, 0 failed.
1426
1428
1427 support for running a test outside the current directory
1429 support for running a test outside the current directory
1428 $ mkdir nonlocal
1430 $ mkdir nonlocal
1429 $ cat > nonlocal/test-is-not-here.t << EOF
1431 $ cat > nonlocal/test-is-not-here.t << EOF
1430 > $ echo pass
1432 > $ echo pass
1431 > pass
1433 > pass
1432 > EOF
1434 > EOF
1433 $ rt nonlocal/test-is-not-here.t
1435 $ rt nonlocal/test-is-not-here.t
1434 running 1 tests using 1 parallel processes
1436 running 1 tests using 1 parallel processes
1435 .
1437 .
1436 # Ran 1 tests, 0 skipped, 0 failed.
1438 # Ran 1 tests, 0 skipped, 0 failed.
1437
1439
1438 support for automatically discovering test if arg is a folder
1440 support for automatically discovering test if arg is a folder
1439 $ mkdir tmp && cd tmp
1441 $ mkdir tmp && cd tmp
1440
1442
1441 $ cat > test-uno.t << EOF
1443 $ cat > test-uno.t << EOF
1442 > $ echo line
1444 > $ echo line
1443 > line
1445 > line
1444 > EOF
1446 > EOF
1445
1447
1446 $ cp test-uno.t test-dos.t
1448 $ cp test-uno.t test-dos.t
1447 $ cd ..
1449 $ cd ..
1448 $ cp -R tmp tmpp
1450 $ cp -R tmp tmpp
1449 $ cp tmp/test-uno.t test-solo.t
1451 $ cp tmp/test-uno.t test-solo.t
1450
1452
1451 $ rt tmp/ test-solo.t tmpp
1453 $ rt tmp/ test-solo.t tmpp
1452 running 5 tests using 1 parallel processes
1454 running 5 tests using 1 parallel processes
1453 .....
1455 .....
1454 # Ran 5 tests, 0 skipped, 0 failed.
1456 # Ran 5 tests, 0 skipped, 0 failed.
1455 $ rm -rf tmp tmpp
1457 $ rm -rf tmp tmpp
1456
1458
1457 support for running run-tests.py from another directory
1459 support for running run-tests.py from another directory
1458 $ mkdir tmp && cd tmp
1460 $ mkdir tmp && cd tmp
1459
1461
1460 $ cat > useful-file.sh << EOF
1462 $ cat > useful-file.sh << EOF
1461 > important command
1463 > important command
1462 > EOF
1464 > EOF
1463
1465
1464 $ cat > test-folder.t << EOF
1466 $ cat > test-folder.t << EOF
1465 > $ cat \$TESTDIR/useful-file.sh
1467 > $ cat \$TESTDIR/useful-file.sh
1466 > important command
1468 > important command
1467 > EOF
1469 > EOF
1468
1470
1469 $ cat > test-folder-fail.t << EOF
1471 $ cat > test-folder-fail.t << EOF
1470 > $ cat \$TESTDIR/useful-file.sh
1472 > $ cat \$TESTDIR/useful-file.sh
1471 > important commando
1473 > important commando
1472 > EOF
1474 > EOF
1473
1475
1474 $ cd ..
1476 $ cd ..
1475 $ rt tmp/test-*.t
1477 $ rt tmp/test-*.t
1476 running 2 tests using 1 parallel processes
1478 running 2 tests using 1 parallel processes
1477
1479
1478 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1480 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1479 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1481 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1480 @@ -1,2 +1,2 @@
1482 @@ -1,2 +1,2 @@
1481 $ cat $TESTDIR/useful-file.sh
1483 $ cat $TESTDIR/useful-file.sh
1482 - important commando
1484 - important commando
1483 + important command
1485 + important command
1484
1486
1485 ERROR: test-folder-fail.t output changed
1487 ERROR: test-folder-fail.t output changed
1486 !.
1488 !.
1487 Failed test-folder-fail.t: output changed
1489 Failed test-folder-fail.t: output changed
1488 # Ran 2 tests, 0 skipped, 1 failed.
1490 # Ran 2 tests, 0 skipped, 1 failed.
1489 python hash seed: * (glob)
1491 python hash seed: * (glob)
1490 [1]
1492 [1]
1491
1493
1492 support for bisecting failed tests automatically
1494 support for bisecting failed tests automatically
1493 $ hg init bisect
1495 $ hg init bisect
1494 $ cd bisect
1496 $ cd bisect
1495 $ cat >> test-bisect.t <<EOF
1497 $ cat >> test-bisect.t <<EOF
1496 > $ echo pass
1498 > $ echo pass
1497 > pass
1499 > pass
1498 > EOF
1500 > EOF
1499 $ hg add test-bisect.t
1501 $ hg add test-bisect.t
1500 $ hg ci -m 'good'
1502 $ hg ci -m 'good'
1501 $ cat >> test-bisect.t <<EOF
1503 $ cat >> test-bisect.t <<EOF
1502 > $ echo pass
1504 > $ echo pass
1503 > fail
1505 > fail
1504 > EOF
1506 > EOF
1505 $ hg ci -m 'bad'
1507 $ hg ci -m 'bad'
1506 $ rt --known-good-rev=0 test-bisect.t
1508 $ rt --known-good-rev=0 test-bisect.t
1507 running 1 tests using 1 parallel processes
1509 running 1 tests using 1 parallel processes
1508
1510
1509 --- $TESTTMP/anothertests/bisect/test-bisect.t
1511 --- $TESTTMP/anothertests/bisect/test-bisect.t
1510 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1512 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1511 @@ -1,4 +1,4 @@
1513 @@ -1,4 +1,4 @@
1512 $ echo pass
1514 $ echo pass
1513 pass
1515 pass
1514 $ echo pass
1516 $ echo pass
1515 - fail
1517 - fail
1516 + pass
1518 + pass
1517
1519
1518 ERROR: test-bisect.t output changed
1520 ERROR: test-bisect.t output changed
1519 !
1521 !
1520 Failed test-bisect.t: output changed
1522 Failed test-bisect.t: output changed
1521 test-bisect.t broken by 72cbf122d116 (bad)
1523 test-bisect.t broken by 72cbf122d116 (bad)
1522 # Ran 1 tests, 0 skipped, 1 failed.
1524 # Ran 1 tests, 0 skipped, 1 failed.
1523 python hash seed: * (glob)
1525 python hash seed: * (glob)
1524 [1]
1526 [1]
1525
1527
1526 $ cd ..
1528 $ cd ..
1527
1529
1528 support bisecting a separate repo
1530 support bisecting a separate repo
1529
1531
1530 $ hg init bisect-dependent
1532 $ hg init bisect-dependent
1531 $ cd bisect-dependent
1533 $ cd bisect-dependent
1532 $ cat > test-bisect-dependent.t <<EOF
1534 $ cat > test-bisect-dependent.t <<EOF
1533 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1535 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1534 > pass
1536 > pass
1535 > EOF
1537 > EOF
1536 $ hg commit -Am dependent test-bisect-dependent.t
1538 $ hg commit -Am dependent test-bisect-dependent.t
1537
1539
1538 $ rt --known-good-rev=0 test-bisect-dependent.t
1540 $ rt --known-good-rev=0 test-bisect-dependent.t
1539 running 1 tests using 1 parallel processes
1541 running 1 tests using 1 parallel processes
1540
1542
1541 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1543 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1542 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1544 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1543 @@ -1,2 +1,2 @@
1545 @@ -1,2 +1,2 @@
1544 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1546 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1545 - pass
1547 - pass
1546 + fail
1548 + fail
1547
1549
1548 ERROR: test-bisect-dependent.t output changed
1550 ERROR: test-bisect-dependent.t output changed
1549 !
1551 !
1550 Failed test-bisect-dependent.t: output changed
1552 Failed test-bisect-dependent.t: output changed
1551 Failed to identify failure point for test-bisect-dependent.t
1553 Failed to identify failure point for test-bisect-dependent.t
1552 # Ran 1 tests, 0 skipped, 1 failed.
1554 # Ran 1 tests, 0 skipped, 1 failed.
1553 python hash seed: * (glob)
1555 python hash seed: * (glob)
1554 [1]
1556 [1]
1555
1557
1556 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1558 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1557 usage: run-tests.py [options] [tests]
1559 usage: run-tests.py [options] [tests]
1558 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1560 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1559 [2]
1561 [2]
1560
1562
1561 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1563 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1562 running 1 tests using 1 parallel processes
1564 running 1 tests using 1 parallel processes
1563
1565
1564 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1566 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1565 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1567 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1566 @@ -1,2 +1,2 @@
1568 @@ -1,2 +1,2 @@
1567 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1569 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1568 - pass
1570 - pass
1569 + fail
1571 + fail
1570
1572
1571 ERROR: test-bisect-dependent.t output changed
1573 ERROR: test-bisect-dependent.t output changed
1572 !
1574 !
1573 Failed test-bisect-dependent.t: output changed
1575 Failed test-bisect-dependent.t: output changed
1574 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1576 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1575 # Ran 1 tests, 0 skipped, 1 failed.
1577 # Ran 1 tests, 0 skipped, 1 failed.
1576 python hash seed: * (glob)
1578 python hash seed: * (glob)
1577 [1]
1579 [1]
1578
1580
1579 $ cd ..
1581 $ cd ..
1580
1582
1581 Test a broken #if statement doesn't break run-tests threading.
1583 Test a broken #if statement doesn't break run-tests threading.
1582 ==============================================================
1584 ==============================================================
1583 $ mkdir broken
1585 $ mkdir broken
1584 $ cd broken
1586 $ cd broken
1585 $ cat > test-broken.t <<EOF
1587 $ cat > test-broken.t <<EOF
1586 > true
1588 > true
1587 > #if notarealhghavefeature
1589 > #if notarealhghavefeature
1588 > $ false
1590 > $ false
1589 > #endif
1591 > #endif
1590 > EOF
1592 > EOF
1591 $ for f in 1 2 3 4 ; do
1593 $ for f in 1 2 3 4 ; do
1592 > cat > test-works-$f.t <<EOF
1594 > cat > test-works-$f.t <<EOF
1593 > This is test case $f
1595 > This is test case $f
1594 > $ sleep 1
1596 > $ sleep 1
1595 > EOF
1597 > EOF
1596 > done
1598 > done
1597 $ rt -j 2
1599 $ rt -j 2
1598 running 5 tests using 2 parallel processes
1600 running 5 tests using 2 parallel processes
1599 ....
1601 ....
1600 # Ran 5 tests, 0 skipped, 0 failed.
1602 # Ran 5 tests, 0 skipped, 0 failed.
1601 skipped: unknown feature: notarealhghavefeature
1603 skipped: unknown feature: notarealhghavefeature
1602
1604
1603 $ cd ..
1605 $ cd ..
1604 $ rm -rf broken
1606 $ rm -rf broken
1605
1607
1606 Test cases in .t files
1608 Test cases in .t files
1607 ======================
1609 ======================
1608 $ mkdir cases
1610 $ mkdir cases
1609 $ cd cases
1611 $ cd cases
1610 $ cat > test-cases-abc.t <<'EOF'
1612 $ cat > test-cases-abc.t <<'EOF'
1611 > #testcases A B C
1613 > #testcases A B C
1612 > $ V=B
1614 > $ V=B
1613 > #if A
1615 > #if A
1614 > $ V=A
1616 > $ V=A
1615 > #endif
1617 > #endif
1616 > #if C
1618 > #if C
1617 > $ V=C
1619 > $ V=C
1618 > #endif
1620 > #endif
1619 > $ echo $V | sed 's/A/C/'
1621 > $ echo $V | sed 's/A/C/'
1620 > C
1622 > C
1621 > #if C
1623 > #if C
1622 > $ [ $V = C ]
1624 > $ [ $V = C ]
1623 > #endif
1625 > #endif
1624 > #if A
1626 > #if A
1625 > $ [ $V = C ]
1627 > $ [ $V = C ]
1626 > [1]
1628 > [1]
1627 > #endif
1629 > #endif
1628 > #if no-C
1630 > #if no-C
1629 > $ [ $V = C ]
1631 > $ [ $V = C ]
1630 > [1]
1632 > [1]
1631 > #endif
1633 > #endif
1632 > $ [ $V = D ]
1634 > $ [ $V = D ]
1633 > [1]
1635 > [1]
1634 > EOF
1636 > EOF
1635 $ rt
1637 $ rt
1636 running 3 tests using 1 parallel processes
1638 running 3 tests using 1 parallel processes
1637 .
1639 .
1638 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1640 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1639 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1641 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1640 @@ -7,7 +7,7 @@
1642 @@ -7,7 +7,7 @@
1641 $ V=C
1643 $ V=C
1642 #endif
1644 #endif
1643 $ echo $V | sed 's/A/C/'
1645 $ echo $V | sed 's/A/C/'
1644 - C
1646 - C
1645 + B
1647 + B
1646 #if C
1648 #if C
1647 $ [ $V = C ]
1649 $ [ $V = C ]
1648 #endif
1650 #endif
1649
1651
1650 ERROR: test-cases-abc.t#B output changed
1652 ERROR: test-cases-abc.t#B output changed
1651 !.
1653 !.
1652 Failed test-cases-abc.t#B: output changed
1654 Failed test-cases-abc.t#B: output changed
1653 # Ran 3 tests, 0 skipped, 1 failed.
1655 # Ran 3 tests, 0 skipped, 1 failed.
1654 python hash seed: * (glob)
1656 python hash seed: * (glob)
1655 [1]
1657 [1]
1656
1658
1657 --restart works
1659 --restart works
1658
1660
1659 $ rt --restart
1661 $ rt --restart
1660 running 2 tests using 1 parallel processes
1662 running 2 tests using 1 parallel processes
1661
1663
1662 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1664 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1663 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1665 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1664 @@ -7,7 +7,7 @@
1666 @@ -7,7 +7,7 @@
1665 $ V=C
1667 $ V=C
1666 #endif
1668 #endif
1667 $ echo $V | sed 's/A/C/'
1669 $ echo $V | sed 's/A/C/'
1668 - C
1670 - C
1669 + B
1671 + B
1670 #if C
1672 #if C
1671 $ [ $V = C ]
1673 $ [ $V = C ]
1672 #endif
1674 #endif
1673
1675
1674 ERROR: test-cases-abc.t#B output changed
1676 ERROR: test-cases-abc.t#B output changed
1675 !.
1677 !.
1676 Failed test-cases-abc.t#B: output changed
1678 Failed test-cases-abc.t#B: output changed
1677 # Ran 2 tests, 0 skipped, 1 failed.
1679 # Ran 2 tests, 0 skipped, 1 failed.
1678 python hash seed: * (glob)
1680 python hash seed: * (glob)
1679 [1]
1681 [1]
1680
1682
1681 --restart works with outputdir
1683 --restart works with outputdir
1682
1684
1683 $ mkdir output
1685 $ mkdir output
1684 $ mv test-cases-abc.t#B.err output
1686 $ mv test-cases-abc.t#B.err output
1685 $ rt --restart --outputdir output
1687 $ rt --restart --outputdir output
1686 running 2 tests using 1 parallel processes
1688 running 2 tests using 1 parallel processes
1687
1689
1688 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1690 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1689 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1691 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
1690 @@ -7,7 +7,7 @@
1692 @@ -7,7 +7,7 @@
1691 $ V=C
1693 $ V=C
1692 #endif
1694 #endif
1693 $ echo $V | sed 's/A/C/'
1695 $ echo $V | sed 's/A/C/'
1694 - C
1696 - C
1695 + B
1697 + B
1696 #if C
1698 #if C
1697 $ [ $V = C ]
1699 $ [ $V = C ]
1698 #endif
1700 #endif
1699
1701
1700 ERROR: test-cases-abc.t#B output changed
1702 ERROR: test-cases-abc.t#B output changed
1701 !.
1703 !.
1702 Failed test-cases-abc.t#B: output changed
1704 Failed test-cases-abc.t#B: output changed
1703 # Ran 2 tests, 0 skipped, 1 failed.
1705 # Ran 2 tests, 0 skipped, 1 failed.
1704 python hash seed: * (glob)
1706 python hash seed: * (glob)
1705 [1]
1707 [1]
1706
1708
1707 Test TESTCASE variable
1709 Test TESTCASE variable
1708
1710
1709 $ cat > test-cases-ab.t <<'EOF'
1711 $ cat > test-cases-ab.t <<'EOF'
1710 > $ dostuff() {
1712 > $ dostuff() {
1711 > > echo "In case $TESTCASE"
1713 > > echo "In case $TESTCASE"
1712 > > }
1714 > > }
1713 > #testcases A B
1715 > #testcases A B
1714 > #if A
1716 > #if A
1715 > $ dostuff
1717 > $ dostuff
1716 > In case A
1718 > In case A
1717 > #endif
1719 > #endif
1718 > #if B
1720 > #if B
1719 > $ dostuff
1721 > $ dostuff
1720 > In case B
1722 > In case B
1721 > #endif
1723 > #endif
1722 > EOF
1724 > EOF
1723 $ rt test-cases-ab.t
1725 $ rt test-cases-ab.t
1724 running 2 tests using 1 parallel processes
1726 running 2 tests using 1 parallel processes
1725 ..
1727 ..
1726 # Ran 2 tests, 0 skipped, 0 failed.
1728 # Ran 2 tests, 0 skipped, 0 failed.
1727
1729
1728 Support running a specific test case
1730 Support running a specific test case
1729
1731
1730 $ rt "test-cases-abc.t#B"
1732 $ rt "test-cases-abc.t#B"
1731 running 1 tests using 1 parallel processes
1733 running 1 tests using 1 parallel processes
1732
1734
1733 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1735 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1734 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1736 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1735 @@ -7,7 +7,7 @@
1737 @@ -7,7 +7,7 @@
1736 $ V=C
1738 $ V=C
1737 #endif
1739 #endif
1738 $ echo $V | sed 's/A/C/'
1740 $ echo $V | sed 's/A/C/'
1739 - C
1741 - C
1740 + B
1742 + B
1741 #if C
1743 #if C
1742 $ [ $V = C ]
1744 $ [ $V = C ]
1743 #endif
1745 #endif
1744
1746
1745 ERROR: test-cases-abc.t#B output changed
1747 ERROR: test-cases-abc.t#B output changed
1746 !
1748 !
1747 Failed test-cases-abc.t#B: output changed
1749 Failed test-cases-abc.t#B: output changed
1748 # Ran 1 tests, 0 skipped, 1 failed.
1750 # Ran 1 tests, 0 skipped, 1 failed.
1749 python hash seed: * (glob)
1751 python hash seed: * (glob)
1750 [1]
1752 [1]
1751
1753
1752 Support running multiple test cases in the same file
1754 Support running multiple test cases in the same file
1753
1755
1754 $ rt test-cases-abc.t#B test-cases-abc.t#C
1756 $ rt test-cases-abc.t#B test-cases-abc.t#C
1755 running 2 tests using 1 parallel processes
1757 running 2 tests using 1 parallel processes
1756
1758
1757 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1759 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1758 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1760 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1759 @@ -7,7 +7,7 @@
1761 @@ -7,7 +7,7 @@
1760 $ V=C
1762 $ V=C
1761 #endif
1763 #endif
1762 $ echo $V | sed 's/A/C/'
1764 $ echo $V | sed 's/A/C/'
1763 - C
1765 - C
1764 + B
1766 + B
1765 #if C
1767 #if C
1766 $ [ $V = C ]
1768 $ [ $V = C ]
1767 #endif
1769 #endif
1768
1770
1769 ERROR: test-cases-abc.t#B output changed
1771 ERROR: test-cases-abc.t#B output changed
1770 !.
1772 !.
1771 Failed test-cases-abc.t#B: output changed
1773 Failed test-cases-abc.t#B: output changed
1772 # Ran 2 tests, 0 skipped, 1 failed.
1774 # Ran 2 tests, 0 skipped, 1 failed.
1773 python hash seed: * (glob)
1775 python hash seed: * (glob)
1774 [1]
1776 [1]
1775
1777
1776 Support ignoring invalid test cases
1778 Support ignoring invalid test cases
1777
1779
1778 $ rt test-cases-abc.t#B test-cases-abc.t#D
1780 $ rt test-cases-abc.t#B test-cases-abc.t#D
1779 running 1 tests using 1 parallel processes
1781 running 1 tests using 1 parallel processes
1780
1782
1781 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1783 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1782 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1784 +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
1783 @@ -7,7 +7,7 @@
1785 @@ -7,7 +7,7 @@
1784 $ V=C
1786 $ V=C
1785 #endif
1787 #endif
1786 $ echo $V | sed 's/A/C/'
1788 $ echo $V | sed 's/A/C/'
1787 - C
1789 - C
1788 + B
1790 + B
1789 #if C
1791 #if C
1790 $ [ $V = C ]
1792 $ [ $V = C ]
1791 #endif
1793 #endif
1792
1794
1793 ERROR: test-cases-abc.t#B output changed
1795 ERROR: test-cases-abc.t#B output changed
1794 !
1796 !
1795 Failed test-cases-abc.t#B: output changed
1797 Failed test-cases-abc.t#B: output changed
1796 # Ran 1 tests, 0 skipped, 1 failed.
1798 # Ran 1 tests, 0 skipped, 1 failed.
1797 python hash seed: * (glob)
1799 python hash seed: * (glob)
1798 [1]
1800 [1]
1799
1801
1800 Support running complex test cases names
1802 Support running complex test cases names
1801
1803
1802 $ cat > test-cases-advanced-cases.t <<'EOF'
1804 $ cat > test-cases-advanced-cases.t <<'EOF'
1803 > #testcases simple case-with-dashes casewith_-.chars
1805 > #testcases simple case-with-dashes casewith_-.chars
1804 > $ echo $TESTCASE
1806 > $ echo $TESTCASE
1805 > simple
1807 > simple
1806 > EOF
1808 > EOF
1807
1809
1808 $ cat test-cases-advanced-cases.t
1810 $ cat test-cases-advanced-cases.t
1809 #testcases simple case-with-dashes casewith_-.chars
1811 #testcases simple case-with-dashes casewith_-.chars
1810 $ echo $TESTCASE
1812 $ echo $TESTCASE
1811 simple
1813 simple
1812
1814
1813 $ rt test-cases-advanced-cases.t
1815 $ rt test-cases-advanced-cases.t
1814 running 3 tests using 1 parallel processes
1816 running 3 tests using 1 parallel processes
1815
1817
1816 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1818 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1817 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1819 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1818 @@ -1,3 +1,3 @@
1820 @@ -1,3 +1,3 @@
1819 #testcases simple case-with-dashes casewith_-.chars
1821 #testcases simple case-with-dashes casewith_-.chars
1820 $ echo $TESTCASE
1822 $ echo $TESTCASE
1821 - simple
1823 - simple
1822 + case-with-dashes
1824 + case-with-dashes
1823
1825
1824 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1826 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1825 !
1827 !
1826 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1828 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1827 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1829 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1828 @@ -1,3 +1,3 @@
1830 @@ -1,3 +1,3 @@
1829 #testcases simple case-with-dashes casewith_-.chars
1831 #testcases simple case-with-dashes casewith_-.chars
1830 $ echo $TESTCASE
1832 $ echo $TESTCASE
1831 - simple
1833 - simple
1832 + casewith_-.chars
1834 + casewith_-.chars
1833
1835
1834 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1836 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1835 !.
1837 !.
1836 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1838 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1837 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1839 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1838 # Ran 3 tests, 0 skipped, 2 failed.
1840 # Ran 3 tests, 0 skipped, 2 failed.
1839 python hash seed: * (glob)
1841 python hash seed: * (glob)
1840 [1]
1842 [1]
1841
1843
1842 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1844 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1843 running 1 tests using 1 parallel processes
1845 running 1 tests using 1 parallel processes
1844
1846
1845 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1847 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1846 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1848 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
1847 @@ -1,3 +1,3 @@
1849 @@ -1,3 +1,3 @@
1848 #testcases simple case-with-dashes casewith_-.chars
1850 #testcases simple case-with-dashes casewith_-.chars
1849 $ echo $TESTCASE
1851 $ echo $TESTCASE
1850 - simple
1852 - simple
1851 + case-with-dashes
1853 + case-with-dashes
1852
1854
1853 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1855 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1854 !
1856 !
1855 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1857 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1856 # Ran 1 tests, 0 skipped, 1 failed.
1858 # Ran 1 tests, 0 skipped, 1 failed.
1857 python hash seed: * (glob)
1859 python hash seed: * (glob)
1858 [1]
1860 [1]
1859
1861
1860 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1862 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1861 running 1 tests using 1 parallel processes
1863 running 1 tests using 1 parallel processes
1862
1864
1863 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1865 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1864 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1866 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
1865 @@ -1,3 +1,3 @@
1867 @@ -1,3 +1,3 @@
1866 #testcases simple case-with-dashes casewith_-.chars
1868 #testcases simple case-with-dashes casewith_-.chars
1867 $ echo $TESTCASE
1869 $ echo $TESTCASE
1868 - simple
1870 - simple
1869 + casewith_-.chars
1871 + casewith_-.chars
1870
1872
1871 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1873 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1872 !
1874 !
1873 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1875 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1874 # Ran 1 tests, 0 skipped, 1 failed.
1876 # Ran 1 tests, 0 skipped, 1 failed.
1875 python hash seed: * (glob)
1877 python hash seed: * (glob)
1876 [1]
1878 [1]
1877
1879
1878 Test automatic pattern replacement
1880 Test automatic pattern replacement
1879 ==================================
1881 ==================================
1880
1882
1881 $ cat << EOF >> common-pattern.py
1883 $ cat << EOF >> common-pattern.py
1882 > substitutions = [
1884 > substitutions = [
1883 > (br'foo-(.*)\\b',
1885 > (br'foo-(.*)\\b',
1884 > br'\$XXX=\\1\$'),
1886 > br'\$XXX=\\1\$'),
1885 > (br'bar\\n',
1887 > (br'bar\\n',
1886 > br'\$YYY$\\n'),
1888 > br'\$YYY$\\n'),
1887 > ]
1889 > ]
1888 > EOF
1890 > EOF
1889
1891
1890 $ cat << EOF >> test-substitution.t
1892 $ cat << EOF >> test-substitution.t
1891 > $ echo foo-12
1893 > $ echo foo-12
1892 > \$XXX=12$
1894 > \$XXX=12$
1893 > $ echo foo-42
1895 > $ echo foo-42
1894 > \$XXX=42$
1896 > \$XXX=42$
1895 > $ echo bar prior
1897 > $ echo bar prior
1896 > bar prior
1898 > bar prior
1897 > $ echo lastbar
1899 > $ echo lastbar
1898 > last\$YYY$
1900 > last\$YYY$
1899 > $ echo foo-bar foo-baz
1901 > $ echo foo-bar foo-baz
1900 > EOF
1902 > EOF
1901
1903
1902 $ rt test-substitution.t
1904 $ rt test-substitution.t
1903 running 1 tests using 1 parallel processes
1905 running 1 tests using 1 parallel processes
1904
1906
1905 --- $TESTTMP/anothertests/cases/test-substitution.t
1907 --- $TESTTMP/anothertests/cases/test-substitution.t
1906 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1908 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1907 @@ -7,3 +7,4 @@
1909 @@ -7,3 +7,4 @@
1908 $ echo lastbar
1910 $ echo lastbar
1909 last$YYY$
1911 last$YYY$
1910 $ echo foo-bar foo-baz
1912 $ echo foo-bar foo-baz
1911 + $XXX=bar foo-baz$
1913 + $XXX=bar foo-baz$
1912
1914
1913 ERROR: test-substitution.t output changed
1915 ERROR: test-substitution.t output changed
1914 !
1916 !
1915 Failed test-substitution.t: output changed
1917 Failed test-substitution.t: output changed
1916 # Ran 1 tests, 0 skipped, 1 failed.
1918 # Ran 1 tests, 0 skipped, 1 failed.
1917 python hash seed: * (glob)
1919 python hash seed: * (glob)
1918 [1]
1920 [1]
1919
1921
1920 --extra-config-opt works
1922 --extra-config-opt works
1921
1923
1922 $ cat << EOF >> test-config-opt.t
1924 $ cat << EOF >> test-config-opt.t
1923 > $ hg init test-config-opt
1925 > $ hg init test-config-opt
1924 > $ hg -R test-config-opt purge
1926 > $ hg -R test-config-opt purge
1925 > EOF
1927 > EOF
1926
1928
1927 $ rt --extra-config-opt extensions.purge= test-config-opt.t
1929 $ rt --extra-config-opt extensions.purge= test-config-opt.t
1928 running 1 tests using 1 parallel processes
1930 running 1 tests using 1 parallel processes
1929 .
1931 .
1930 # Ran 1 tests, 0 skipped, 0 failed.
1932 # Ran 1 tests, 0 skipped, 0 failed.
General Comments 0
You need to be logged in to leave comments. Login now