##// END OF EJS Templates
tests: don't allow reodering of glob/re lines across non-glob/re lines...
Martin von Zweigbergk -
r38572:f83600ef default
parent child Browse files
Show More
@@ -1,3132 +1,3137 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 os
54 import os
55 import random
55 import random
56 import re
56 import re
57 import shutil
57 import shutil
58 import signal
58 import signal
59 import socket
59 import socket
60 import subprocess
60 import subprocess
61 import sys
61 import sys
62 import sysconfig
62 import sysconfig
63 import tempfile
63 import tempfile
64 import threading
64 import threading
65 import time
65 import time
66 import unittest
66 import unittest
67 import xml.dom.minidom as minidom
67 import xml.dom.minidom as minidom
68
68
69 try:
69 try:
70 import Queue as queue
70 import Queue as queue
71 except ImportError:
71 except ImportError:
72 import queue
72 import queue
73
73
74 try:
74 try:
75 import shlex
75 import shlex
76 shellquote = shlex.quote
76 shellquote = shlex.quote
77 except (ImportError, AttributeError):
77 except (ImportError, AttributeError):
78 import pipes
78 import pipes
79 shellquote = pipes.quote
79 shellquote = pipes.quote
80
80
81 if os.environ.get('RTUNICODEPEDANTRY', False):
81 if os.environ.get('RTUNICODEPEDANTRY', False):
82 try:
82 try:
83 reload(sys)
83 reload(sys)
84 sys.setdefaultencoding("undefined")
84 sys.setdefaultencoding("undefined")
85 except NameError:
85 except NameError:
86 pass
86 pass
87
87
88 origenviron = os.environ.copy()
88 origenviron = os.environ.copy()
89 osenvironb = getattr(os, 'environb', os.environ)
89 osenvironb = getattr(os, 'environb', os.environ)
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 if sys.version_info > (3, 5, 0):
143 if sys.version_info > (3, 5, 0):
144 PYTHON3 = True
144 PYTHON3 = True
145 xrange = range # we use xrange in one place, and we'd rather not use range
145 xrange = range # we use xrange in one place, and we'd rather not use range
146 def _bytespath(p):
146 def _bytespath(p):
147 if p is None:
147 if p is None:
148 return p
148 return p
149 return p.encode('utf-8')
149 return p.encode('utf-8')
150
150
151 def _strpath(p):
151 def _strpath(p):
152 if p is None:
152 if p is None:
153 return p
153 return p
154 return p.decode('utf-8')
154 return p.decode('utf-8')
155
155
156 elif sys.version_info >= (3, 0, 0):
156 elif sys.version_info >= (3, 0, 0):
157 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
157 print('%s is only supported on Python 3.5+ and 2.7, not %s' %
158 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
158 (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
159 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
159 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
160 else:
160 else:
161 PYTHON3 = False
161 PYTHON3 = False
162
162
163 # In python 2.x, path operations are generally done using
163 # In python 2.x, path operations are generally done using
164 # bytestrings by default, so we don't have to do any extra
164 # bytestrings by default, so we don't have to do any extra
165 # fiddling there. We define the wrapper functions anyway just to
165 # fiddling there. We define the wrapper functions anyway just to
166 # help keep code consistent between platforms.
166 # help keep code consistent between platforms.
167 def _bytespath(p):
167 def _bytespath(p):
168 return p
168 return p
169
169
170 _strpath = _bytespath
170 _strpath = _bytespath
171
171
172 # For Windows support
172 # For Windows support
173 wifexited = getattr(os, "WIFEXITED", lambda x: False)
173 wifexited = getattr(os, "WIFEXITED", lambda x: False)
174
174
175 # Whether to use IPv6
175 # Whether to use IPv6
176 def checksocketfamily(name, port=20058):
176 def checksocketfamily(name, port=20058):
177 """return true if we can listen on localhost using family=name
177 """return true if we can listen on localhost using family=name
178
178
179 name should be either 'AF_INET', or 'AF_INET6'.
179 name should be either 'AF_INET', or 'AF_INET6'.
180 port being used is okay - EADDRINUSE is considered as successful.
180 port being used is okay - EADDRINUSE is considered as successful.
181 """
181 """
182 family = getattr(socket, name, None)
182 family = getattr(socket, name, None)
183 if family is None:
183 if family is None:
184 return False
184 return False
185 try:
185 try:
186 s = socket.socket(family, socket.SOCK_STREAM)
186 s = socket.socket(family, socket.SOCK_STREAM)
187 s.bind(('localhost', port))
187 s.bind(('localhost', port))
188 s.close()
188 s.close()
189 return True
189 return True
190 except socket.error as exc:
190 except socket.error as exc:
191 if exc.errno == errno.EADDRINUSE:
191 if exc.errno == errno.EADDRINUSE:
192 return True
192 return True
193 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
193 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
194 return False
194 return False
195 else:
195 else:
196 raise
196 raise
197 else:
197 else:
198 return False
198 return False
199
199
200 # useipv6 will be set by parseargs
200 # useipv6 will be set by parseargs
201 useipv6 = None
201 useipv6 = None
202
202
203 def checkportisavailable(port):
203 def checkportisavailable(port):
204 """return true if a port seems free to bind on localhost"""
204 """return true if a port seems free to bind on localhost"""
205 if useipv6:
205 if useipv6:
206 family = socket.AF_INET6
206 family = socket.AF_INET6
207 else:
207 else:
208 family = socket.AF_INET
208 family = socket.AF_INET
209 try:
209 try:
210 s = socket.socket(family, socket.SOCK_STREAM)
210 s = socket.socket(family, socket.SOCK_STREAM)
211 s.bind(('localhost', port))
211 s.bind(('localhost', port))
212 s.close()
212 s.close()
213 return True
213 return True
214 except socket.error as exc:
214 except socket.error as exc:
215 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
215 if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL,
216 errno.EPROTONOSUPPORT):
216 errno.EPROTONOSUPPORT):
217 raise
217 raise
218 return False
218 return False
219
219
220 closefds = os.name == 'posix'
220 closefds = os.name == 'posix'
221 def Popen4(cmd, wd, timeout, env=None):
221 def Popen4(cmd, wd, timeout, env=None):
222 processlock.acquire()
222 processlock.acquire()
223 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
223 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
224 close_fds=closefds,
224 close_fds=closefds,
225 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
225 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
226 stderr=subprocess.STDOUT)
226 stderr=subprocess.STDOUT)
227 processlock.release()
227 processlock.release()
228
228
229 p.fromchild = p.stdout
229 p.fromchild = p.stdout
230 p.tochild = p.stdin
230 p.tochild = p.stdin
231 p.childerr = p.stderr
231 p.childerr = p.stderr
232
232
233 p.timeout = False
233 p.timeout = False
234 if timeout:
234 if timeout:
235 def t():
235 def t():
236 start = time.time()
236 start = time.time()
237 while time.time() - start < timeout and p.returncode is None:
237 while time.time() - start < timeout and p.returncode is None:
238 time.sleep(.1)
238 time.sleep(.1)
239 p.timeout = True
239 p.timeout = True
240 if p.returncode is None:
240 if p.returncode is None:
241 terminate(p)
241 terminate(p)
242 threading.Thread(target=t).start()
242 threading.Thread(target=t).start()
243
243
244 return p
244 return p
245
245
246 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
246 PYTHON = _bytespath(sys.executable.replace('\\', '/'))
247 IMPL_PATH = b'PYTHONPATH'
247 IMPL_PATH = b'PYTHONPATH'
248 if 'java' in sys.platform:
248 if 'java' in sys.platform:
249 IMPL_PATH = b'JYTHONPATH'
249 IMPL_PATH = b'JYTHONPATH'
250
250
251 defaults = {
251 defaults = {
252 'jobs': ('HGTEST_JOBS', 1),
252 'jobs': ('HGTEST_JOBS', 1),
253 'timeout': ('HGTEST_TIMEOUT', 180),
253 'timeout': ('HGTEST_TIMEOUT', 180),
254 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
254 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500),
255 'port': ('HGTEST_PORT', 20059),
255 'port': ('HGTEST_PORT', 20059),
256 'shell': ('HGTEST_SHELL', 'sh'),
256 'shell': ('HGTEST_SHELL', 'sh'),
257 }
257 }
258
258
259 def canonpath(path):
259 def canonpath(path):
260 return os.path.realpath(os.path.expanduser(path))
260 return os.path.realpath(os.path.expanduser(path))
261
261
262 def parselistfiles(files, listtype, warn=True):
262 def parselistfiles(files, listtype, warn=True):
263 entries = dict()
263 entries = dict()
264 for filename in files:
264 for filename in files:
265 try:
265 try:
266 path = os.path.expanduser(os.path.expandvars(filename))
266 path = os.path.expanduser(os.path.expandvars(filename))
267 f = open(path, "rb")
267 f = open(path, "rb")
268 except IOError as err:
268 except IOError as err:
269 if err.errno != errno.ENOENT:
269 if err.errno != errno.ENOENT:
270 raise
270 raise
271 if warn:
271 if warn:
272 print("warning: no such %s file: %s" % (listtype, filename))
272 print("warning: no such %s file: %s" % (listtype, filename))
273 continue
273 continue
274
274
275 for line in f.readlines():
275 for line in f.readlines():
276 line = line.split(b'#', 1)[0].strip()
276 line = line.split(b'#', 1)[0].strip()
277 if line:
277 if line:
278 entries[line] = filename
278 entries[line] = filename
279
279
280 f.close()
280 f.close()
281 return entries
281 return entries
282
282
283 def parsettestcases(path):
283 def parsettestcases(path):
284 """read a .t test file, return a set of test case names
284 """read a .t test file, return a set of test case names
285
285
286 If path does not exist, return an empty set.
286 If path does not exist, return an empty set.
287 """
287 """
288 cases = set()
288 cases = set()
289 try:
289 try:
290 with open(path, 'rb') as f:
290 with open(path, 'rb') as f:
291 for l in f:
291 for l in f:
292 if l.startswith(b'#testcases '):
292 if l.startswith(b'#testcases '):
293 cases.update(l[11:].split())
293 cases.update(l[11:].split())
294 except IOError as ex:
294 except IOError as ex:
295 if ex.errno != errno.ENOENT:
295 if ex.errno != errno.ENOENT:
296 raise
296 raise
297 return cases
297 return cases
298
298
299 def getparser():
299 def getparser():
300 """Obtain the OptionParser used by the CLI."""
300 """Obtain the OptionParser used by the CLI."""
301 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
301 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
302
302
303 selection = parser.add_argument_group('Test Selection')
303 selection = parser.add_argument_group('Test Selection')
304 selection.add_argument('--allow-slow-tests', action='store_true',
304 selection.add_argument('--allow-slow-tests', action='store_true',
305 help='allow extremely slow tests')
305 help='allow extremely slow tests')
306 selection.add_argument("--blacklist", action="append",
306 selection.add_argument("--blacklist", action="append",
307 help="skip tests listed in the specified blacklist file")
307 help="skip tests listed in the specified blacklist file")
308 selection.add_argument("--changed",
308 selection.add_argument("--changed",
309 help="run tests that are changed in parent rev or working directory")
309 help="run tests that are changed in parent rev or working directory")
310 selection.add_argument("-k", "--keywords",
310 selection.add_argument("-k", "--keywords",
311 help="run tests matching keywords")
311 help="run tests matching keywords")
312 selection.add_argument("-r", "--retest", action="store_true",
312 selection.add_argument("-r", "--retest", action="store_true",
313 help = "retest failed tests")
313 help = "retest failed tests")
314 selection.add_argument("--test-list", action="append",
314 selection.add_argument("--test-list", action="append",
315 help="read tests to run from the specified file")
315 help="read tests to run from the specified file")
316 selection.add_argument("--whitelist", action="append",
316 selection.add_argument("--whitelist", action="append",
317 help="always run tests listed in the specified whitelist file")
317 help="always run tests listed in the specified whitelist file")
318 selection.add_argument('tests', metavar='TESTS', nargs='*',
318 selection.add_argument('tests', metavar='TESTS', nargs='*',
319 help='Tests to run')
319 help='Tests to run')
320
320
321 harness = parser.add_argument_group('Test Harness Behavior')
321 harness = parser.add_argument_group('Test Harness Behavior')
322 harness.add_argument('--bisect-repo',
322 harness.add_argument('--bisect-repo',
323 metavar='bisect_repo',
323 metavar='bisect_repo',
324 help=("Path of a repo to bisect. Use together with "
324 help=("Path of a repo to bisect. Use together with "
325 "--known-good-rev"))
325 "--known-good-rev"))
326 harness.add_argument("-d", "--debug", action="store_true",
326 harness.add_argument("-d", "--debug", action="store_true",
327 help="debug mode: write output of test scripts to console"
327 help="debug mode: write output of test scripts to console"
328 " rather than capturing and diffing it (disables timeout)")
328 " rather than capturing and diffing it (disables timeout)")
329 harness.add_argument("-f", "--first", action="store_true",
329 harness.add_argument("-f", "--first", action="store_true",
330 help="exit on the first test failure")
330 help="exit on the first test failure")
331 harness.add_argument("-i", "--interactive", action="store_true",
331 harness.add_argument("-i", "--interactive", action="store_true",
332 help="prompt to accept changed output")
332 help="prompt to accept changed output")
333 harness.add_argument("-j", "--jobs", type=int,
333 harness.add_argument("-j", "--jobs", type=int,
334 help="number of jobs to run in parallel"
334 help="number of jobs to run in parallel"
335 " (default: $%s or %d)" % defaults['jobs'])
335 " (default: $%s or %d)" % defaults['jobs'])
336 harness.add_argument("--keep-tmpdir", action="store_true",
336 harness.add_argument("--keep-tmpdir", action="store_true",
337 help="keep temporary directory after running tests")
337 help="keep temporary directory after running tests")
338 harness.add_argument('--known-good-rev',
338 harness.add_argument('--known-good-rev',
339 metavar="known_good_rev",
339 metavar="known_good_rev",
340 help=("Automatically bisect any failures using this "
340 help=("Automatically bisect any failures using this "
341 "revision as a known-good revision."))
341 "revision as a known-good revision."))
342 harness.add_argument("--list-tests", action="store_true",
342 harness.add_argument("--list-tests", action="store_true",
343 help="list tests instead of running them")
343 help="list tests instead of running them")
344 harness.add_argument("--loop", action="store_true",
344 harness.add_argument("--loop", action="store_true",
345 help="loop tests repeatedly")
345 help="loop tests repeatedly")
346 harness.add_argument('--random', action="store_true",
346 harness.add_argument('--random', action="store_true",
347 help='run tests in random order')
347 help='run tests in random order')
348 harness.add_argument('--order-by-runtime', action="store_true",
348 harness.add_argument('--order-by-runtime', action="store_true",
349 help='run slowest tests first, according to .testtimes')
349 help='run slowest tests first, according to .testtimes')
350 harness.add_argument("-p", "--port", type=int,
350 harness.add_argument("-p", "--port", type=int,
351 help="port on which servers should listen"
351 help="port on which servers should listen"
352 " (default: $%s or %d)" % defaults['port'])
352 " (default: $%s or %d)" % defaults['port'])
353 harness.add_argument('--profile-runner', action='store_true',
353 harness.add_argument('--profile-runner', action='store_true',
354 help='run statprof on run-tests')
354 help='run statprof on run-tests')
355 harness.add_argument("-R", "--restart", action="store_true",
355 harness.add_argument("-R", "--restart", action="store_true",
356 help="restart at last error")
356 help="restart at last error")
357 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
357 harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
358 help="run each test N times (default=1)", default=1)
358 help="run each test N times (default=1)", default=1)
359 harness.add_argument("--shell",
359 harness.add_argument("--shell",
360 help="shell to use (default: $%s or %s)" % defaults['shell'])
360 help="shell to use (default: $%s or %s)" % defaults['shell'])
361 harness.add_argument('--showchannels', action='store_true',
361 harness.add_argument('--showchannels', action='store_true',
362 help='show scheduling channels')
362 help='show scheduling channels')
363 harness.add_argument("--slowtimeout", type=int,
363 harness.add_argument("--slowtimeout", type=int,
364 help="kill errant slow tests after SLOWTIMEOUT seconds"
364 help="kill errant slow tests after SLOWTIMEOUT seconds"
365 " (default: $%s or %d)" % defaults['slowtimeout'])
365 " (default: $%s or %d)" % defaults['slowtimeout'])
366 harness.add_argument("-t", "--timeout", type=int,
366 harness.add_argument("-t", "--timeout", type=int,
367 help="kill errant tests after TIMEOUT seconds"
367 help="kill errant tests after TIMEOUT seconds"
368 " (default: $%s or %d)" % defaults['timeout'])
368 " (default: $%s or %d)" % defaults['timeout'])
369 harness.add_argument("--tmpdir",
369 harness.add_argument("--tmpdir",
370 help="run tests in the given temporary directory"
370 help="run tests in the given temporary directory"
371 " (implies --keep-tmpdir)")
371 " (implies --keep-tmpdir)")
372 harness.add_argument("-v", "--verbose", action="store_true",
372 harness.add_argument("-v", "--verbose", action="store_true",
373 help="output verbose messages")
373 help="output verbose messages")
374
374
375 hgconf = parser.add_argument_group('Mercurial Configuration')
375 hgconf = parser.add_argument_group('Mercurial Configuration')
376 hgconf.add_argument("--chg", action="store_true",
376 hgconf.add_argument("--chg", action="store_true",
377 help="install and use chg wrapper in place of hg")
377 help="install and use chg wrapper in place of hg")
378 hgconf.add_argument("--compiler",
378 hgconf.add_argument("--compiler",
379 help="compiler to build with")
379 help="compiler to build with")
380 hgconf.add_argument('--extra-config-opt', action="append", default=[],
380 hgconf.add_argument('--extra-config-opt', action="append", default=[],
381 help='set the given config opt in the test hgrc')
381 help='set the given config opt in the test hgrc')
382 hgconf.add_argument("-l", "--local", action="store_true",
382 hgconf.add_argument("-l", "--local", action="store_true",
383 help="shortcut for --with-hg=<testdir>/../hg, "
383 help="shortcut for --with-hg=<testdir>/../hg, "
384 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
384 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
385 hgconf.add_argument("--ipv6", action="store_true",
385 hgconf.add_argument("--ipv6", action="store_true",
386 help="prefer IPv6 to IPv4 for network related tests")
386 help="prefer IPv6 to IPv4 for network related tests")
387 hgconf.add_argument("--pure", action="store_true",
387 hgconf.add_argument("--pure", action="store_true",
388 help="use pure Python code instead of C extensions")
388 help="use pure Python code instead of C extensions")
389 hgconf.add_argument("-3", "--py3k-warnings", action="store_true",
389 hgconf.add_argument("-3", "--py3k-warnings", action="store_true",
390 help="enable Py3k warnings on Python 2.7+")
390 help="enable Py3k warnings on Python 2.7+")
391 hgconf.add_argument("--with-chg", metavar="CHG",
391 hgconf.add_argument("--with-chg", metavar="CHG",
392 help="use specified chg wrapper in place of hg")
392 help="use specified chg wrapper in place of hg")
393 hgconf.add_argument("--with-hg",
393 hgconf.add_argument("--with-hg",
394 metavar="HG",
394 metavar="HG",
395 help="test using specified hg script rather than a "
395 help="test using specified hg script rather than a "
396 "temporary installation")
396 "temporary installation")
397 # This option should be deleted once test-check-py3-compat.t and other
397 # This option should be deleted once test-check-py3-compat.t and other
398 # Python 3 tests run with Python 3.
398 # Python 3 tests run with Python 3.
399 hgconf.add_argument("--with-python3", metavar="PYTHON3",
399 hgconf.add_argument("--with-python3", metavar="PYTHON3",
400 help="Python 3 interpreter (if running under Python 2)"
400 help="Python 3 interpreter (if running under Python 2)"
401 " (TEMPORARY)")
401 " (TEMPORARY)")
402
402
403 reporting = parser.add_argument_group('Results Reporting')
403 reporting = parser.add_argument_group('Results Reporting')
404 reporting.add_argument("-C", "--annotate", action="store_true",
404 reporting.add_argument("-C", "--annotate", action="store_true",
405 help="output files annotated with coverage")
405 help="output files annotated with coverage")
406 reporting.add_argument("--color", choices=["always", "auto", "never"],
406 reporting.add_argument("--color", choices=["always", "auto", "never"],
407 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
407 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
408 help="colorisation: always|auto|never (default: auto)")
408 help="colorisation: always|auto|never (default: auto)")
409 reporting.add_argument("-c", "--cover", action="store_true",
409 reporting.add_argument("-c", "--cover", action="store_true",
410 help="print a test coverage report")
410 help="print a test coverage report")
411 reporting.add_argument('--exceptions', action='store_true',
411 reporting.add_argument('--exceptions', action='store_true',
412 help='log all exceptions and generate an exception report')
412 help='log all exceptions and generate an exception report')
413 reporting.add_argument("-H", "--htmlcov", action="store_true",
413 reporting.add_argument("-H", "--htmlcov", action="store_true",
414 help="create an HTML report of the coverage of the files")
414 help="create an HTML report of the coverage of the files")
415 reporting.add_argument("--json", action="store_true",
415 reporting.add_argument("--json", action="store_true",
416 help="store test result data in 'report.json' file")
416 help="store test result data in 'report.json' file")
417 reporting.add_argument("--outputdir",
417 reporting.add_argument("--outputdir",
418 help="directory to write error logs to (default=test directory)")
418 help="directory to write error logs to (default=test directory)")
419 reporting.add_argument("-n", "--nodiff", action="store_true",
419 reporting.add_argument("-n", "--nodiff", action="store_true",
420 help="skip showing test changes")
420 help="skip showing test changes")
421 reporting.add_argument("-S", "--noskips", action="store_true",
421 reporting.add_argument("-S", "--noskips", action="store_true",
422 help="don't report skip tests verbosely")
422 help="don't report skip tests verbosely")
423 reporting.add_argument("--time", action="store_true",
423 reporting.add_argument("--time", action="store_true",
424 help="time how long each test takes")
424 help="time how long each test takes")
425 reporting.add_argument("--view",
425 reporting.add_argument("--view",
426 help="external diff viewer")
426 help="external diff viewer")
427 reporting.add_argument("--xunit",
427 reporting.add_argument("--xunit",
428 help="record xunit results at specified path")
428 help="record xunit results at specified path")
429
429
430 for option, (envvar, default) in defaults.items():
430 for option, (envvar, default) in defaults.items():
431 defaults[option] = type(default)(os.environ.get(envvar, default))
431 defaults[option] = type(default)(os.environ.get(envvar, default))
432 parser.set_defaults(**defaults)
432 parser.set_defaults(**defaults)
433
433
434 return parser
434 return parser
435
435
436 def parseargs(args, parser):
436 def parseargs(args, parser):
437 """Parse arguments with our OptionParser and validate results."""
437 """Parse arguments with our OptionParser and validate results."""
438 options = parser.parse_args(args)
438 options = parser.parse_args(args)
439
439
440 # jython is always pure
440 # jython is always pure
441 if 'java' in sys.platform or '__pypy__' in sys.modules:
441 if 'java' in sys.platform or '__pypy__' in sys.modules:
442 options.pure = True
442 options.pure = True
443
443
444 if options.with_hg:
444 if options.with_hg:
445 options.with_hg = canonpath(_bytespath(options.with_hg))
445 options.with_hg = canonpath(_bytespath(options.with_hg))
446 if not (os.path.isfile(options.with_hg) and
446 if not (os.path.isfile(options.with_hg) and
447 os.access(options.with_hg, os.X_OK)):
447 os.access(options.with_hg, os.X_OK)):
448 parser.error('--with-hg must specify an executable hg script')
448 parser.error('--with-hg must specify an executable hg script')
449 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
449 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
450 sys.stderr.write('warning: --with-hg should specify an hg script\n')
450 sys.stderr.write('warning: --with-hg should specify an hg script\n')
451 if options.local:
451 if options.local:
452 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
452 testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0])))
453 reporootdir = os.path.dirname(testdir)
453 reporootdir = os.path.dirname(testdir)
454 pathandattrs = [(b'hg', 'with_hg')]
454 pathandattrs = [(b'hg', 'with_hg')]
455 if options.chg:
455 if options.chg:
456 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
456 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
457 for relpath, attr in pathandattrs:
457 for relpath, attr in pathandattrs:
458 binpath = os.path.join(reporootdir, relpath)
458 binpath = os.path.join(reporootdir, relpath)
459 if os.name != 'nt' and not os.access(binpath, os.X_OK):
459 if os.name != 'nt' and not os.access(binpath, os.X_OK):
460 parser.error('--local specified, but %r not found or '
460 parser.error('--local specified, but %r not found or '
461 'not executable' % binpath)
461 'not executable' % binpath)
462 setattr(options, attr, binpath)
462 setattr(options, attr, binpath)
463
463
464 if (options.chg or options.with_chg) and os.name == 'nt':
464 if (options.chg or options.with_chg) and os.name == 'nt':
465 parser.error('chg does not work on %s' % os.name)
465 parser.error('chg does not work on %s' % os.name)
466 if options.with_chg:
466 if options.with_chg:
467 options.chg = False # no installation to temporary location
467 options.chg = False # no installation to temporary location
468 options.with_chg = canonpath(_bytespath(options.with_chg))
468 options.with_chg = canonpath(_bytespath(options.with_chg))
469 if not (os.path.isfile(options.with_chg) and
469 if not (os.path.isfile(options.with_chg) and
470 os.access(options.with_chg, os.X_OK)):
470 os.access(options.with_chg, os.X_OK)):
471 parser.error('--with-chg must specify a chg executable')
471 parser.error('--with-chg must specify a chg executable')
472 if options.chg and options.with_hg:
472 if options.chg and options.with_hg:
473 # chg shares installation location with hg
473 # chg shares installation location with hg
474 parser.error('--chg does not work when --with-hg is specified '
474 parser.error('--chg does not work when --with-hg is specified '
475 '(use --with-chg instead)')
475 '(use --with-chg instead)')
476
476
477 if options.color == 'always' and not pygmentspresent:
477 if options.color == 'always' and not pygmentspresent:
478 sys.stderr.write('warning: --color=always ignored because '
478 sys.stderr.write('warning: --color=always ignored because '
479 'pygments is not installed\n')
479 'pygments is not installed\n')
480
480
481 if options.bisect_repo and not options.known_good_rev:
481 if options.bisect_repo and not options.known_good_rev:
482 parser.error("--bisect-repo cannot be used without --known-good-rev")
482 parser.error("--bisect-repo cannot be used without --known-good-rev")
483
483
484 global useipv6
484 global useipv6
485 if options.ipv6:
485 if options.ipv6:
486 useipv6 = checksocketfamily('AF_INET6')
486 useipv6 = checksocketfamily('AF_INET6')
487 else:
487 else:
488 # only use IPv6 if IPv4 is unavailable and IPv6 is available
488 # only use IPv6 if IPv4 is unavailable and IPv6 is available
489 useipv6 = ((not checksocketfamily('AF_INET'))
489 useipv6 = ((not checksocketfamily('AF_INET'))
490 and checksocketfamily('AF_INET6'))
490 and checksocketfamily('AF_INET6'))
491
491
492 options.anycoverage = options.cover or options.annotate or options.htmlcov
492 options.anycoverage = options.cover or options.annotate or options.htmlcov
493 if options.anycoverage:
493 if options.anycoverage:
494 try:
494 try:
495 import coverage
495 import coverage
496 covver = version.StrictVersion(coverage.__version__).version
496 covver = version.StrictVersion(coverage.__version__).version
497 if covver < (3, 3):
497 if covver < (3, 3):
498 parser.error('coverage options require coverage 3.3 or later')
498 parser.error('coverage options require coverage 3.3 or later')
499 except ImportError:
499 except ImportError:
500 parser.error('coverage options now require the coverage package')
500 parser.error('coverage options now require the coverage package')
501
501
502 if options.anycoverage and options.local:
502 if options.anycoverage and options.local:
503 # this needs some path mangling somewhere, I guess
503 # this needs some path mangling somewhere, I guess
504 parser.error("sorry, coverage options do not work when --local "
504 parser.error("sorry, coverage options do not work when --local "
505 "is specified")
505 "is specified")
506
506
507 if options.anycoverage and options.with_hg:
507 if options.anycoverage and options.with_hg:
508 parser.error("sorry, coverage options do not work when --with-hg "
508 parser.error("sorry, coverage options do not work when --with-hg "
509 "is specified")
509 "is specified")
510
510
511 global verbose
511 global verbose
512 if options.verbose:
512 if options.verbose:
513 verbose = ''
513 verbose = ''
514
514
515 if options.tmpdir:
515 if options.tmpdir:
516 options.tmpdir = canonpath(options.tmpdir)
516 options.tmpdir = canonpath(options.tmpdir)
517
517
518 if options.jobs < 1:
518 if options.jobs < 1:
519 parser.error('--jobs must be positive')
519 parser.error('--jobs must be positive')
520 if options.interactive and options.debug:
520 if options.interactive and options.debug:
521 parser.error("-i/--interactive and -d/--debug are incompatible")
521 parser.error("-i/--interactive and -d/--debug are incompatible")
522 if options.debug:
522 if options.debug:
523 if options.timeout != defaults['timeout']:
523 if options.timeout != defaults['timeout']:
524 sys.stderr.write(
524 sys.stderr.write(
525 'warning: --timeout option ignored with --debug\n')
525 'warning: --timeout option ignored with --debug\n')
526 if options.slowtimeout != defaults['slowtimeout']:
526 if options.slowtimeout != defaults['slowtimeout']:
527 sys.stderr.write(
527 sys.stderr.write(
528 'warning: --slowtimeout option ignored with --debug\n')
528 'warning: --slowtimeout option ignored with --debug\n')
529 options.timeout = 0
529 options.timeout = 0
530 options.slowtimeout = 0
530 options.slowtimeout = 0
531 if options.py3k_warnings:
531 if options.py3k_warnings:
532 if PYTHON3:
532 if PYTHON3:
533 parser.error(
533 parser.error(
534 '--py3k-warnings can only be used on Python 2.7')
534 '--py3k-warnings can only be used on Python 2.7')
535 if options.with_python3:
535 if options.with_python3:
536 if PYTHON3:
536 if PYTHON3:
537 parser.error('--with-python3 cannot be used when executing with '
537 parser.error('--with-python3 cannot be used when executing with '
538 'Python 3')
538 'Python 3')
539
539
540 options.with_python3 = canonpath(options.with_python3)
540 options.with_python3 = canonpath(options.with_python3)
541 # Verify Python3 executable is acceptable.
541 # Verify Python3 executable is acceptable.
542 proc = subprocess.Popen([options.with_python3, b'--version'],
542 proc = subprocess.Popen([options.with_python3, b'--version'],
543 stdout=subprocess.PIPE,
543 stdout=subprocess.PIPE,
544 stderr=subprocess.STDOUT)
544 stderr=subprocess.STDOUT)
545 out, _err = proc.communicate()
545 out, _err = proc.communicate()
546 ret = proc.wait()
546 ret = proc.wait()
547 if ret != 0:
547 if ret != 0:
548 parser.error('could not determine version of python 3')
548 parser.error('could not determine version of python 3')
549 if not out.startswith('Python '):
549 if not out.startswith('Python '):
550 parser.error('unexpected output from python3 --version: %s' %
550 parser.error('unexpected output from python3 --version: %s' %
551 out)
551 out)
552 vers = version.LooseVersion(out[len('Python '):])
552 vers = version.LooseVersion(out[len('Python '):])
553 if vers < version.LooseVersion('3.5.0'):
553 if vers < version.LooseVersion('3.5.0'):
554 parser.error('--with-python3 version must be 3.5.0 or greater; '
554 parser.error('--with-python3 version must be 3.5.0 or greater; '
555 'got %s' % out)
555 'got %s' % out)
556
556
557 if options.blacklist:
557 if options.blacklist:
558 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
558 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
559 if options.whitelist:
559 if options.whitelist:
560 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
560 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
561 else:
561 else:
562 options.whitelisted = {}
562 options.whitelisted = {}
563
563
564 if options.showchannels:
564 if options.showchannels:
565 options.nodiff = True
565 options.nodiff = True
566
566
567 return options
567 return options
568
568
569 def rename(src, dst):
569 def rename(src, dst):
570 """Like os.rename(), trade atomicity and opened files friendliness
570 """Like os.rename(), trade atomicity and opened files friendliness
571 for existing destination support.
571 for existing destination support.
572 """
572 """
573 shutil.copy(src, dst)
573 shutil.copy(src, dst)
574 os.remove(src)
574 os.remove(src)
575
575
576 _unified_diff = difflib.unified_diff
576 _unified_diff = difflib.unified_diff
577 if PYTHON3:
577 if PYTHON3:
578 import functools
578 import functools
579 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
579 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
580
580
581 def getdiff(expected, output, ref, err):
581 def getdiff(expected, output, ref, err):
582 servefail = False
582 servefail = False
583 lines = []
583 lines = []
584 for line in _unified_diff(expected, output, ref, err):
584 for line in _unified_diff(expected, output, ref, err):
585 if line.startswith(b'+++') or line.startswith(b'---'):
585 if line.startswith(b'+++') or line.startswith(b'---'):
586 line = line.replace(b'\\', b'/')
586 line = line.replace(b'\\', b'/')
587 if line.endswith(b' \n'):
587 if line.endswith(b' \n'):
588 line = line[:-2] + b'\n'
588 line = line[:-2] + b'\n'
589 lines.append(line)
589 lines.append(line)
590 if not servefail and line.startswith(
590 if not servefail and line.startswith(
591 b'+ abort: child process failed to start'):
591 b'+ abort: child process failed to start'):
592 servefail = True
592 servefail = True
593
593
594 return servefail, lines
594 return servefail, lines
595
595
596 verbose = False
596 verbose = False
597 def vlog(*msg):
597 def vlog(*msg):
598 """Log only when in verbose mode."""
598 """Log only when in verbose mode."""
599 if verbose is False:
599 if verbose is False:
600 return
600 return
601
601
602 return log(*msg)
602 return log(*msg)
603
603
604 # Bytes that break XML even in a CDATA block: control characters 0-31
604 # Bytes that break XML even in a CDATA block: control characters 0-31
605 # sans \t, \n and \r
605 # sans \t, \n and \r
606 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
606 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
607
607
608 # Match feature conditionalized output lines in the form, capturing the feature
608 # Match feature conditionalized output lines in the form, capturing the feature
609 # list in group 2, and the preceeding line output in group 1:
609 # list in group 2, and the preceeding line output in group 1:
610 #
610 #
611 # output..output (feature !)\n
611 # output..output (feature !)\n
612 optline = re.compile(b'(.*) \((.+?) !\)\n$')
612 optline = re.compile(b'(.*) \((.+?) !\)\n$')
613
613
614 def cdatasafe(data):
614 def cdatasafe(data):
615 """Make a string safe to include in a CDATA block.
615 """Make a string safe to include in a CDATA block.
616
616
617 Certain control characters are illegal in a CDATA block, and
617 Certain control characters are illegal in a CDATA block, and
618 there's no way to include a ]]> in a CDATA either. This function
618 there's no way to include a ]]> in a CDATA either. This function
619 replaces illegal bytes with ? and adds a space between the ]] so
619 replaces illegal bytes with ? and adds a space between the ]] so
620 that it won't break the CDATA block.
620 that it won't break the CDATA block.
621 """
621 """
622 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
622 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
623
623
624 def log(*msg):
624 def log(*msg):
625 """Log something to stdout.
625 """Log something to stdout.
626
626
627 Arguments are strings to print.
627 Arguments are strings to print.
628 """
628 """
629 with iolock:
629 with iolock:
630 if verbose:
630 if verbose:
631 print(verbose, end=' ')
631 print(verbose, end=' ')
632 for m in msg:
632 for m in msg:
633 print(m, end=' ')
633 print(m, end=' ')
634 print()
634 print()
635 sys.stdout.flush()
635 sys.stdout.flush()
636
636
637 def highlightdiff(line, color):
637 def highlightdiff(line, color):
638 if not color:
638 if not color:
639 return line
639 return line
640 assert pygmentspresent
640 assert pygmentspresent
641 return pygments.highlight(line.decode('latin1'), difflexer,
641 return pygments.highlight(line.decode('latin1'), difflexer,
642 terminal256formatter).encode('latin1')
642 terminal256formatter).encode('latin1')
643
643
644 def highlightmsg(msg, color):
644 def highlightmsg(msg, color):
645 if not color:
645 if not color:
646 return msg
646 return msg
647 assert pygmentspresent
647 assert pygmentspresent
648 return pygments.highlight(msg, runnerlexer, runnerformatter)
648 return pygments.highlight(msg, runnerlexer, runnerformatter)
649
649
650 def terminate(proc):
650 def terminate(proc):
651 """Terminate subprocess"""
651 """Terminate subprocess"""
652 vlog('# Terminating process %d' % proc.pid)
652 vlog('# Terminating process %d' % proc.pid)
653 try:
653 try:
654 proc.terminate()
654 proc.terminate()
655 except OSError:
655 except OSError:
656 pass
656 pass
657
657
658 def killdaemons(pidfile):
658 def killdaemons(pidfile):
659 import killdaemons as killmod
659 import killdaemons as killmod
660 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
660 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
661 logfn=vlog)
661 logfn=vlog)
662
662
663 class Test(unittest.TestCase):
663 class Test(unittest.TestCase):
664 """Encapsulates a single, runnable test.
664 """Encapsulates a single, runnable test.
665
665
666 While this class conforms to the unittest.TestCase API, it differs in that
666 While this class conforms to the unittest.TestCase API, it differs in that
667 instances need to be instantiated manually. (Typically, unittest.TestCase
667 instances need to be instantiated manually. (Typically, unittest.TestCase
668 classes are instantiated automatically by scanning modules.)
668 classes are instantiated automatically by scanning modules.)
669 """
669 """
670
670
671 # Status code reserved for skipped tests (used by hghave).
671 # Status code reserved for skipped tests (used by hghave).
672 SKIPPED_STATUS = 80
672 SKIPPED_STATUS = 80
673
673
674 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
674 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
675 debug=False,
675 debug=False,
676 first=False,
676 first=False,
677 timeout=None,
677 timeout=None,
678 startport=None, extraconfigopts=None,
678 startport=None, extraconfigopts=None,
679 py3kwarnings=False, shell=None, hgcommand=None,
679 py3kwarnings=False, shell=None, hgcommand=None,
680 slowtimeout=None, usechg=False,
680 slowtimeout=None, usechg=False,
681 useipv6=False):
681 useipv6=False):
682 """Create a test from parameters.
682 """Create a test from parameters.
683
683
684 path is the full path to the file defining the test.
684 path is the full path to the file defining the test.
685
685
686 tmpdir is the main temporary directory to use for this test.
686 tmpdir is the main temporary directory to use for this test.
687
687
688 keeptmpdir determines whether to keep the test's temporary directory
688 keeptmpdir determines whether to keep the test's temporary directory
689 after execution. It defaults to removal (False).
689 after execution. It defaults to removal (False).
690
690
691 debug mode will make the test execute verbosely, with unfiltered
691 debug mode will make the test execute verbosely, with unfiltered
692 output.
692 output.
693
693
694 timeout controls the maximum run time of the test. It is ignored when
694 timeout controls the maximum run time of the test. It is ignored when
695 debug is True. See slowtimeout for tests with #require slow.
695 debug is True. See slowtimeout for tests with #require slow.
696
696
697 slowtimeout overrides timeout if the test has #require slow.
697 slowtimeout overrides timeout if the test has #require slow.
698
698
699 startport controls the starting port number to use for this test. Each
699 startport controls the starting port number to use for this test. Each
700 test will reserve 3 port numbers for execution. It is the caller's
700 test will reserve 3 port numbers for execution. It is the caller's
701 responsibility to allocate a non-overlapping port range to Test
701 responsibility to allocate a non-overlapping port range to Test
702 instances.
702 instances.
703
703
704 extraconfigopts is an iterable of extra hgrc config options. Values
704 extraconfigopts is an iterable of extra hgrc config options. Values
705 must have the form "key=value" (something understood by hgrc). Values
705 must have the form "key=value" (something understood by hgrc). Values
706 of the form "foo.key=value" will result in "[foo] key=value".
706 of the form "foo.key=value" will result in "[foo] key=value".
707
707
708 py3kwarnings enables Py3k warnings.
708 py3kwarnings enables Py3k warnings.
709
709
710 shell is the shell to execute tests in.
710 shell is the shell to execute tests in.
711 """
711 """
712 if timeout is None:
712 if timeout is None:
713 timeout = defaults['timeout']
713 timeout = defaults['timeout']
714 if startport is None:
714 if startport is None:
715 startport = defaults['port']
715 startport = defaults['port']
716 if slowtimeout is None:
716 if slowtimeout is None:
717 slowtimeout = defaults['slowtimeout']
717 slowtimeout = defaults['slowtimeout']
718 self.path = path
718 self.path = path
719 self.bname = os.path.basename(path)
719 self.bname = os.path.basename(path)
720 self.name = _strpath(self.bname)
720 self.name = _strpath(self.bname)
721 self._testdir = os.path.dirname(path)
721 self._testdir = os.path.dirname(path)
722 self._outputdir = outputdir
722 self._outputdir = outputdir
723 self._tmpname = os.path.basename(path)
723 self._tmpname = os.path.basename(path)
724 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
724 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
725
725
726 self._threadtmp = tmpdir
726 self._threadtmp = tmpdir
727 self._keeptmpdir = keeptmpdir
727 self._keeptmpdir = keeptmpdir
728 self._debug = debug
728 self._debug = debug
729 self._first = first
729 self._first = first
730 self._timeout = timeout
730 self._timeout = timeout
731 self._slowtimeout = slowtimeout
731 self._slowtimeout = slowtimeout
732 self._startport = startport
732 self._startport = startport
733 self._extraconfigopts = extraconfigopts or []
733 self._extraconfigopts = extraconfigopts or []
734 self._py3kwarnings = py3kwarnings
734 self._py3kwarnings = py3kwarnings
735 self._shell = _bytespath(shell)
735 self._shell = _bytespath(shell)
736 self._hgcommand = hgcommand or b'hg'
736 self._hgcommand = hgcommand or b'hg'
737 self._usechg = usechg
737 self._usechg = usechg
738 self._useipv6 = useipv6
738 self._useipv6 = useipv6
739
739
740 self._aborted = False
740 self._aborted = False
741 self._daemonpids = []
741 self._daemonpids = []
742 self._finished = None
742 self._finished = None
743 self._ret = None
743 self._ret = None
744 self._out = None
744 self._out = None
745 self._skipped = None
745 self._skipped = None
746 self._testtmp = None
746 self._testtmp = None
747 self._chgsockdir = None
747 self._chgsockdir = None
748
748
749 self._refout = self.readrefout()
749 self._refout = self.readrefout()
750
750
751 def readrefout(self):
751 def readrefout(self):
752 """read reference output"""
752 """read reference output"""
753 # If we're not in --debug mode and reference output file exists,
753 # If we're not in --debug mode and reference output file exists,
754 # check test output against it.
754 # check test output against it.
755 if self._debug:
755 if self._debug:
756 return None # to match "out is None"
756 return None # to match "out is None"
757 elif os.path.exists(self.refpath):
757 elif os.path.exists(self.refpath):
758 with open(self.refpath, 'rb') as f:
758 with open(self.refpath, 'rb') as f:
759 return f.read().splitlines(True)
759 return f.read().splitlines(True)
760 else:
760 else:
761 return []
761 return []
762
762
763 # needed to get base class __repr__ running
763 # needed to get base class __repr__ running
764 @property
764 @property
765 def _testMethodName(self):
765 def _testMethodName(self):
766 return self.name
766 return self.name
767
767
768 def __str__(self):
768 def __str__(self):
769 return self.name
769 return self.name
770
770
771 def shortDescription(self):
771 def shortDescription(self):
772 return self.name
772 return self.name
773
773
774 def setUp(self):
774 def setUp(self):
775 """Tasks to perform before run()."""
775 """Tasks to perform before run()."""
776 self._finished = False
776 self._finished = False
777 self._ret = None
777 self._ret = None
778 self._out = None
778 self._out = None
779 self._skipped = None
779 self._skipped = None
780
780
781 try:
781 try:
782 os.mkdir(self._threadtmp)
782 os.mkdir(self._threadtmp)
783 except OSError as e:
783 except OSError as e:
784 if e.errno != errno.EEXIST:
784 if e.errno != errno.EEXIST:
785 raise
785 raise
786
786
787 name = self._tmpname
787 name = self._tmpname
788 self._testtmp = os.path.join(self._threadtmp, name)
788 self._testtmp = os.path.join(self._threadtmp, name)
789 os.mkdir(self._testtmp)
789 os.mkdir(self._testtmp)
790
790
791 # Remove any previous output files.
791 # Remove any previous output files.
792 if os.path.exists(self.errpath):
792 if os.path.exists(self.errpath):
793 try:
793 try:
794 os.remove(self.errpath)
794 os.remove(self.errpath)
795 except OSError as e:
795 except OSError as e:
796 # We might have raced another test to clean up a .err
796 # We might have raced another test to clean up a .err
797 # file, so ignore ENOENT when removing a previous .err
797 # file, so ignore ENOENT when removing a previous .err
798 # file.
798 # file.
799 if e.errno != errno.ENOENT:
799 if e.errno != errno.ENOENT:
800 raise
800 raise
801
801
802 if self._usechg:
802 if self._usechg:
803 self._chgsockdir = os.path.join(self._threadtmp,
803 self._chgsockdir = os.path.join(self._threadtmp,
804 b'%s.chgsock' % name)
804 b'%s.chgsock' % name)
805 os.mkdir(self._chgsockdir)
805 os.mkdir(self._chgsockdir)
806
806
807 def run(self, result):
807 def run(self, result):
808 """Run this test and report results against a TestResult instance."""
808 """Run this test and report results against a TestResult instance."""
809 # This function is extremely similar to unittest.TestCase.run(). Once
809 # This function is extremely similar to unittest.TestCase.run(). Once
810 # we require Python 2.7 (or at least its version of unittest), this
810 # we require Python 2.7 (or at least its version of unittest), this
811 # function can largely go away.
811 # function can largely go away.
812 self._result = result
812 self._result = result
813 result.startTest(self)
813 result.startTest(self)
814 try:
814 try:
815 try:
815 try:
816 self.setUp()
816 self.setUp()
817 except (KeyboardInterrupt, SystemExit):
817 except (KeyboardInterrupt, SystemExit):
818 self._aborted = True
818 self._aborted = True
819 raise
819 raise
820 except Exception:
820 except Exception:
821 result.addError(self, sys.exc_info())
821 result.addError(self, sys.exc_info())
822 return
822 return
823
823
824 success = False
824 success = False
825 try:
825 try:
826 self.runTest()
826 self.runTest()
827 except KeyboardInterrupt:
827 except KeyboardInterrupt:
828 self._aborted = True
828 self._aborted = True
829 raise
829 raise
830 except unittest.SkipTest as e:
830 except unittest.SkipTest as e:
831 result.addSkip(self, str(e))
831 result.addSkip(self, str(e))
832 # The base class will have already counted this as a
832 # The base class will have already counted this as a
833 # test we "ran", but we want to exclude skipped tests
833 # test we "ran", but we want to exclude skipped tests
834 # from those we count towards those run.
834 # from those we count towards those run.
835 result.testsRun -= 1
835 result.testsRun -= 1
836 except self.failureException as e:
836 except self.failureException as e:
837 # This differs from unittest in that we don't capture
837 # This differs from unittest in that we don't capture
838 # the stack trace. This is for historical reasons and
838 # the stack trace. This is for historical reasons and
839 # this decision could be revisited in the future,
839 # this decision could be revisited in the future,
840 # especially for PythonTest instances.
840 # especially for PythonTest instances.
841 if result.addFailure(self, str(e)):
841 if result.addFailure(self, str(e)):
842 success = True
842 success = True
843 except Exception:
843 except Exception:
844 result.addError(self, sys.exc_info())
844 result.addError(self, sys.exc_info())
845 else:
845 else:
846 success = True
846 success = True
847
847
848 try:
848 try:
849 self.tearDown()
849 self.tearDown()
850 except (KeyboardInterrupt, SystemExit):
850 except (KeyboardInterrupt, SystemExit):
851 self._aborted = True
851 self._aborted = True
852 raise
852 raise
853 except Exception:
853 except Exception:
854 result.addError(self, sys.exc_info())
854 result.addError(self, sys.exc_info())
855 success = False
855 success = False
856
856
857 if success:
857 if success:
858 result.addSuccess(self)
858 result.addSuccess(self)
859 finally:
859 finally:
860 result.stopTest(self, interrupted=self._aborted)
860 result.stopTest(self, interrupted=self._aborted)
861
861
862 def runTest(self):
862 def runTest(self):
863 """Run this test instance.
863 """Run this test instance.
864
864
865 This will return a tuple describing the result of the test.
865 This will return a tuple describing the result of the test.
866 """
866 """
867 env = self._getenv()
867 env = self._getenv()
868 self._genrestoreenv(env)
868 self._genrestoreenv(env)
869 self._daemonpids.append(env['DAEMON_PIDS'])
869 self._daemonpids.append(env['DAEMON_PIDS'])
870 self._createhgrc(env['HGRCPATH'])
870 self._createhgrc(env['HGRCPATH'])
871
871
872 vlog('# Test', self.name)
872 vlog('# Test', self.name)
873
873
874 ret, out = self._run(env)
874 ret, out = self._run(env)
875 self._finished = True
875 self._finished = True
876 self._ret = ret
876 self._ret = ret
877 self._out = out
877 self._out = out
878
878
879 def describe(ret):
879 def describe(ret):
880 if ret < 0:
880 if ret < 0:
881 return 'killed by signal: %d' % -ret
881 return 'killed by signal: %d' % -ret
882 return 'returned error code %d' % ret
882 return 'returned error code %d' % ret
883
883
884 self._skipped = False
884 self._skipped = False
885
885
886 if ret == self.SKIPPED_STATUS:
886 if ret == self.SKIPPED_STATUS:
887 if out is None: # Debug mode, nothing to parse.
887 if out is None: # Debug mode, nothing to parse.
888 missing = ['unknown']
888 missing = ['unknown']
889 failed = None
889 failed = None
890 else:
890 else:
891 missing, failed = TTest.parsehghaveoutput(out)
891 missing, failed = TTest.parsehghaveoutput(out)
892
892
893 if not missing:
893 if not missing:
894 missing = ['skipped']
894 missing = ['skipped']
895
895
896 if failed:
896 if failed:
897 self.fail('hg have failed checking for %s' % failed[-1])
897 self.fail('hg have failed checking for %s' % failed[-1])
898 else:
898 else:
899 self._skipped = True
899 self._skipped = True
900 raise unittest.SkipTest(missing[-1])
900 raise unittest.SkipTest(missing[-1])
901 elif ret == 'timeout':
901 elif ret == 'timeout':
902 self.fail('timed out')
902 self.fail('timed out')
903 elif ret is False:
903 elif ret is False:
904 self.fail('no result code from test')
904 self.fail('no result code from test')
905 elif out != self._refout:
905 elif out != self._refout:
906 # Diff generation may rely on written .err file.
906 # Diff generation may rely on written .err file.
907 if (ret != 0 or out != self._refout) and not self._skipped \
907 if (ret != 0 or out != self._refout) and not self._skipped \
908 and not self._debug:
908 and not self._debug:
909 with open(self.errpath, 'wb') as f:
909 with open(self.errpath, 'wb') as f:
910 for line in out:
910 for line in out:
911 f.write(line)
911 f.write(line)
912
912
913 # The result object handles diff calculation for us.
913 # The result object handles diff calculation for us.
914 with firstlock:
914 with firstlock:
915 if self._result.addOutputMismatch(self, ret, out, self._refout):
915 if self._result.addOutputMismatch(self, ret, out, self._refout):
916 # change was accepted, skip failing
916 # change was accepted, skip failing
917 return
917 return
918 if self._first:
918 if self._first:
919 global firsterror
919 global firsterror
920 firsterror = True
920 firsterror = True
921
921
922 if ret:
922 if ret:
923 msg = 'output changed and ' + describe(ret)
923 msg = 'output changed and ' + describe(ret)
924 else:
924 else:
925 msg = 'output changed'
925 msg = 'output changed'
926
926
927 self.fail(msg)
927 self.fail(msg)
928 elif ret:
928 elif ret:
929 self.fail(describe(ret))
929 self.fail(describe(ret))
930
930
931 def tearDown(self):
931 def tearDown(self):
932 """Tasks to perform after run()."""
932 """Tasks to perform after run()."""
933 for entry in self._daemonpids:
933 for entry in self._daemonpids:
934 killdaemons(entry)
934 killdaemons(entry)
935 self._daemonpids = []
935 self._daemonpids = []
936
936
937 if self._keeptmpdir:
937 if self._keeptmpdir:
938 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
938 log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' %
939 (self._testtmp.decode('utf-8'),
939 (self._testtmp.decode('utf-8'),
940 self._threadtmp.decode('utf-8')))
940 self._threadtmp.decode('utf-8')))
941 else:
941 else:
942 shutil.rmtree(self._testtmp, True)
942 shutil.rmtree(self._testtmp, True)
943 shutil.rmtree(self._threadtmp, True)
943 shutil.rmtree(self._threadtmp, True)
944
944
945 if self._usechg:
945 if self._usechg:
946 # chgservers will stop automatically after they find the socket
946 # chgservers will stop automatically after they find the socket
947 # files are deleted
947 # files are deleted
948 shutil.rmtree(self._chgsockdir, True)
948 shutil.rmtree(self._chgsockdir, True)
949
949
950 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
950 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
951 and not self._debug and self._out:
951 and not self._debug and self._out:
952 with open(self.errpath, 'wb') as f:
952 with open(self.errpath, 'wb') as f:
953 for line in self._out:
953 for line in self._out:
954 f.write(line)
954 f.write(line)
955
955
956 vlog("# Ret was:", self._ret, '(%s)' % self.name)
956 vlog("# Ret was:", self._ret, '(%s)' % self.name)
957
957
958 def _run(self, env):
958 def _run(self, env):
959 # This should be implemented in child classes to run tests.
959 # This should be implemented in child classes to run tests.
960 raise unittest.SkipTest('unknown test type')
960 raise unittest.SkipTest('unknown test type')
961
961
962 def abort(self):
962 def abort(self):
963 """Terminate execution of this test."""
963 """Terminate execution of this test."""
964 self._aborted = True
964 self._aborted = True
965
965
966 def _portmap(self, i):
966 def _portmap(self, i):
967 offset = b'' if i == 0 else b'%d' % i
967 offset = b'' if i == 0 else b'%d' % i
968 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
968 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
969
969
970 def _getreplacements(self):
970 def _getreplacements(self):
971 """Obtain a mapping of text replacements to apply to test output.
971 """Obtain a mapping of text replacements to apply to test output.
972
972
973 Test output needs to be normalized so it can be compared to expected
973 Test output needs to be normalized so it can be compared to expected
974 output. This function defines how some of that normalization will
974 output. This function defines how some of that normalization will
975 occur.
975 occur.
976 """
976 """
977 r = [
977 r = [
978 # This list should be parallel to defineport in _getenv
978 # This list should be parallel to defineport in _getenv
979 self._portmap(0),
979 self._portmap(0),
980 self._portmap(1),
980 self._portmap(1),
981 self._portmap(2),
981 self._portmap(2),
982 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
982 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
983 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
983 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
984 ]
984 ]
985 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
985 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
986
986
987 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
987 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
988
988
989 if os.path.exists(replacementfile):
989 if os.path.exists(replacementfile):
990 data = {}
990 data = {}
991 with open(replacementfile, mode='rb') as source:
991 with open(replacementfile, mode='rb') as source:
992 # the intermediate 'compile' step help with debugging
992 # the intermediate 'compile' step help with debugging
993 code = compile(source.read(), replacementfile, 'exec')
993 code = compile(source.read(), replacementfile, 'exec')
994 exec(code, data)
994 exec(code, data)
995 for value in data.get('substitutions', ()):
995 for value in data.get('substitutions', ()):
996 if len(value) != 2:
996 if len(value) != 2:
997 msg = 'malformatted substitution in %s: %r'
997 msg = 'malformatted substitution in %s: %r'
998 msg %= (replacementfile, value)
998 msg %= (replacementfile, value)
999 raise ValueError(msg)
999 raise ValueError(msg)
1000 r.append(value)
1000 r.append(value)
1001 return r
1001 return r
1002
1002
1003 def _escapepath(self, p):
1003 def _escapepath(self, p):
1004 if os.name == 'nt':
1004 if os.name == 'nt':
1005 return (
1005 return (
1006 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1006 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
1007 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1007 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
1008 for c in p))
1008 for c in p))
1009 )
1009 )
1010 else:
1010 else:
1011 return re.escape(p)
1011 return re.escape(p)
1012
1012
1013 def _localip(self):
1013 def _localip(self):
1014 if self._useipv6:
1014 if self._useipv6:
1015 return b'::1'
1015 return b'::1'
1016 else:
1016 else:
1017 return b'127.0.0.1'
1017 return b'127.0.0.1'
1018
1018
1019 def _genrestoreenv(self, testenv):
1019 def _genrestoreenv(self, testenv):
1020 """Generate a script that can be used by tests to restore the original
1020 """Generate a script that can be used by tests to restore the original
1021 environment."""
1021 environment."""
1022 # Put the restoreenv script inside self._threadtmp
1022 # Put the restoreenv script inside self._threadtmp
1023 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1023 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1024 testenv['HGTEST_RESTOREENV'] = scriptpath
1024 testenv['HGTEST_RESTOREENV'] = scriptpath
1025
1025
1026 # Only restore environment variable names that the shell allows
1026 # Only restore environment variable names that the shell allows
1027 # us to export.
1027 # us to export.
1028 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1028 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1029
1029
1030 # Do not restore these variables; otherwise tests would fail.
1030 # Do not restore these variables; otherwise tests would fail.
1031 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1031 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1032
1032
1033 with open(scriptpath, 'w') as envf:
1033 with open(scriptpath, 'w') as envf:
1034 for name, value in origenviron.items():
1034 for name, value in origenviron.items():
1035 if not name_regex.match(name):
1035 if not name_regex.match(name):
1036 # Skip environment variables with unusual names not
1036 # Skip environment variables with unusual names not
1037 # allowed by most shells.
1037 # allowed by most shells.
1038 continue
1038 continue
1039 if name in reqnames:
1039 if name in reqnames:
1040 continue
1040 continue
1041 envf.write('%s=%s\n' % (name, shellquote(value)))
1041 envf.write('%s=%s\n' % (name, shellquote(value)))
1042
1042
1043 for name in testenv:
1043 for name in testenv:
1044 if name in origenviron or name in reqnames:
1044 if name in origenviron or name in reqnames:
1045 continue
1045 continue
1046 envf.write('unset %s\n' % (name,))
1046 envf.write('unset %s\n' % (name,))
1047
1047
1048 def _getenv(self):
1048 def _getenv(self):
1049 """Obtain environment variables to use during test execution."""
1049 """Obtain environment variables to use during test execution."""
1050 def defineport(i):
1050 def defineport(i):
1051 offset = '' if i == 0 else '%s' % i
1051 offset = '' if i == 0 else '%s' % i
1052 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1052 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1053 env = os.environ.copy()
1053 env = os.environ.copy()
1054 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1054 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1055 env['HGEMITWARNINGS'] = '1'
1055 env['HGEMITWARNINGS'] = '1'
1056 env['TESTTMP'] = self._testtmp
1056 env['TESTTMP'] = self._testtmp
1057 env['TESTNAME'] = self.name
1057 env['TESTNAME'] = self.name
1058 env['HOME'] = self._testtmp
1058 env['HOME'] = self._testtmp
1059 # This number should match portneeded in _getport
1059 # This number should match portneeded in _getport
1060 for port in xrange(3):
1060 for port in xrange(3):
1061 # This list should be parallel to _portmap in _getreplacements
1061 # This list should be parallel to _portmap in _getreplacements
1062 defineport(port)
1062 defineport(port)
1063 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
1063 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
1064 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
1064 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
1065 env["HGEDITOR"] = ('"' + sys.executable + '"'
1065 env["HGEDITOR"] = ('"' + sys.executable + '"'
1066 + ' -c "import sys; sys.exit(0)"')
1066 + ' -c "import sys; sys.exit(0)"')
1067 env["HGMERGE"] = "internal:merge"
1067 env["HGMERGE"] = "internal:merge"
1068 env["HGUSER"] = "test"
1068 env["HGUSER"] = "test"
1069 env["HGENCODING"] = "ascii"
1069 env["HGENCODING"] = "ascii"
1070 env["HGENCODINGMODE"] = "strict"
1070 env["HGENCODINGMODE"] = "strict"
1071 env['HGIPV6'] = str(int(self._useipv6))
1071 env['HGIPV6'] = str(int(self._useipv6))
1072
1072
1073 extraextensions = []
1073 extraextensions = []
1074 for opt in self._extraconfigopts:
1074 for opt in self._extraconfigopts:
1075 section, key = opt.encode('utf-8').split(b'.', 1)
1075 section, key = opt.encode('utf-8').split(b'.', 1)
1076 if section != 'extensions':
1076 if section != 'extensions':
1077 continue
1077 continue
1078 name = key.split(b'=', 1)[0]
1078 name = key.split(b'=', 1)[0]
1079 extraextensions.append(name)
1079 extraextensions.append(name)
1080
1080
1081 if extraextensions:
1081 if extraextensions:
1082 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1082 env['HGTESTEXTRAEXTENSIONS'] = b' '.join(extraextensions)
1083
1083
1084 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1084 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1085 # IP addresses.
1085 # IP addresses.
1086 env['LOCALIP'] = self._localip()
1086 env['LOCALIP'] = self._localip()
1087
1087
1088 # Reset some environment variables to well-known values so that
1088 # Reset some environment variables to well-known values so that
1089 # the tests produce repeatable output.
1089 # the tests produce repeatable output.
1090 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1090 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1091 env['TZ'] = 'GMT'
1091 env['TZ'] = 'GMT'
1092 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1092 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1093 env['COLUMNS'] = '80'
1093 env['COLUMNS'] = '80'
1094 env['TERM'] = 'xterm'
1094 env['TERM'] = 'xterm'
1095
1095
1096 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
1096 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
1097 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
1097 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' +
1098 'NO_PROXY CHGDEBUG').split():
1098 'NO_PROXY CHGDEBUG').split():
1099 if k in env:
1099 if k in env:
1100 del env[k]
1100 del env[k]
1101
1101
1102 # unset env related to hooks
1102 # unset env related to hooks
1103 for k in list(env):
1103 for k in list(env):
1104 if k.startswith('HG_'):
1104 if k.startswith('HG_'):
1105 del env[k]
1105 del env[k]
1106
1106
1107 if self._usechg:
1107 if self._usechg:
1108 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1108 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1109
1109
1110 return env
1110 return env
1111
1111
1112 def _createhgrc(self, path):
1112 def _createhgrc(self, path):
1113 """Create an hgrc file for this test."""
1113 """Create an hgrc file for this test."""
1114 with open(path, 'wb') as hgrc:
1114 with open(path, 'wb') as hgrc:
1115 hgrc.write(b'[ui]\n')
1115 hgrc.write(b'[ui]\n')
1116 hgrc.write(b'slash = True\n')
1116 hgrc.write(b'slash = True\n')
1117 hgrc.write(b'interactive = False\n')
1117 hgrc.write(b'interactive = False\n')
1118 hgrc.write(b'mergemarkers = detailed\n')
1118 hgrc.write(b'mergemarkers = detailed\n')
1119 hgrc.write(b'promptecho = True\n')
1119 hgrc.write(b'promptecho = True\n')
1120 hgrc.write(b'[defaults]\n')
1120 hgrc.write(b'[defaults]\n')
1121 hgrc.write(b'[devel]\n')
1121 hgrc.write(b'[devel]\n')
1122 hgrc.write(b'all-warnings = true\n')
1122 hgrc.write(b'all-warnings = true\n')
1123 hgrc.write(b'default-date = 0 0\n')
1123 hgrc.write(b'default-date = 0 0\n')
1124 hgrc.write(b'[largefiles]\n')
1124 hgrc.write(b'[largefiles]\n')
1125 hgrc.write(b'usercache = %s\n' %
1125 hgrc.write(b'usercache = %s\n' %
1126 (os.path.join(self._testtmp, b'.cache/largefiles')))
1126 (os.path.join(self._testtmp, b'.cache/largefiles')))
1127 hgrc.write(b'[lfs]\n')
1127 hgrc.write(b'[lfs]\n')
1128 hgrc.write(b'usercache = %s\n' %
1128 hgrc.write(b'usercache = %s\n' %
1129 (os.path.join(self._testtmp, b'.cache/lfs')))
1129 (os.path.join(self._testtmp, b'.cache/lfs')))
1130 hgrc.write(b'[web]\n')
1130 hgrc.write(b'[web]\n')
1131 hgrc.write(b'address = localhost\n')
1131 hgrc.write(b'address = localhost\n')
1132 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1132 hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
1133 hgrc.write(b'server-header = testing stub value\n')
1133 hgrc.write(b'server-header = testing stub value\n')
1134
1134
1135 for opt in self._extraconfigopts:
1135 for opt in self._extraconfigopts:
1136 section, key = opt.encode('utf-8').split(b'.', 1)
1136 section, key = opt.encode('utf-8').split(b'.', 1)
1137 assert b'=' in key, ('extra config opt %s must '
1137 assert b'=' in key, ('extra config opt %s must '
1138 'have an = for assignment' % opt)
1138 'have an = for assignment' % opt)
1139 hgrc.write(b'[%s]\n%s\n' % (section, key))
1139 hgrc.write(b'[%s]\n%s\n' % (section, key))
1140
1140
1141 def fail(self, msg):
1141 def fail(self, msg):
1142 # unittest differentiates between errored and failed.
1142 # unittest differentiates between errored and failed.
1143 # Failed is denoted by AssertionError (by default at least).
1143 # Failed is denoted by AssertionError (by default at least).
1144 raise AssertionError(msg)
1144 raise AssertionError(msg)
1145
1145
1146 def _runcommand(self, cmd, env, normalizenewlines=False):
1146 def _runcommand(self, cmd, env, normalizenewlines=False):
1147 """Run command in a sub-process, capturing the output (stdout and
1147 """Run command in a sub-process, capturing the output (stdout and
1148 stderr).
1148 stderr).
1149
1149
1150 Return a tuple (exitcode, output). output is None in debug mode.
1150 Return a tuple (exitcode, output). output is None in debug mode.
1151 """
1151 """
1152 if self._debug:
1152 if self._debug:
1153 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
1153 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
1154 env=env)
1154 env=env)
1155 ret = proc.wait()
1155 ret = proc.wait()
1156 return (ret, None)
1156 return (ret, None)
1157
1157
1158 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1158 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1159 def cleanup():
1159 def cleanup():
1160 terminate(proc)
1160 terminate(proc)
1161 ret = proc.wait()
1161 ret = proc.wait()
1162 if ret == 0:
1162 if ret == 0:
1163 ret = signal.SIGTERM << 8
1163 ret = signal.SIGTERM << 8
1164 killdaemons(env['DAEMON_PIDS'])
1164 killdaemons(env['DAEMON_PIDS'])
1165 return ret
1165 return ret
1166
1166
1167 output = ''
1167 output = ''
1168 proc.tochild.close()
1168 proc.tochild.close()
1169
1169
1170 try:
1170 try:
1171 output = proc.fromchild.read()
1171 output = proc.fromchild.read()
1172 except KeyboardInterrupt:
1172 except KeyboardInterrupt:
1173 vlog('# Handling keyboard interrupt')
1173 vlog('# Handling keyboard interrupt')
1174 cleanup()
1174 cleanup()
1175 raise
1175 raise
1176
1176
1177 ret = proc.wait()
1177 ret = proc.wait()
1178 if wifexited(ret):
1178 if wifexited(ret):
1179 ret = os.WEXITSTATUS(ret)
1179 ret = os.WEXITSTATUS(ret)
1180
1180
1181 if proc.timeout:
1181 if proc.timeout:
1182 ret = 'timeout'
1182 ret = 'timeout'
1183
1183
1184 if ret:
1184 if ret:
1185 killdaemons(env['DAEMON_PIDS'])
1185 killdaemons(env['DAEMON_PIDS'])
1186
1186
1187 for s, r in self._getreplacements():
1187 for s, r in self._getreplacements():
1188 output = re.sub(s, r, output)
1188 output = re.sub(s, r, output)
1189
1189
1190 if normalizenewlines:
1190 if normalizenewlines:
1191 output = output.replace('\r\n', '\n')
1191 output = output.replace('\r\n', '\n')
1192
1192
1193 return ret, output.splitlines(True)
1193 return ret, output.splitlines(True)
1194
1194
1195 class PythonTest(Test):
1195 class PythonTest(Test):
1196 """A Python-based test."""
1196 """A Python-based test."""
1197
1197
1198 @property
1198 @property
1199 def refpath(self):
1199 def refpath(self):
1200 return os.path.join(self._testdir, b'%s.out' % self.bname)
1200 return os.path.join(self._testdir, b'%s.out' % self.bname)
1201
1201
1202 def _run(self, env):
1202 def _run(self, env):
1203 py3kswitch = self._py3kwarnings and b' -3' or b''
1203 py3kswitch = self._py3kwarnings and b' -3' or b''
1204 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1204 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
1205 vlog("# Running", cmd)
1205 vlog("# Running", cmd)
1206 normalizenewlines = os.name == 'nt'
1206 normalizenewlines = os.name == 'nt'
1207 result = self._runcommand(cmd, env,
1207 result = self._runcommand(cmd, env,
1208 normalizenewlines=normalizenewlines)
1208 normalizenewlines=normalizenewlines)
1209 if self._aborted:
1209 if self._aborted:
1210 raise KeyboardInterrupt()
1210 raise KeyboardInterrupt()
1211
1211
1212 return result
1212 return result
1213
1213
1214 # Some glob patterns apply only in some circumstances, so the script
1214 # Some glob patterns apply only in some circumstances, so the script
1215 # might want to remove (glob) annotations that otherwise should be
1215 # might want to remove (glob) annotations that otherwise should be
1216 # retained.
1216 # retained.
1217 checkcodeglobpats = [
1217 checkcodeglobpats = [
1218 # On Windows it looks like \ doesn't require a (glob), but we know
1218 # On Windows it looks like \ doesn't require a (glob), but we know
1219 # better.
1219 # better.
1220 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1220 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1221 re.compile(br'^moving \S+/.*[^)]$'),
1221 re.compile(br'^moving \S+/.*[^)]$'),
1222 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1222 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1223 # Not all platforms have 127.0.0.1 as loopback (though most do),
1223 # Not all platforms have 127.0.0.1 as loopback (though most do),
1224 # so we always glob that too.
1224 # so we always glob that too.
1225 re.compile(br'.*\$LOCALIP.*$'),
1225 re.compile(br'.*\$LOCALIP.*$'),
1226 ]
1226 ]
1227
1227
1228 bchr = chr
1228 bchr = chr
1229 if PYTHON3:
1229 if PYTHON3:
1230 bchr = lambda x: bytes([x])
1230 bchr = lambda x: bytes([x])
1231
1231
1232 class TTest(Test):
1232 class TTest(Test):
1233 """A "t test" is a test backed by a .t file."""
1233 """A "t test" is a test backed by a .t file."""
1234
1234
1235 SKIPPED_PREFIX = b'skipped: '
1235 SKIPPED_PREFIX = b'skipped: '
1236 FAILED_PREFIX = b'hghave check failed: '
1236 FAILED_PREFIX = b'hghave check failed: '
1237 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1237 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1238
1238
1239 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1239 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1240 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1240 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
1241 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1241 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1242
1242
1243 def __init__(self, path, *args, **kwds):
1243 def __init__(self, path, *args, **kwds):
1244 # accept an extra "case" parameter
1244 # accept an extra "case" parameter
1245 case = kwds.pop('case', None)
1245 case = kwds.pop('case', None)
1246 self._case = case
1246 self._case = case
1247 self._allcases = parsettestcases(path)
1247 self._allcases = parsettestcases(path)
1248 super(TTest, self).__init__(path, *args, **kwds)
1248 super(TTest, self).__init__(path, *args, **kwds)
1249 if case:
1249 if case:
1250 self.name = '%s#%s' % (self.name, _strpath(case))
1250 self.name = '%s#%s' % (self.name, _strpath(case))
1251 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1251 self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
1252 self._tmpname += b'-%s' % case
1252 self._tmpname += b'-%s' % case
1253 self._have = {}
1253 self._have = {}
1254
1254
1255 @property
1255 @property
1256 def refpath(self):
1256 def refpath(self):
1257 return os.path.join(self._testdir, self.bname)
1257 return os.path.join(self._testdir, self.bname)
1258
1258
1259 def _run(self, env):
1259 def _run(self, env):
1260 with open(self.path, 'rb') as f:
1260 with open(self.path, 'rb') as f:
1261 lines = f.readlines()
1261 lines = f.readlines()
1262
1262
1263 # .t file is both reference output and the test input, keep reference
1263 # .t file is both reference output and the test input, keep reference
1264 # output updated with the the test input. This avoids some race
1264 # output updated with the the test input. This avoids some race
1265 # conditions where the reference output does not match the actual test.
1265 # conditions where the reference output does not match the actual test.
1266 if self._refout is not None:
1266 if self._refout is not None:
1267 self._refout = lines
1267 self._refout = lines
1268
1268
1269 salt, script, after, expected = self._parsetest(lines)
1269 salt, script, after, expected = self._parsetest(lines)
1270
1270
1271 # Write out the generated script.
1271 # Write out the generated script.
1272 fname = b'%s.sh' % self._testtmp
1272 fname = b'%s.sh' % self._testtmp
1273 with open(fname, 'wb') as f:
1273 with open(fname, 'wb') as f:
1274 for l in script:
1274 for l in script:
1275 f.write(l)
1275 f.write(l)
1276
1276
1277 cmd = b'%s "%s"' % (self._shell, fname)
1277 cmd = b'%s "%s"' % (self._shell, fname)
1278 vlog("# Running", cmd)
1278 vlog("# Running", cmd)
1279
1279
1280 exitcode, output = self._runcommand(cmd, env)
1280 exitcode, output = self._runcommand(cmd, env)
1281
1281
1282 if self._aborted:
1282 if self._aborted:
1283 raise KeyboardInterrupt()
1283 raise KeyboardInterrupt()
1284
1284
1285 # Do not merge output if skipped. Return hghave message instead.
1285 # Do not merge output if skipped. Return hghave message instead.
1286 # Similarly, with --debug, output is None.
1286 # Similarly, with --debug, output is None.
1287 if exitcode == self.SKIPPED_STATUS or output is None:
1287 if exitcode == self.SKIPPED_STATUS or output is None:
1288 return exitcode, output
1288 return exitcode, output
1289
1289
1290 return self._processoutput(exitcode, output, salt, after, expected)
1290 return self._processoutput(exitcode, output, salt, after, expected)
1291
1291
1292 def _hghave(self, reqs):
1292 def _hghave(self, reqs):
1293 allreqs = b' '.join(reqs)
1293 allreqs = b' '.join(reqs)
1294 if allreqs in self._have:
1294 if allreqs in self._have:
1295 return self._have.get(allreqs)
1295 return self._have.get(allreqs)
1296
1296
1297 # TODO do something smarter when all other uses of hghave are gone.
1297 # TODO do something smarter when all other uses of hghave are gone.
1298 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1298 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
1299 tdir = runtestdir.replace(b'\\', b'/')
1299 tdir = runtestdir.replace(b'\\', b'/')
1300 proc = Popen4(b'%s -c "%s/hghave %s"' %
1300 proc = Popen4(b'%s -c "%s/hghave %s"' %
1301 (self._shell, tdir, allreqs),
1301 (self._shell, tdir, allreqs),
1302 self._testtmp, 0, self._getenv())
1302 self._testtmp, 0, self._getenv())
1303 stdout, stderr = proc.communicate()
1303 stdout, stderr = proc.communicate()
1304 ret = proc.wait()
1304 ret = proc.wait()
1305 if wifexited(ret):
1305 if wifexited(ret):
1306 ret = os.WEXITSTATUS(ret)
1306 ret = os.WEXITSTATUS(ret)
1307 if ret == 2:
1307 if ret == 2:
1308 print(stdout.decode('utf-8'))
1308 print(stdout.decode('utf-8'))
1309 sys.exit(1)
1309 sys.exit(1)
1310
1310
1311 if ret != 0:
1311 if ret != 0:
1312 self._have[allreqs] = (False, stdout)
1312 self._have[allreqs] = (False, stdout)
1313 return False, stdout
1313 return False, stdout
1314
1314
1315 if b'slow' in reqs:
1315 if b'slow' in reqs:
1316 self._timeout = self._slowtimeout
1316 self._timeout = self._slowtimeout
1317
1317
1318 self._have[allreqs] = (True, None)
1318 self._have[allreqs] = (True, None)
1319 return True, None
1319 return True, None
1320
1320
1321 def _iftest(self, args):
1321 def _iftest(self, args):
1322 # implements "#if"
1322 # implements "#if"
1323 reqs = []
1323 reqs = []
1324 for arg in args:
1324 for arg in args:
1325 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1325 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1326 if arg[3:] == self._case:
1326 if arg[3:] == self._case:
1327 return False
1327 return False
1328 elif arg in self._allcases:
1328 elif arg in self._allcases:
1329 if arg != self._case:
1329 if arg != self._case:
1330 return False
1330 return False
1331 else:
1331 else:
1332 reqs.append(arg)
1332 reqs.append(arg)
1333 return self._hghave(reqs)[0]
1333 return self._hghave(reqs)[0]
1334
1334
1335 def _parsetest(self, lines):
1335 def _parsetest(self, lines):
1336 # We generate a shell script which outputs unique markers to line
1336 # We generate a shell script which outputs unique markers to line
1337 # up script results with our source. These markers include input
1337 # up script results with our source. These markers include input
1338 # line number and the last return code.
1338 # line number and the last return code.
1339 salt = b"SALT%d" % time.time()
1339 salt = b"SALT%d" % time.time()
1340 def addsalt(line, inpython):
1340 def addsalt(line, inpython):
1341 if inpython:
1341 if inpython:
1342 script.append(b'%s %d 0\n' % (salt, line))
1342 script.append(b'%s %d 0\n' % (salt, line))
1343 else:
1343 else:
1344 script.append(b'echo %s %d $?\n' % (salt, line))
1344 script.append(b'echo %s %d $?\n' % (salt, line))
1345
1345
1346 script = []
1346 script = []
1347
1347
1348 # After we run the shell script, we re-unify the script output
1348 # After we run the shell script, we re-unify the script output
1349 # with non-active parts of the source, with synchronization by our
1349 # with non-active parts of the source, with synchronization by our
1350 # SALT line number markers. The after table contains the non-active
1350 # SALT line number markers. The after table contains the non-active
1351 # components, ordered by line number.
1351 # components, ordered by line number.
1352 after = {}
1352 after = {}
1353
1353
1354 # Expected shell script output.
1354 # Expected shell script output.
1355 expected = {}
1355 expected = {}
1356
1356
1357 pos = prepos = -1
1357 pos = prepos = -1
1358
1358
1359 # True or False when in a true or false conditional section
1359 # True or False when in a true or false conditional section
1360 skipping = None
1360 skipping = None
1361
1361
1362 # We keep track of whether or not we're in a Python block so we
1362 # We keep track of whether or not we're in a Python block so we
1363 # can generate the surrounding doctest magic.
1363 # can generate the surrounding doctest magic.
1364 inpython = False
1364 inpython = False
1365
1365
1366 if self._debug:
1366 if self._debug:
1367 script.append(b'set -x\n')
1367 script.append(b'set -x\n')
1368 if self._hgcommand != b'hg':
1368 if self._hgcommand != b'hg':
1369 script.append(b'alias hg="%s"\n' % self._hgcommand)
1369 script.append(b'alias hg="%s"\n' % self._hgcommand)
1370 if os.getenv('MSYSTEM'):
1370 if os.getenv('MSYSTEM'):
1371 script.append(b'alias pwd="pwd -W"\n')
1371 script.append(b'alias pwd="pwd -W"\n')
1372 if self._case:
1372 if self._case:
1373 if isinstance(self._case, str):
1373 if isinstance(self._case, str):
1374 quoted = shellquote(self._case)
1374 quoted = shellquote(self._case)
1375 else:
1375 else:
1376 quoted = shellquote(self._case.decode('utf8')).encode('utf8')
1376 quoted = shellquote(self._case.decode('utf8')).encode('utf8')
1377 script.append(b'TESTCASE=%s\n' % quoted)
1377 script.append(b'TESTCASE=%s\n' % quoted)
1378 script.append(b'export TESTCASE\n')
1378 script.append(b'export TESTCASE\n')
1379
1379
1380 n = 0
1380 n = 0
1381 for n, l in enumerate(lines):
1381 for n, l in enumerate(lines):
1382 if not l.endswith(b'\n'):
1382 if not l.endswith(b'\n'):
1383 l += b'\n'
1383 l += b'\n'
1384 if l.startswith(b'#require'):
1384 if l.startswith(b'#require'):
1385 lsplit = l.split()
1385 lsplit = l.split()
1386 if len(lsplit) < 2 or lsplit[0] != b'#require':
1386 if len(lsplit) < 2 or lsplit[0] != b'#require':
1387 after.setdefault(pos, []).append(' !!! invalid #require\n')
1387 after.setdefault(pos, []).append(' !!! invalid #require\n')
1388 if not skipping:
1388 if not skipping:
1389 haveresult, message = self._hghave(lsplit[1:])
1389 haveresult, message = self._hghave(lsplit[1:])
1390 if not haveresult:
1390 if not haveresult:
1391 script = [b'echo "%s"\nexit 80\n' % message]
1391 script = [b'echo "%s"\nexit 80\n' % message]
1392 break
1392 break
1393 after.setdefault(pos, []).append(l)
1393 after.setdefault(pos, []).append(l)
1394 elif l.startswith(b'#if'):
1394 elif l.startswith(b'#if'):
1395 lsplit = l.split()
1395 lsplit = l.split()
1396 if len(lsplit) < 2 or lsplit[0] != b'#if':
1396 if len(lsplit) < 2 or lsplit[0] != b'#if':
1397 after.setdefault(pos, []).append(' !!! invalid #if\n')
1397 after.setdefault(pos, []).append(' !!! invalid #if\n')
1398 if skipping is not None:
1398 if skipping is not None:
1399 after.setdefault(pos, []).append(' !!! nested #if\n')
1399 after.setdefault(pos, []).append(' !!! nested #if\n')
1400 skipping = not self._iftest(lsplit[1:])
1400 skipping = not self._iftest(lsplit[1:])
1401 after.setdefault(pos, []).append(l)
1401 after.setdefault(pos, []).append(l)
1402 elif l.startswith(b'#else'):
1402 elif l.startswith(b'#else'):
1403 if skipping is None:
1403 if skipping is None:
1404 after.setdefault(pos, []).append(' !!! missing #if\n')
1404 after.setdefault(pos, []).append(' !!! missing #if\n')
1405 skipping = not skipping
1405 skipping = not skipping
1406 after.setdefault(pos, []).append(l)
1406 after.setdefault(pos, []).append(l)
1407 elif l.startswith(b'#endif'):
1407 elif l.startswith(b'#endif'):
1408 if skipping is None:
1408 if skipping is None:
1409 after.setdefault(pos, []).append(' !!! missing #if\n')
1409 after.setdefault(pos, []).append(' !!! missing #if\n')
1410 skipping = None
1410 skipping = None
1411 after.setdefault(pos, []).append(l)
1411 after.setdefault(pos, []).append(l)
1412 elif skipping:
1412 elif skipping:
1413 after.setdefault(pos, []).append(l)
1413 after.setdefault(pos, []).append(l)
1414 elif l.startswith(b' >>> '): # python inlines
1414 elif l.startswith(b' >>> '): # python inlines
1415 after.setdefault(pos, []).append(l)
1415 after.setdefault(pos, []).append(l)
1416 prepos = pos
1416 prepos = pos
1417 pos = n
1417 pos = n
1418 if not inpython:
1418 if not inpython:
1419 # We've just entered a Python block. Add the header.
1419 # We've just entered a Python block. Add the header.
1420 inpython = True
1420 inpython = True
1421 addsalt(prepos, False) # Make sure we report the exit code.
1421 addsalt(prepos, False) # Make sure we report the exit code.
1422 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1422 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1423 addsalt(n, True)
1423 addsalt(n, True)
1424 script.append(l[2:])
1424 script.append(l[2:])
1425 elif l.startswith(b' ... '): # python inlines
1425 elif l.startswith(b' ... '): # python inlines
1426 after.setdefault(prepos, []).append(l)
1426 after.setdefault(prepos, []).append(l)
1427 script.append(l[2:])
1427 script.append(l[2:])
1428 elif l.startswith(b' $ '): # commands
1428 elif l.startswith(b' $ '): # commands
1429 if inpython:
1429 if inpython:
1430 script.append(b'EOF\n')
1430 script.append(b'EOF\n')
1431 inpython = False
1431 inpython = False
1432 after.setdefault(pos, []).append(l)
1432 after.setdefault(pos, []).append(l)
1433 prepos = pos
1433 prepos = pos
1434 pos = n
1434 pos = n
1435 addsalt(n, False)
1435 addsalt(n, False)
1436 cmd = l[4:].split()
1436 cmd = l[4:].split()
1437 if len(cmd) == 2 and cmd[0] == b'cd':
1437 if len(cmd) == 2 and cmd[0] == b'cd':
1438 l = b' $ cd %s || exit 1\n' % cmd[1]
1438 l = b' $ cd %s || exit 1\n' % cmd[1]
1439 script.append(l[4:])
1439 script.append(l[4:])
1440 elif l.startswith(b' > '): # continuations
1440 elif l.startswith(b' > '): # continuations
1441 after.setdefault(prepos, []).append(l)
1441 after.setdefault(prepos, []).append(l)
1442 script.append(l[4:])
1442 script.append(l[4:])
1443 elif l.startswith(b' '): # results
1443 elif l.startswith(b' '): # results
1444 # Queue up a list of expected results.
1444 # Queue up a list of expected results.
1445 expected.setdefault(pos, []).append(l[2:])
1445 expected.setdefault(pos, []).append(l[2:])
1446 else:
1446 else:
1447 if inpython:
1447 if inpython:
1448 script.append(b'EOF\n')
1448 script.append(b'EOF\n')
1449 inpython = False
1449 inpython = False
1450 # Non-command/result. Queue up for merged output.
1450 # Non-command/result. Queue up for merged output.
1451 after.setdefault(pos, []).append(l)
1451 after.setdefault(pos, []).append(l)
1452
1452
1453 if inpython:
1453 if inpython:
1454 script.append(b'EOF\n')
1454 script.append(b'EOF\n')
1455 if skipping is not None:
1455 if skipping is not None:
1456 after.setdefault(pos, []).append(' !!! missing #endif\n')
1456 after.setdefault(pos, []).append(' !!! missing #endif\n')
1457 addsalt(n + 1, False)
1457 addsalt(n + 1, False)
1458
1458
1459 return salt, script, after, expected
1459 return salt, script, after, expected
1460
1460
1461 def _processoutput(self, exitcode, output, salt, after, expected):
1461 def _processoutput(self, exitcode, output, salt, after, expected):
1462 # Merge the script output back into a unified test.
1462 # Merge the script output back into a unified test.
1463 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1463 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1464 if exitcode != 0:
1464 if exitcode != 0:
1465 warnonly = 3
1465 warnonly = 3
1466
1466
1467 pos = -1
1467 pos = -1
1468 postout = []
1468 postout = []
1469 for l in output:
1469 for l in output:
1470 lout, lcmd = l, None
1470 lout, lcmd = l, None
1471 if salt in l:
1471 if salt in l:
1472 lout, lcmd = l.split(salt, 1)
1472 lout, lcmd = l.split(salt, 1)
1473
1473
1474 while lout:
1474 while lout:
1475 if not lout.endswith(b'\n'):
1475 if not lout.endswith(b'\n'):
1476 lout += b' (no-eol)\n'
1476 lout += b' (no-eol)\n'
1477
1477
1478 # Find the expected output at the current position.
1478 # Find the expected output at the current position.
1479 els = [None]
1479 els = [None]
1480 if expected.get(pos, None):
1480 if expected.get(pos, None):
1481 els = expected[pos]
1481 els = expected[pos]
1482
1482
1483 optional = []
1483 optional = []
1484 for i, el in enumerate(els):
1484 for i, el in enumerate(els):
1485 r = False
1485 r = False
1486 if el:
1486 if el:
1487 r = self.linematch(el, lout)
1487 r, exact = self.linematch(el, lout)
1488 if isinstance(r, str):
1488 if isinstance(r, str):
1489 if r == '-glob':
1489 if r == '-glob':
1490 lout = ''.join(el.rsplit(' (glob)', 1))
1490 lout = ''.join(el.rsplit(' (glob)', 1))
1491 r = '' # Warn only this line.
1491 r = '' # Warn only this line.
1492 elif r == "retry":
1492 elif r == "retry":
1493 postout.append(b' ' + el)
1493 postout.append(b' ' + el)
1494 else:
1494 else:
1495 log('\ninfo, unknown linematch result: %r\n' % r)
1495 log('\ninfo, unknown linematch result: %r\n' % r)
1496 r = False
1496 r = False
1497 if r:
1497 if r:
1498 els.pop(i)
1498 els.pop(i)
1499 break
1499 break
1500 if el:
1500 if el:
1501 if el.endswith(b" (?)\n"):
1501 if el.endswith(b" (?)\n"):
1502 optional.append(i)
1502 optional.append(i)
1503 else:
1503 else:
1504 m = optline.match(el)
1504 m = optline.match(el)
1505 if m:
1505 if m:
1506 conditions = [
1506 conditions = [
1507 c for c in m.group(2).split(b' ')]
1507 c for c in m.group(2).split(b' ')]
1508
1508
1509 if not self._iftest(conditions):
1509 if not self._iftest(conditions):
1510 optional.append(i)
1510 optional.append(i)
1511 if exact:
1512 # Don't allow line to be matches against a later
1513 # line in the output
1514 els.pop(i)
1515 break
1511
1516
1512 if r:
1517 if r:
1513 if r == "retry":
1518 if r == "retry":
1514 continue
1519 continue
1515 # clean up any optional leftovers
1520 # clean up any optional leftovers
1516 for i in optional:
1521 for i in optional:
1517 postout.append(b' ' + els[i])
1522 postout.append(b' ' + els[i])
1518 for i in reversed(optional):
1523 for i in reversed(optional):
1519 del els[i]
1524 del els[i]
1520 postout.append(b' ' + el)
1525 postout.append(b' ' + el)
1521 else:
1526 else:
1522 if self.NEEDESCAPE(lout):
1527 if self.NEEDESCAPE(lout):
1523 lout = TTest._stringescape(b'%s (esc)\n' %
1528 lout = TTest._stringescape(b'%s (esc)\n' %
1524 lout.rstrip(b'\n'))
1529 lout.rstrip(b'\n'))
1525 postout.append(b' ' + lout) # Let diff deal with it.
1530 postout.append(b' ' + lout) # Let diff deal with it.
1526 if r != '': # If line failed.
1531 if r != '': # If line failed.
1527 warnonly = 3 # for sure not
1532 warnonly = 3 # for sure not
1528 elif warnonly == 1: # Is "not yet" and line is warn only.
1533 elif warnonly == 1: # Is "not yet" and line is warn only.
1529 warnonly = 2 # Yes do warn.
1534 warnonly = 2 # Yes do warn.
1530 break
1535 break
1531 else:
1536 else:
1532 # clean up any optional leftovers
1537 # clean up any optional leftovers
1533 while expected.get(pos, None):
1538 while expected.get(pos, None):
1534 el = expected[pos].pop(0)
1539 el = expected[pos].pop(0)
1535 if el:
1540 if el:
1536 if not el.endswith(b" (?)\n"):
1541 if not el.endswith(b" (?)\n"):
1537 m = optline.match(el)
1542 m = optline.match(el)
1538 if m:
1543 if m:
1539 conditions = [c for c in m.group(2).split(b' ')]
1544 conditions = [c for c in m.group(2).split(b' ')]
1540
1545
1541 if self._iftest(conditions):
1546 if self._iftest(conditions):
1542 # Don't append as optional line
1547 # Don't append as optional line
1543 continue
1548 continue
1544 else:
1549 else:
1545 continue
1550 continue
1546 postout.append(b' ' + el)
1551 postout.append(b' ' + el)
1547
1552
1548 if lcmd:
1553 if lcmd:
1549 # Add on last return code.
1554 # Add on last return code.
1550 ret = int(lcmd.split()[1])
1555 ret = int(lcmd.split()[1])
1551 if ret != 0:
1556 if ret != 0:
1552 postout.append(b' [%d]\n' % ret)
1557 postout.append(b' [%d]\n' % ret)
1553 if pos in after:
1558 if pos in after:
1554 # Merge in non-active test bits.
1559 # Merge in non-active test bits.
1555 postout += after.pop(pos)
1560 postout += after.pop(pos)
1556 pos = int(lcmd.split()[0])
1561 pos = int(lcmd.split()[0])
1557
1562
1558 if pos in after:
1563 if pos in after:
1559 postout += after.pop(pos)
1564 postout += after.pop(pos)
1560
1565
1561 if warnonly == 2:
1566 if warnonly == 2:
1562 exitcode = False # Set exitcode to warned.
1567 exitcode = False # Set exitcode to warned.
1563
1568
1564 return exitcode, postout
1569 return exitcode, postout
1565
1570
1566 @staticmethod
1571 @staticmethod
1567 def rematch(el, l):
1572 def rematch(el, l):
1568 try:
1573 try:
1569 el = b'(?:' + el + b')'
1574 el = b'(?:' + el + b')'
1570 # use \Z to ensure that the regex matches to the end of the string
1575 # use \Z to ensure that the regex matches to the end of the string
1571 if os.name == 'nt':
1576 if os.name == 'nt':
1572 return re.match(el + br'\r?\n\Z', l)
1577 return re.match(el + br'\r?\n\Z', l)
1573 return re.match(el + br'\n\Z', l)
1578 return re.match(el + br'\n\Z', l)
1574 except re.error:
1579 except re.error:
1575 # el is an invalid regex
1580 # el is an invalid regex
1576 return False
1581 return False
1577
1582
1578 @staticmethod
1583 @staticmethod
1579 def globmatch(el, l):
1584 def globmatch(el, l):
1580 # The only supported special characters are * and ? plus / which also
1585 # The only supported special characters are * and ? plus / which also
1581 # matches \ on windows. Escaping of these characters is supported.
1586 # matches \ on windows. Escaping of these characters is supported.
1582 if el + b'\n' == l:
1587 if el + b'\n' == l:
1583 if os.altsep:
1588 if os.altsep:
1584 # matching on "/" is not needed for this line
1589 # matching on "/" is not needed for this line
1585 for pat in checkcodeglobpats:
1590 for pat in checkcodeglobpats:
1586 if pat.match(el):
1591 if pat.match(el):
1587 return True
1592 return True
1588 return b'-glob'
1593 return b'-glob'
1589 return True
1594 return True
1590 el = el.replace(b'$LOCALIP', b'*')
1595 el = el.replace(b'$LOCALIP', b'*')
1591 i, n = 0, len(el)
1596 i, n = 0, len(el)
1592 res = b''
1597 res = b''
1593 while i < n:
1598 while i < n:
1594 c = el[i:i + 1]
1599 c = el[i:i + 1]
1595 i += 1
1600 i += 1
1596 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1601 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1597 res += el[i - 1:i + 1]
1602 res += el[i - 1:i + 1]
1598 i += 1
1603 i += 1
1599 elif c == b'*':
1604 elif c == b'*':
1600 res += b'.*'
1605 res += b'.*'
1601 elif c == b'?':
1606 elif c == b'?':
1602 res += b'.'
1607 res += b'.'
1603 elif c == b'/' and os.altsep:
1608 elif c == b'/' and os.altsep:
1604 res += b'[/\\\\]'
1609 res += b'[/\\\\]'
1605 else:
1610 else:
1606 res += re.escape(c)
1611 res += re.escape(c)
1607 return TTest.rematch(res, l)
1612 return TTest.rematch(res, l)
1608
1613
1609 def linematch(self, el, l):
1614 def linematch(self, el, l):
1610 if el == l: # perfect match (fast)
1615 if el == l: # perfect match (fast)
1611 return True
1616 return True, True
1612 retry = False
1617 retry = False
1613 if el.endswith(b" (?)\n"):
1618 if el.endswith(b" (?)\n"):
1614 retry = "retry"
1619 retry = "retry"
1615 el = el[:-5] + b"\n"
1620 el = el[:-5] + b"\n"
1616 else:
1621 else:
1617 m = optline.match(el)
1622 m = optline.match(el)
1618 if m:
1623 if m:
1619 conditions = [c for c in m.group(2).split(b' ')]
1624 conditions = [c for c in m.group(2).split(b' ')]
1620
1625
1621 el = m.group(1) + b"\n"
1626 el = m.group(1) + b"\n"
1622 if not self._iftest(conditions):
1627 if not self._iftest(conditions):
1623 retry = "retry" # Not required by listed features
1628 retry = "retry" # Not required by listed features
1624
1629
1625 if el.endswith(b" (esc)\n"):
1630 if el.endswith(b" (esc)\n"):
1626 if PYTHON3:
1631 if PYTHON3:
1627 el = el[:-7].decode('unicode_escape') + '\n'
1632 el = el[:-7].decode('unicode_escape') + '\n'
1628 el = el.encode('utf-8')
1633 el = el.encode('utf-8')
1629 else:
1634 else:
1630 el = el[:-7].decode('string-escape') + '\n'
1635 el = el[:-7].decode('string-escape') + '\n'
1631 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1636 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1632 return True
1637 return True, True
1633 if el.endswith(b" (re)\n"):
1638 if el.endswith(b" (re)\n"):
1634 return TTest.rematch(el[:-6], l) or retry
1639 return (TTest.rematch(el[:-6], l) or retry), False
1635 if el.endswith(b" (glob)\n"):
1640 if el.endswith(b" (glob)\n"):
1636 # ignore '(glob)' added to l by 'replacements'
1641 # ignore '(glob)' added to l by 'replacements'
1637 if l.endswith(b" (glob)\n"):
1642 if l.endswith(b" (glob)\n"):
1638 l = l[:-8] + b"\n"
1643 l = l[:-8] + b"\n"
1639 return TTest.globmatch(el[:-8], l) or retry
1644 return (TTest.globmatch(el[:-8], l) or retry), False
1640 if os.altsep:
1645 if os.altsep:
1641 _l = l.replace(b'\\', b'/')
1646 _l = l.replace(b'\\', b'/')
1642 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1647 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
1643 return True
1648 return True, True
1644 return retry
1649 return retry, True
1645
1650
1646 @staticmethod
1651 @staticmethod
1647 def parsehghaveoutput(lines):
1652 def parsehghaveoutput(lines):
1648 '''Parse hghave log lines.
1653 '''Parse hghave log lines.
1649
1654
1650 Return tuple of lists (missing, failed):
1655 Return tuple of lists (missing, failed):
1651 * the missing/unknown features
1656 * the missing/unknown features
1652 * the features for which existence check failed'''
1657 * the features for which existence check failed'''
1653 missing = []
1658 missing = []
1654 failed = []
1659 failed = []
1655 for line in lines:
1660 for line in lines:
1656 if line.startswith(TTest.SKIPPED_PREFIX):
1661 if line.startswith(TTest.SKIPPED_PREFIX):
1657 line = line.splitlines()[0]
1662 line = line.splitlines()[0]
1658 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1663 missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8'))
1659 elif line.startswith(TTest.FAILED_PREFIX):
1664 elif line.startswith(TTest.FAILED_PREFIX):
1660 line = line.splitlines()[0]
1665 line = line.splitlines()[0]
1661 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1666 failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8'))
1662
1667
1663 return missing, failed
1668 return missing, failed
1664
1669
1665 @staticmethod
1670 @staticmethod
1666 def _escapef(m):
1671 def _escapef(m):
1667 return TTest.ESCAPEMAP[m.group(0)]
1672 return TTest.ESCAPEMAP[m.group(0)]
1668
1673
1669 @staticmethod
1674 @staticmethod
1670 def _stringescape(s):
1675 def _stringescape(s):
1671 return TTest.ESCAPESUB(TTest._escapef, s)
1676 return TTest.ESCAPESUB(TTest._escapef, s)
1672
1677
1673 iolock = threading.RLock()
1678 iolock = threading.RLock()
1674 firstlock = threading.RLock()
1679 firstlock = threading.RLock()
1675 firsterror = False
1680 firsterror = False
1676
1681
1677 class TestResult(unittest._TextTestResult):
1682 class TestResult(unittest._TextTestResult):
1678 """Holds results when executing via unittest."""
1683 """Holds results when executing via unittest."""
1679 # Don't worry too much about accessing the non-public _TextTestResult.
1684 # Don't worry too much about accessing the non-public _TextTestResult.
1680 # It is relatively common in Python testing tools.
1685 # It is relatively common in Python testing tools.
1681 def __init__(self, options, *args, **kwargs):
1686 def __init__(self, options, *args, **kwargs):
1682 super(TestResult, self).__init__(*args, **kwargs)
1687 super(TestResult, self).__init__(*args, **kwargs)
1683
1688
1684 self._options = options
1689 self._options = options
1685
1690
1686 # unittest.TestResult didn't have skipped until 2.7. We need to
1691 # unittest.TestResult didn't have skipped until 2.7. We need to
1687 # polyfill it.
1692 # polyfill it.
1688 self.skipped = []
1693 self.skipped = []
1689
1694
1690 # We have a custom "ignored" result that isn't present in any Python
1695 # We have a custom "ignored" result that isn't present in any Python
1691 # unittest implementation. It is very similar to skipped. It may make
1696 # unittest implementation. It is very similar to skipped. It may make
1692 # sense to map it into skip some day.
1697 # sense to map it into skip some day.
1693 self.ignored = []
1698 self.ignored = []
1694
1699
1695 self.times = []
1700 self.times = []
1696 self._firststarttime = None
1701 self._firststarttime = None
1697 # Data stored for the benefit of generating xunit reports.
1702 # Data stored for the benefit of generating xunit reports.
1698 self.successes = []
1703 self.successes = []
1699 self.faildata = {}
1704 self.faildata = {}
1700
1705
1701 if options.color == 'auto':
1706 if options.color == 'auto':
1702 self.color = pygmentspresent and self.stream.isatty()
1707 self.color = pygmentspresent and self.stream.isatty()
1703 elif options.color == 'never':
1708 elif options.color == 'never':
1704 self.color = False
1709 self.color = False
1705 else: # 'always', for testing purposes
1710 else: # 'always', for testing purposes
1706 self.color = pygmentspresent
1711 self.color = pygmentspresent
1707
1712
1708 def addFailure(self, test, reason):
1713 def addFailure(self, test, reason):
1709 self.failures.append((test, reason))
1714 self.failures.append((test, reason))
1710
1715
1711 if self._options.first:
1716 if self._options.first:
1712 self.stop()
1717 self.stop()
1713 else:
1718 else:
1714 with iolock:
1719 with iolock:
1715 if reason == "timed out":
1720 if reason == "timed out":
1716 self.stream.write('t')
1721 self.stream.write('t')
1717 else:
1722 else:
1718 if not self._options.nodiff:
1723 if not self._options.nodiff:
1719 self.stream.write('\n')
1724 self.stream.write('\n')
1720 # Exclude the '\n' from highlighting to lex correctly
1725 # Exclude the '\n' from highlighting to lex correctly
1721 formatted = 'ERROR: %s output changed\n' % test
1726 formatted = 'ERROR: %s output changed\n' % test
1722 self.stream.write(highlightmsg(formatted, self.color))
1727 self.stream.write(highlightmsg(formatted, self.color))
1723 self.stream.write('!')
1728 self.stream.write('!')
1724
1729
1725 self.stream.flush()
1730 self.stream.flush()
1726
1731
1727 def addSuccess(self, test):
1732 def addSuccess(self, test):
1728 with iolock:
1733 with iolock:
1729 super(TestResult, self).addSuccess(test)
1734 super(TestResult, self).addSuccess(test)
1730 self.successes.append(test)
1735 self.successes.append(test)
1731
1736
1732 def addError(self, test, err):
1737 def addError(self, test, err):
1733 super(TestResult, self).addError(test, err)
1738 super(TestResult, self).addError(test, err)
1734 if self._options.first:
1739 if self._options.first:
1735 self.stop()
1740 self.stop()
1736
1741
1737 # Polyfill.
1742 # Polyfill.
1738 def addSkip(self, test, reason):
1743 def addSkip(self, test, reason):
1739 self.skipped.append((test, reason))
1744 self.skipped.append((test, reason))
1740 with iolock:
1745 with iolock:
1741 if self.showAll:
1746 if self.showAll:
1742 self.stream.writeln('skipped %s' % reason)
1747 self.stream.writeln('skipped %s' % reason)
1743 else:
1748 else:
1744 self.stream.write('s')
1749 self.stream.write('s')
1745 self.stream.flush()
1750 self.stream.flush()
1746
1751
1747 def addIgnore(self, test, reason):
1752 def addIgnore(self, test, reason):
1748 self.ignored.append((test, reason))
1753 self.ignored.append((test, reason))
1749 with iolock:
1754 with iolock:
1750 if self.showAll:
1755 if self.showAll:
1751 self.stream.writeln('ignored %s' % reason)
1756 self.stream.writeln('ignored %s' % reason)
1752 else:
1757 else:
1753 if reason not in ('not retesting', "doesn't match keyword"):
1758 if reason not in ('not retesting', "doesn't match keyword"):
1754 self.stream.write('i')
1759 self.stream.write('i')
1755 else:
1760 else:
1756 self.testsRun += 1
1761 self.testsRun += 1
1757 self.stream.flush()
1762 self.stream.flush()
1758
1763
1759 def addOutputMismatch(self, test, ret, got, expected):
1764 def addOutputMismatch(self, test, ret, got, expected):
1760 """Record a mismatch in test output for a particular test."""
1765 """Record a mismatch in test output for a particular test."""
1761 if self.shouldStop or firsterror:
1766 if self.shouldStop or firsterror:
1762 # don't print, some other test case already failed and
1767 # don't print, some other test case already failed and
1763 # printed, we're just stale and probably failed due to our
1768 # printed, we're just stale and probably failed due to our
1764 # temp dir getting cleaned up.
1769 # temp dir getting cleaned up.
1765 return
1770 return
1766
1771
1767 accepted = False
1772 accepted = False
1768 lines = []
1773 lines = []
1769
1774
1770 with iolock:
1775 with iolock:
1771 if self._options.nodiff:
1776 if self._options.nodiff:
1772 pass
1777 pass
1773 elif self._options.view:
1778 elif self._options.view:
1774 v = self._options.view
1779 v = self._options.view
1775 if PYTHON3:
1780 if PYTHON3:
1776 v = _bytespath(v)
1781 v = _bytespath(v)
1777 os.system(b"%s %s %s" %
1782 os.system(b"%s %s %s" %
1778 (v, test.refpath, test.errpath))
1783 (v, test.refpath, test.errpath))
1779 else:
1784 else:
1780 servefail, lines = getdiff(expected, got,
1785 servefail, lines = getdiff(expected, got,
1781 test.refpath, test.errpath)
1786 test.refpath, test.errpath)
1782 self.stream.write('\n')
1787 self.stream.write('\n')
1783 for line in lines:
1788 for line in lines:
1784 line = highlightdiff(line, self.color)
1789 line = highlightdiff(line, self.color)
1785 if PYTHON3:
1790 if PYTHON3:
1786 self.stream.flush()
1791 self.stream.flush()
1787 self.stream.buffer.write(line)
1792 self.stream.buffer.write(line)
1788 self.stream.buffer.flush()
1793 self.stream.buffer.flush()
1789 else:
1794 else:
1790 self.stream.write(line)
1795 self.stream.write(line)
1791 self.stream.flush()
1796 self.stream.flush()
1792
1797
1793 if servefail:
1798 if servefail:
1794 raise test.failureException(
1799 raise test.failureException(
1795 'server failed to start (HGPORT=%s)' % test._startport)
1800 'server failed to start (HGPORT=%s)' % test._startport)
1796
1801
1797 # handle interactive prompt without releasing iolock
1802 # handle interactive prompt without releasing iolock
1798 if self._options.interactive:
1803 if self._options.interactive:
1799 if test.readrefout() != expected:
1804 if test.readrefout() != expected:
1800 self.stream.write(
1805 self.stream.write(
1801 'Reference output has changed (run again to prompt '
1806 'Reference output has changed (run again to prompt '
1802 'changes)')
1807 'changes)')
1803 else:
1808 else:
1804 self.stream.write('Accept this change? [n] ')
1809 self.stream.write('Accept this change? [n] ')
1805 answer = sys.stdin.readline().strip()
1810 answer = sys.stdin.readline().strip()
1806 if answer.lower() in ('y', 'yes'):
1811 if answer.lower() in ('y', 'yes'):
1807 if test.path.endswith(b'.t'):
1812 if test.path.endswith(b'.t'):
1808 rename(test.errpath, test.path)
1813 rename(test.errpath, test.path)
1809 else:
1814 else:
1810 rename(test.errpath, '%s.out' % test.path)
1815 rename(test.errpath, '%s.out' % test.path)
1811 accepted = True
1816 accepted = True
1812 if not accepted:
1817 if not accepted:
1813 self.faildata[test.name] = b''.join(lines)
1818 self.faildata[test.name] = b''.join(lines)
1814
1819
1815 return accepted
1820 return accepted
1816
1821
1817 def startTest(self, test):
1822 def startTest(self, test):
1818 super(TestResult, self).startTest(test)
1823 super(TestResult, self).startTest(test)
1819
1824
1820 # os.times module computes the user time and system time spent by
1825 # os.times module computes the user time and system time spent by
1821 # child's processes along with real elapsed time taken by a process.
1826 # child's processes along with real elapsed time taken by a process.
1822 # This module has one limitation. It can only work for Linux user
1827 # This module has one limitation. It can only work for Linux user
1823 # and not for Windows.
1828 # and not for Windows.
1824 test.started = os.times()
1829 test.started = os.times()
1825 if self._firststarttime is None: # thread racy but irrelevant
1830 if self._firststarttime is None: # thread racy but irrelevant
1826 self._firststarttime = test.started[4]
1831 self._firststarttime = test.started[4]
1827
1832
1828 def stopTest(self, test, interrupted=False):
1833 def stopTest(self, test, interrupted=False):
1829 super(TestResult, self).stopTest(test)
1834 super(TestResult, self).stopTest(test)
1830
1835
1831 test.stopped = os.times()
1836 test.stopped = os.times()
1832
1837
1833 starttime = test.started
1838 starttime = test.started
1834 endtime = test.stopped
1839 endtime = test.stopped
1835 origin = self._firststarttime
1840 origin = self._firststarttime
1836 self.times.append((test.name,
1841 self.times.append((test.name,
1837 endtime[2] - starttime[2], # user space CPU time
1842 endtime[2] - starttime[2], # user space CPU time
1838 endtime[3] - starttime[3], # sys space CPU time
1843 endtime[3] - starttime[3], # sys space CPU time
1839 endtime[4] - starttime[4], # real time
1844 endtime[4] - starttime[4], # real time
1840 starttime[4] - origin, # start date in run context
1845 starttime[4] - origin, # start date in run context
1841 endtime[4] - origin, # end date in run context
1846 endtime[4] - origin, # end date in run context
1842 ))
1847 ))
1843
1848
1844 if interrupted:
1849 if interrupted:
1845 with iolock:
1850 with iolock:
1846 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1851 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1847 test.name, self.times[-1][3]))
1852 test.name, self.times[-1][3]))
1848
1853
1849 class TestSuite(unittest.TestSuite):
1854 class TestSuite(unittest.TestSuite):
1850 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1855 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1851
1856
1852 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1857 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1853 retest=False, keywords=None, loop=False, runs_per_test=1,
1858 retest=False, keywords=None, loop=False, runs_per_test=1,
1854 loadtest=None, showchannels=False,
1859 loadtest=None, showchannels=False,
1855 *args, **kwargs):
1860 *args, **kwargs):
1856 """Create a new instance that can run tests with a configuration.
1861 """Create a new instance that can run tests with a configuration.
1857
1862
1858 testdir specifies the directory where tests are executed from. This
1863 testdir specifies the directory where tests are executed from. This
1859 is typically the ``tests`` directory from Mercurial's source
1864 is typically the ``tests`` directory from Mercurial's source
1860 repository.
1865 repository.
1861
1866
1862 jobs specifies the number of jobs to run concurrently. Each test
1867 jobs specifies the number of jobs to run concurrently. Each test
1863 executes on its own thread. Tests actually spawn new processes, so
1868 executes on its own thread. Tests actually spawn new processes, so
1864 state mutation should not be an issue.
1869 state mutation should not be an issue.
1865
1870
1866 If there is only one job, it will use the main thread.
1871 If there is only one job, it will use the main thread.
1867
1872
1868 whitelist and blacklist denote tests that have been whitelisted and
1873 whitelist and blacklist denote tests that have been whitelisted and
1869 blacklisted, respectively. These arguments don't belong in TestSuite.
1874 blacklisted, respectively. These arguments don't belong in TestSuite.
1870 Instead, whitelist and blacklist should be handled by the thing that
1875 Instead, whitelist and blacklist should be handled by the thing that
1871 populates the TestSuite with tests. They are present to preserve
1876 populates the TestSuite with tests. They are present to preserve
1872 backwards compatible behavior which reports skipped tests as part
1877 backwards compatible behavior which reports skipped tests as part
1873 of the results.
1878 of the results.
1874
1879
1875 retest denotes whether to retest failed tests. This arguably belongs
1880 retest denotes whether to retest failed tests. This arguably belongs
1876 outside of TestSuite.
1881 outside of TestSuite.
1877
1882
1878 keywords denotes key words that will be used to filter which tests
1883 keywords denotes key words that will be used to filter which tests
1879 to execute. This arguably belongs outside of TestSuite.
1884 to execute. This arguably belongs outside of TestSuite.
1880
1885
1881 loop denotes whether to loop over tests forever.
1886 loop denotes whether to loop over tests forever.
1882 """
1887 """
1883 super(TestSuite, self).__init__(*args, **kwargs)
1888 super(TestSuite, self).__init__(*args, **kwargs)
1884
1889
1885 self._jobs = jobs
1890 self._jobs = jobs
1886 self._whitelist = whitelist
1891 self._whitelist = whitelist
1887 self._blacklist = blacklist
1892 self._blacklist = blacklist
1888 self._retest = retest
1893 self._retest = retest
1889 self._keywords = keywords
1894 self._keywords = keywords
1890 self._loop = loop
1895 self._loop = loop
1891 self._runs_per_test = runs_per_test
1896 self._runs_per_test = runs_per_test
1892 self._loadtest = loadtest
1897 self._loadtest = loadtest
1893 self._showchannels = showchannels
1898 self._showchannels = showchannels
1894
1899
1895 def run(self, result):
1900 def run(self, result):
1896 # We have a number of filters that need to be applied. We do this
1901 # We have a number of filters that need to be applied. We do this
1897 # here instead of inside Test because it makes the running logic for
1902 # here instead of inside Test because it makes the running logic for
1898 # Test simpler.
1903 # Test simpler.
1899 tests = []
1904 tests = []
1900 num_tests = [0]
1905 num_tests = [0]
1901 for test in self._tests:
1906 for test in self._tests:
1902 def get():
1907 def get():
1903 num_tests[0] += 1
1908 num_tests[0] += 1
1904 if getattr(test, 'should_reload', False):
1909 if getattr(test, 'should_reload', False):
1905 return self._loadtest(test, num_tests[0])
1910 return self._loadtest(test, num_tests[0])
1906 return test
1911 return test
1907 if not os.path.exists(test.path):
1912 if not os.path.exists(test.path):
1908 result.addSkip(test, "Doesn't exist")
1913 result.addSkip(test, "Doesn't exist")
1909 continue
1914 continue
1910
1915
1911 if not (self._whitelist and test.bname in self._whitelist):
1916 if not (self._whitelist and test.bname in self._whitelist):
1912 if self._blacklist and test.bname in self._blacklist:
1917 if self._blacklist and test.bname in self._blacklist:
1913 result.addSkip(test, 'blacklisted')
1918 result.addSkip(test, 'blacklisted')
1914 continue
1919 continue
1915
1920
1916 if self._retest and not os.path.exists(test.errpath):
1921 if self._retest and not os.path.exists(test.errpath):
1917 result.addIgnore(test, 'not retesting')
1922 result.addIgnore(test, 'not retesting')
1918 continue
1923 continue
1919
1924
1920 if self._keywords:
1925 if self._keywords:
1921 with open(test.path, 'rb') as f:
1926 with open(test.path, 'rb') as f:
1922 t = f.read().lower() + test.bname.lower()
1927 t = f.read().lower() + test.bname.lower()
1923 ignored = False
1928 ignored = False
1924 for k in self._keywords.lower().split():
1929 for k in self._keywords.lower().split():
1925 if k not in t:
1930 if k not in t:
1926 result.addIgnore(test, "doesn't match keyword")
1931 result.addIgnore(test, "doesn't match keyword")
1927 ignored = True
1932 ignored = True
1928 break
1933 break
1929
1934
1930 if ignored:
1935 if ignored:
1931 continue
1936 continue
1932 for _ in xrange(self._runs_per_test):
1937 for _ in xrange(self._runs_per_test):
1933 tests.append(get())
1938 tests.append(get())
1934
1939
1935 runtests = list(tests)
1940 runtests = list(tests)
1936 done = queue.Queue()
1941 done = queue.Queue()
1937 running = 0
1942 running = 0
1938
1943
1939 channels = [""] * self._jobs
1944 channels = [""] * self._jobs
1940
1945
1941 def job(test, result):
1946 def job(test, result):
1942 for n, v in enumerate(channels):
1947 for n, v in enumerate(channels):
1943 if not v:
1948 if not v:
1944 channel = n
1949 channel = n
1945 break
1950 break
1946 else:
1951 else:
1947 raise ValueError('Could not find output channel')
1952 raise ValueError('Could not find output channel')
1948 channels[channel] = "=" + test.name[5:].split(".")[0]
1953 channels[channel] = "=" + test.name[5:].split(".")[0]
1949 try:
1954 try:
1950 test(result)
1955 test(result)
1951 done.put(None)
1956 done.put(None)
1952 except KeyboardInterrupt:
1957 except KeyboardInterrupt:
1953 pass
1958 pass
1954 except: # re-raises
1959 except: # re-raises
1955 done.put(('!', test, 'run-test raised an error, see traceback'))
1960 done.put(('!', test, 'run-test raised an error, see traceback'))
1956 raise
1961 raise
1957 finally:
1962 finally:
1958 try:
1963 try:
1959 channels[channel] = ''
1964 channels[channel] = ''
1960 except IndexError:
1965 except IndexError:
1961 pass
1966 pass
1962
1967
1963 def stat():
1968 def stat():
1964 count = 0
1969 count = 0
1965 while channels:
1970 while channels:
1966 d = '\n%03s ' % count
1971 d = '\n%03s ' % count
1967 for n, v in enumerate(channels):
1972 for n, v in enumerate(channels):
1968 if v:
1973 if v:
1969 d += v[0]
1974 d += v[0]
1970 channels[n] = v[1:] or '.'
1975 channels[n] = v[1:] or '.'
1971 else:
1976 else:
1972 d += ' '
1977 d += ' '
1973 d += ' '
1978 d += ' '
1974 with iolock:
1979 with iolock:
1975 sys.stdout.write(d + ' ')
1980 sys.stdout.write(d + ' ')
1976 sys.stdout.flush()
1981 sys.stdout.flush()
1977 for x in xrange(10):
1982 for x in xrange(10):
1978 if channels:
1983 if channels:
1979 time.sleep(.1)
1984 time.sleep(.1)
1980 count += 1
1985 count += 1
1981
1986
1982 stoppedearly = False
1987 stoppedearly = False
1983
1988
1984 if self._showchannels:
1989 if self._showchannels:
1985 statthread = threading.Thread(target=stat, name="stat")
1990 statthread = threading.Thread(target=stat, name="stat")
1986 statthread.start()
1991 statthread.start()
1987
1992
1988 try:
1993 try:
1989 while tests or running:
1994 while tests or running:
1990 if not done.empty() or running == self._jobs or not tests:
1995 if not done.empty() or running == self._jobs or not tests:
1991 try:
1996 try:
1992 done.get(True, 1)
1997 done.get(True, 1)
1993 running -= 1
1998 running -= 1
1994 if result and result.shouldStop:
1999 if result and result.shouldStop:
1995 stoppedearly = True
2000 stoppedearly = True
1996 break
2001 break
1997 except queue.Empty:
2002 except queue.Empty:
1998 continue
2003 continue
1999 if tests and not running == self._jobs:
2004 if tests and not running == self._jobs:
2000 test = tests.pop(0)
2005 test = tests.pop(0)
2001 if self._loop:
2006 if self._loop:
2002 if getattr(test, 'should_reload', False):
2007 if getattr(test, 'should_reload', False):
2003 num_tests[0] += 1
2008 num_tests[0] += 1
2004 tests.append(
2009 tests.append(
2005 self._loadtest(test, num_tests[0]))
2010 self._loadtest(test, num_tests[0]))
2006 else:
2011 else:
2007 tests.append(test)
2012 tests.append(test)
2008 if self._jobs == 1:
2013 if self._jobs == 1:
2009 job(test, result)
2014 job(test, result)
2010 else:
2015 else:
2011 t = threading.Thread(target=job, name=test.name,
2016 t = threading.Thread(target=job, name=test.name,
2012 args=(test, result))
2017 args=(test, result))
2013 t.start()
2018 t.start()
2014 running += 1
2019 running += 1
2015
2020
2016 # If we stop early we still need to wait on started tests to
2021 # If we stop early we still need to wait on started tests to
2017 # finish. Otherwise, there is a race between the test completing
2022 # finish. Otherwise, there is a race between the test completing
2018 # and the test's cleanup code running. This could result in the
2023 # and the test's cleanup code running. This could result in the
2019 # test reporting incorrect.
2024 # test reporting incorrect.
2020 if stoppedearly:
2025 if stoppedearly:
2021 while running:
2026 while running:
2022 try:
2027 try:
2023 done.get(True, 1)
2028 done.get(True, 1)
2024 running -= 1
2029 running -= 1
2025 except queue.Empty:
2030 except queue.Empty:
2026 continue
2031 continue
2027 except KeyboardInterrupt:
2032 except KeyboardInterrupt:
2028 for test in runtests:
2033 for test in runtests:
2029 test.abort()
2034 test.abort()
2030
2035
2031 channels = []
2036 channels = []
2032
2037
2033 return result
2038 return result
2034
2039
2035 # Save the most recent 5 wall-clock runtimes of each test to a
2040 # Save the most recent 5 wall-clock runtimes of each test to a
2036 # human-readable text file named .testtimes. Tests are sorted
2041 # human-readable text file named .testtimes. Tests are sorted
2037 # alphabetically, while times for each test are listed from oldest to
2042 # alphabetically, while times for each test are listed from oldest to
2038 # newest.
2043 # newest.
2039
2044
2040 def loadtimes(outputdir):
2045 def loadtimes(outputdir):
2041 times = []
2046 times = []
2042 try:
2047 try:
2043 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2048 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2044 for line in fp:
2049 for line in fp:
2045 m = re.match('(.*?) ([0-9. ]+)', line)
2050 m = re.match('(.*?) ([0-9. ]+)', line)
2046 times.append((m.group(1),
2051 times.append((m.group(1),
2047 [float(t) for t in m.group(2).split()]))
2052 [float(t) for t in m.group(2).split()]))
2048 except IOError as err:
2053 except IOError as err:
2049 if err.errno != errno.ENOENT:
2054 if err.errno != errno.ENOENT:
2050 raise
2055 raise
2051 return times
2056 return times
2052
2057
2053 def savetimes(outputdir, result):
2058 def savetimes(outputdir, result):
2054 saved = dict(loadtimes(outputdir))
2059 saved = dict(loadtimes(outputdir))
2055 maxruns = 5
2060 maxruns = 5
2056 skipped = set([str(t[0]) for t in result.skipped])
2061 skipped = set([str(t[0]) for t in result.skipped])
2057 for tdata in result.times:
2062 for tdata in result.times:
2058 test, real = tdata[0], tdata[3]
2063 test, real = tdata[0], tdata[3]
2059 if test not in skipped:
2064 if test not in skipped:
2060 ts = saved.setdefault(test, [])
2065 ts = saved.setdefault(test, [])
2061 ts.append(real)
2066 ts.append(real)
2062 ts[:] = ts[-maxruns:]
2067 ts[:] = ts[-maxruns:]
2063
2068
2064 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2069 fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes',
2065 dir=outputdir, text=True)
2070 dir=outputdir, text=True)
2066 with os.fdopen(fd, 'w') as fp:
2071 with os.fdopen(fd, 'w') as fp:
2067 for name, ts in sorted(saved.items()):
2072 for name, ts in sorted(saved.items()):
2068 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2073 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2069 timepath = os.path.join(outputdir, b'.testtimes')
2074 timepath = os.path.join(outputdir, b'.testtimes')
2070 try:
2075 try:
2071 os.unlink(timepath)
2076 os.unlink(timepath)
2072 except OSError:
2077 except OSError:
2073 pass
2078 pass
2074 try:
2079 try:
2075 os.rename(tmpname, timepath)
2080 os.rename(tmpname, timepath)
2076 except OSError:
2081 except OSError:
2077 pass
2082 pass
2078
2083
2079 class TextTestRunner(unittest.TextTestRunner):
2084 class TextTestRunner(unittest.TextTestRunner):
2080 """Custom unittest test runner that uses appropriate settings."""
2085 """Custom unittest test runner that uses appropriate settings."""
2081
2086
2082 def __init__(self, runner, *args, **kwargs):
2087 def __init__(self, runner, *args, **kwargs):
2083 super(TextTestRunner, self).__init__(*args, **kwargs)
2088 super(TextTestRunner, self).__init__(*args, **kwargs)
2084
2089
2085 self._runner = runner
2090 self._runner = runner
2086
2091
2087 def listtests(self, test):
2092 def listtests(self, test):
2088 result = TestResult(self._runner.options, self.stream,
2093 result = TestResult(self._runner.options, self.stream,
2089 self.descriptions, 0)
2094 self.descriptions, 0)
2090 test = sorted(test, key=lambda t: t.name)
2095 test = sorted(test, key=lambda t: t.name)
2091 for t in test:
2096 for t in test:
2092 print(t.name)
2097 print(t.name)
2093 result.addSuccess(t)
2098 result.addSuccess(t)
2094
2099
2095 if self._runner.options.xunit:
2100 if self._runner.options.xunit:
2096 with open(self._runner.options.xunit, "wb") as xuf:
2101 with open(self._runner.options.xunit, "wb") as xuf:
2097 self._writexunit(result, xuf)
2102 self._writexunit(result, xuf)
2098
2103
2099 if self._runner.options.json:
2104 if self._runner.options.json:
2100 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2105 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2101 with open(jsonpath, 'w') as fp:
2106 with open(jsonpath, 'w') as fp:
2102 self._writejson(result, fp)
2107 self._writejson(result, fp)
2103
2108
2104 return result
2109 return result
2105
2110
2106 def run(self, test):
2111 def run(self, test):
2107 result = TestResult(self._runner.options, self.stream,
2112 result = TestResult(self._runner.options, self.stream,
2108 self.descriptions, self.verbosity)
2113 self.descriptions, self.verbosity)
2109
2114
2110 test(result)
2115 test(result)
2111
2116
2112 failed = len(result.failures)
2117 failed = len(result.failures)
2113 skipped = len(result.skipped)
2118 skipped = len(result.skipped)
2114 ignored = len(result.ignored)
2119 ignored = len(result.ignored)
2115
2120
2116 with iolock:
2121 with iolock:
2117 self.stream.writeln('')
2122 self.stream.writeln('')
2118
2123
2119 if not self._runner.options.noskips:
2124 if not self._runner.options.noskips:
2120 for test, msg in result.skipped:
2125 for test, msg in result.skipped:
2121 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2126 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2122 self.stream.write(highlightmsg(formatted, result.color))
2127 self.stream.write(highlightmsg(formatted, result.color))
2123 for test, msg in result.failures:
2128 for test, msg in result.failures:
2124 formatted = 'Failed %s: %s\n' % (test.name, msg)
2129 formatted = 'Failed %s: %s\n' % (test.name, msg)
2125 self.stream.write(highlightmsg(formatted, result.color))
2130 self.stream.write(highlightmsg(formatted, result.color))
2126 for test, msg in result.errors:
2131 for test, msg in result.errors:
2127 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2132 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2128
2133
2129 if self._runner.options.xunit:
2134 if self._runner.options.xunit:
2130 with open(self._runner.options.xunit, "wb") as xuf:
2135 with open(self._runner.options.xunit, "wb") as xuf:
2131 self._writexunit(result, xuf)
2136 self._writexunit(result, xuf)
2132
2137
2133 if self._runner.options.json:
2138 if self._runner.options.json:
2134 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2139 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2135 with open(jsonpath, 'w') as fp:
2140 with open(jsonpath, 'w') as fp:
2136 self._writejson(result, fp)
2141 self._writejson(result, fp)
2137
2142
2138 self._runner._checkhglib('Tested')
2143 self._runner._checkhglib('Tested')
2139
2144
2140 savetimes(self._runner._outputdir, result)
2145 savetimes(self._runner._outputdir, result)
2141
2146
2142 if failed and self._runner.options.known_good_rev:
2147 if failed and self._runner.options.known_good_rev:
2143 self._bisecttests(t for t, m in result.failures)
2148 self._bisecttests(t for t, m in result.failures)
2144 self.stream.writeln(
2149 self.stream.writeln(
2145 '# Ran %d tests, %d skipped, %d failed.'
2150 '# Ran %d tests, %d skipped, %d failed.'
2146 % (result.testsRun, skipped + ignored, failed))
2151 % (result.testsRun, skipped + ignored, failed))
2147 if failed:
2152 if failed:
2148 self.stream.writeln('python hash seed: %s' %
2153 self.stream.writeln('python hash seed: %s' %
2149 os.environ['PYTHONHASHSEED'])
2154 os.environ['PYTHONHASHSEED'])
2150 if self._runner.options.time:
2155 if self._runner.options.time:
2151 self.printtimes(result.times)
2156 self.printtimes(result.times)
2152
2157
2153 if self._runner.options.exceptions:
2158 if self._runner.options.exceptions:
2154 exceptions = aggregateexceptions(
2159 exceptions = aggregateexceptions(
2155 os.path.join(self._runner._outputdir, b'exceptions'))
2160 os.path.join(self._runner._outputdir, b'exceptions'))
2156
2161
2157 self.stream.writeln('Exceptions Report:')
2162 self.stream.writeln('Exceptions Report:')
2158 self.stream.writeln('%d total from %d frames' %
2163 self.stream.writeln('%d total from %d frames' %
2159 (exceptions['total'],
2164 (exceptions['total'],
2160 len(exceptions['exceptioncounts'])))
2165 len(exceptions['exceptioncounts'])))
2161 combined = exceptions['combined']
2166 combined = exceptions['combined']
2162 for key in sorted(combined, key=combined.get, reverse=True):
2167 for key in sorted(combined, key=combined.get, reverse=True):
2163 frame, line, exc = key
2168 frame, line, exc = key
2164 totalcount, testcount, leastcount, leasttest = combined[key]
2169 totalcount, testcount, leastcount, leasttest = combined[key]
2165
2170
2166 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2171 self.stream.writeln('%d (%d tests)\t%s: %s (%s - %d total)'
2167 % (totalcount,
2172 % (totalcount,
2168 testcount,
2173 testcount,
2169 frame, exc,
2174 frame, exc,
2170 leasttest, leastcount))
2175 leasttest, leastcount))
2171
2176
2172 self.stream.flush()
2177 self.stream.flush()
2173
2178
2174 return result
2179 return result
2175
2180
2176 def _bisecttests(self, tests):
2181 def _bisecttests(self, tests):
2177 bisectcmd = ['hg', 'bisect']
2182 bisectcmd = ['hg', 'bisect']
2178 bisectrepo = self._runner.options.bisect_repo
2183 bisectrepo = self._runner.options.bisect_repo
2179 if bisectrepo:
2184 if bisectrepo:
2180 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2185 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2181 def pread(args):
2186 def pread(args):
2182 env = os.environ.copy()
2187 env = os.environ.copy()
2183 env['HGPLAIN'] = '1'
2188 env['HGPLAIN'] = '1'
2184 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2189 p = subprocess.Popen(args, stderr=subprocess.STDOUT,
2185 stdout=subprocess.PIPE, env=env)
2190 stdout=subprocess.PIPE, env=env)
2186 data = p.stdout.read()
2191 data = p.stdout.read()
2187 p.wait()
2192 p.wait()
2188 return data
2193 return data
2189 for test in tests:
2194 for test in tests:
2190 pread(bisectcmd + ['--reset']),
2195 pread(bisectcmd + ['--reset']),
2191 pread(bisectcmd + ['--bad', '.'])
2196 pread(bisectcmd + ['--bad', '.'])
2192 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2197 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2193 # TODO: we probably need to forward more options
2198 # TODO: we probably need to forward more options
2194 # that alter hg's behavior inside the tests.
2199 # that alter hg's behavior inside the tests.
2195 opts = ''
2200 opts = ''
2196 withhg = self._runner.options.with_hg
2201 withhg = self._runner.options.with_hg
2197 if withhg:
2202 if withhg:
2198 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2203 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
2199 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2204 rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
2200 test)
2205 test)
2201 data = pread(bisectcmd + ['--command', rtc])
2206 data = pread(bisectcmd + ['--command', rtc])
2202 m = re.search(
2207 m = re.search(
2203 (br'\nThe first (?P<goodbad>bad|good) revision '
2208 (br'\nThe first (?P<goodbad>bad|good) revision '
2204 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2209 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2205 br'summary: +(?P<summary>[^\n]+)\n'),
2210 br'summary: +(?P<summary>[^\n]+)\n'),
2206 data, (re.MULTILINE | re.DOTALL))
2211 data, (re.MULTILINE | re.DOTALL))
2207 if m is None:
2212 if m is None:
2208 self.stream.writeln(
2213 self.stream.writeln(
2209 'Failed to identify failure point for %s' % test)
2214 'Failed to identify failure point for %s' % test)
2210 continue
2215 continue
2211 dat = m.groupdict()
2216 dat = m.groupdict()
2212 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2217 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2213 self.stream.writeln(
2218 self.stream.writeln(
2214 '%s %s by %s (%s)' % (
2219 '%s %s by %s (%s)' % (
2215 test, verb, dat['node'].decode('ascii'),
2220 test, verb, dat['node'].decode('ascii'),
2216 dat['summary'].decode('utf8', 'ignore')))
2221 dat['summary'].decode('utf8', 'ignore')))
2217
2222
2218 def printtimes(self, times):
2223 def printtimes(self, times):
2219 # iolock held by run
2224 # iolock held by run
2220 self.stream.writeln('# Producing time report')
2225 self.stream.writeln('# Producing time report')
2221 times.sort(key=lambda t: (t[3]))
2226 times.sort(key=lambda t: (t[3]))
2222 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2227 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2223 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2228 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
2224 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2229 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
2225 for tdata in times:
2230 for tdata in times:
2226 test = tdata[0]
2231 test = tdata[0]
2227 cuser, csys, real, start, end = tdata[1:6]
2232 cuser, csys, real, start, end = tdata[1:6]
2228 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2233 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2229
2234
2230 @staticmethod
2235 @staticmethod
2231 def _writexunit(result, outf):
2236 def _writexunit(result, outf):
2232 # See http://llg.cubic.org/docs/junit/ for a reference.
2237 # See http://llg.cubic.org/docs/junit/ for a reference.
2233 timesd = dict((t[0], t[3]) for t in result.times)
2238 timesd = dict((t[0], t[3]) for t in result.times)
2234 doc = minidom.Document()
2239 doc = minidom.Document()
2235 s = doc.createElement('testsuite')
2240 s = doc.createElement('testsuite')
2236 s.setAttribute('name', 'run-tests')
2241 s.setAttribute('name', 'run-tests')
2237 s.setAttribute('tests', str(result.testsRun))
2242 s.setAttribute('tests', str(result.testsRun))
2238 s.setAttribute('errors', "0") # TODO
2243 s.setAttribute('errors', "0") # TODO
2239 s.setAttribute('failures', str(len(result.failures)))
2244 s.setAttribute('failures', str(len(result.failures)))
2240 s.setAttribute('skipped', str(len(result.skipped) +
2245 s.setAttribute('skipped', str(len(result.skipped) +
2241 len(result.ignored)))
2246 len(result.ignored)))
2242 doc.appendChild(s)
2247 doc.appendChild(s)
2243 for tc in result.successes:
2248 for tc in result.successes:
2244 t = doc.createElement('testcase')
2249 t = doc.createElement('testcase')
2245 t.setAttribute('name', tc.name)
2250 t.setAttribute('name', tc.name)
2246 tctime = timesd.get(tc.name)
2251 tctime = timesd.get(tc.name)
2247 if tctime is not None:
2252 if tctime is not None:
2248 t.setAttribute('time', '%.3f' % tctime)
2253 t.setAttribute('time', '%.3f' % tctime)
2249 s.appendChild(t)
2254 s.appendChild(t)
2250 for tc, err in sorted(result.faildata.items()):
2255 for tc, err in sorted(result.faildata.items()):
2251 t = doc.createElement('testcase')
2256 t = doc.createElement('testcase')
2252 t.setAttribute('name', tc)
2257 t.setAttribute('name', tc)
2253 tctime = timesd.get(tc)
2258 tctime = timesd.get(tc)
2254 if tctime is not None:
2259 if tctime is not None:
2255 t.setAttribute('time', '%.3f' % tctime)
2260 t.setAttribute('time', '%.3f' % tctime)
2256 # createCDATASection expects a unicode or it will
2261 # createCDATASection expects a unicode or it will
2257 # convert using default conversion rules, which will
2262 # convert using default conversion rules, which will
2258 # fail if string isn't ASCII.
2263 # fail if string isn't ASCII.
2259 err = cdatasafe(err).decode('utf-8', 'replace')
2264 err = cdatasafe(err).decode('utf-8', 'replace')
2260 cd = doc.createCDATASection(err)
2265 cd = doc.createCDATASection(err)
2261 # Use 'failure' here instead of 'error' to match errors = 0,
2266 # Use 'failure' here instead of 'error' to match errors = 0,
2262 # failures = len(result.failures) in the testsuite element.
2267 # failures = len(result.failures) in the testsuite element.
2263 failelem = doc.createElement('failure')
2268 failelem = doc.createElement('failure')
2264 failelem.setAttribute('message', 'output changed')
2269 failelem.setAttribute('message', 'output changed')
2265 failelem.setAttribute('type', 'output-mismatch')
2270 failelem.setAttribute('type', 'output-mismatch')
2266 failelem.appendChild(cd)
2271 failelem.appendChild(cd)
2267 t.appendChild(failelem)
2272 t.appendChild(failelem)
2268 s.appendChild(t)
2273 s.appendChild(t)
2269 for tc, message in result.skipped:
2274 for tc, message in result.skipped:
2270 # According to the schema, 'skipped' has no attributes. So store
2275 # According to the schema, 'skipped' has no attributes. So store
2271 # the skip message as a text node instead.
2276 # the skip message as a text node instead.
2272 t = doc.createElement('testcase')
2277 t = doc.createElement('testcase')
2273 t.setAttribute('name', tc.name)
2278 t.setAttribute('name', tc.name)
2274 binmessage = message.encode('utf-8')
2279 binmessage = message.encode('utf-8')
2275 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2280 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2276 cd = doc.createCDATASection(message)
2281 cd = doc.createCDATASection(message)
2277 skipelem = doc.createElement('skipped')
2282 skipelem = doc.createElement('skipped')
2278 skipelem.appendChild(cd)
2283 skipelem.appendChild(cd)
2279 t.appendChild(skipelem)
2284 t.appendChild(skipelem)
2280 s.appendChild(t)
2285 s.appendChild(t)
2281 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2286 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2282
2287
2283 @staticmethod
2288 @staticmethod
2284 def _writejson(result, outf):
2289 def _writejson(result, outf):
2285 timesd = {}
2290 timesd = {}
2286 for tdata in result.times:
2291 for tdata in result.times:
2287 test = tdata[0]
2292 test = tdata[0]
2288 timesd[test] = tdata[1:]
2293 timesd[test] = tdata[1:]
2289
2294
2290 outcome = {}
2295 outcome = {}
2291 groups = [('success', ((tc, None)
2296 groups = [('success', ((tc, None)
2292 for tc in result.successes)),
2297 for tc in result.successes)),
2293 ('failure', result.failures),
2298 ('failure', result.failures),
2294 ('skip', result.skipped)]
2299 ('skip', result.skipped)]
2295 for res, testcases in groups:
2300 for res, testcases in groups:
2296 for tc, __ in testcases:
2301 for tc, __ in testcases:
2297 if tc.name in timesd:
2302 if tc.name in timesd:
2298 diff = result.faildata.get(tc.name, b'')
2303 diff = result.faildata.get(tc.name, b'')
2299 try:
2304 try:
2300 diff = diff.decode('unicode_escape')
2305 diff = diff.decode('unicode_escape')
2301 except UnicodeDecodeError as e:
2306 except UnicodeDecodeError as e:
2302 diff = '%r decoding diff, sorry' % e
2307 diff = '%r decoding diff, sorry' % e
2303 tres = {'result': res,
2308 tres = {'result': res,
2304 'time': ('%0.3f' % timesd[tc.name][2]),
2309 'time': ('%0.3f' % timesd[tc.name][2]),
2305 'cuser': ('%0.3f' % timesd[tc.name][0]),
2310 'cuser': ('%0.3f' % timesd[tc.name][0]),
2306 'csys': ('%0.3f' % timesd[tc.name][1]),
2311 'csys': ('%0.3f' % timesd[tc.name][1]),
2307 'start': ('%0.3f' % timesd[tc.name][3]),
2312 'start': ('%0.3f' % timesd[tc.name][3]),
2308 'end': ('%0.3f' % timesd[tc.name][4]),
2313 'end': ('%0.3f' % timesd[tc.name][4]),
2309 'diff': diff,
2314 'diff': diff,
2310 }
2315 }
2311 else:
2316 else:
2312 # blacklisted test
2317 # blacklisted test
2313 tres = {'result': res}
2318 tres = {'result': res}
2314
2319
2315 outcome[tc.name] = tres
2320 outcome[tc.name] = tres
2316 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2321 jsonout = json.dumps(outcome, sort_keys=True, indent=4,
2317 separators=(',', ': '))
2322 separators=(',', ': '))
2318 outf.writelines(("testreport =", jsonout))
2323 outf.writelines(("testreport =", jsonout))
2319
2324
2320 def sorttests(testdescs, previoustimes, shuffle=False):
2325 def sorttests(testdescs, previoustimes, shuffle=False):
2321 """Do an in-place sort of tests."""
2326 """Do an in-place sort of tests."""
2322 if shuffle:
2327 if shuffle:
2323 random.shuffle(testdescs)
2328 random.shuffle(testdescs)
2324 return
2329 return
2325
2330
2326 if previoustimes:
2331 if previoustimes:
2327 def sortkey(f):
2332 def sortkey(f):
2328 f = f['path']
2333 f = f['path']
2329 if f in previoustimes:
2334 if f in previoustimes:
2330 # Use most recent time as estimate
2335 # Use most recent time as estimate
2331 return -previoustimes[f][-1]
2336 return -previoustimes[f][-1]
2332 else:
2337 else:
2333 # Default to a rather arbitrary value of 1 second for new tests
2338 # Default to a rather arbitrary value of 1 second for new tests
2334 return -1.0
2339 return -1.0
2335 else:
2340 else:
2336 # keywords for slow tests
2341 # keywords for slow tests
2337 slow = {b'svn': 10,
2342 slow = {b'svn': 10,
2338 b'cvs': 10,
2343 b'cvs': 10,
2339 b'hghave': 10,
2344 b'hghave': 10,
2340 b'largefiles-update': 10,
2345 b'largefiles-update': 10,
2341 b'run-tests': 10,
2346 b'run-tests': 10,
2342 b'corruption': 10,
2347 b'corruption': 10,
2343 b'race': 10,
2348 b'race': 10,
2344 b'i18n': 10,
2349 b'i18n': 10,
2345 b'check': 100,
2350 b'check': 100,
2346 b'gendoc': 100,
2351 b'gendoc': 100,
2347 b'contrib-perf': 200,
2352 b'contrib-perf': 200,
2348 }
2353 }
2349 perf = {}
2354 perf = {}
2350
2355
2351 def sortkey(f):
2356 def sortkey(f):
2352 # run largest tests first, as they tend to take the longest
2357 # run largest tests first, as they tend to take the longest
2353 f = f['path']
2358 f = f['path']
2354 try:
2359 try:
2355 return perf[f]
2360 return perf[f]
2356 except KeyError:
2361 except KeyError:
2357 try:
2362 try:
2358 val = -os.stat(f).st_size
2363 val = -os.stat(f).st_size
2359 except OSError as e:
2364 except OSError as e:
2360 if e.errno != errno.ENOENT:
2365 if e.errno != errno.ENOENT:
2361 raise
2366 raise
2362 perf[f] = -1e9 # file does not exist, tell early
2367 perf[f] = -1e9 # file does not exist, tell early
2363 return -1e9
2368 return -1e9
2364 for kw, mul in slow.items():
2369 for kw, mul in slow.items():
2365 if kw in f:
2370 if kw in f:
2366 val *= mul
2371 val *= mul
2367 if f.endswith(b'.py'):
2372 if f.endswith(b'.py'):
2368 val /= 10.0
2373 val /= 10.0
2369 perf[f] = val / 1000.0
2374 perf[f] = val / 1000.0
2370 return perf[f]
2375 return perf[f]
2371
2376
2372 testdescs.sort(key=sortkey)
2377 testdescs.sort(key=sortkey)
2373
2378
2374 class TestRunner(object):
2379 class TestRunner(object):
2375 """Holds context for executing tests.
2380 """Holds context for executing tests.
2376
2381
2377 Tests rely on a lot of state. This object holds it for them.
2382 Tests rely on a lot of state. This object holds it for them.
2378 """
2383 """
2379
2384
2380 # Programs required to run tests.
2385 # Programs required to run tests.
2381 REQUIREDTOOLS = [
2386 REQUIREDTOOLS = [
2382 b'diff',
2387 b'diff',
2383 b'grep',
2388 b'grep',
2384 b'unzip',
2389 b'unzip',
2385 b'gunzip',
2390 b'gunzip',
2386 b'bunzip2',
2391 b'bunzip2',
2387 b'sed',
2392 b'sed',
2388 ]
2393 ]
2389
2394
2390 # Maps file extensions to test class.
2395 # Maps file extensions to test class.
2391 TESTTYPES = [
2396 TESTTYPES = [
2392 (b'.py', PythonTest),
2397 (b'.py', PythonTest),
2393 (b'.t', TTest),
2398 (b'.t', TTest),
2394 ]
2399 ]
2395
2400
2396 def __init__(self):
2401 def __init__(self):
2397 self.options = None
2402 self.options = None
2398 self._hgroot = None
2403 self._hgroot = None
2399 self._testdir = None
2404 self._testdir = None
2400 self._outputdir = None
2405 self._outputdir = None
2401 self._hgtmp = None
2406 self._hgtmp = None
2402 self._installdir = None
2407 self._installdir = None
2403 self._bindir = None
2408 self._bindir = None
2404 self._tmpbinddir = None
2409 self._tmpbinddir = None
2405 self._pythondir = None
2410 self._pythondir = None
2406 self._coveragefile = None
2411 self._coveragefile = None
2407 self._createdfiles = []
2412 self._createdfiles = []
2408 self._hgcommand = None
2413 self._hgcommand = None
2409 self._hgpath = None
2414 self._hgpath = None
2410 self._portoffset = 0
2415 self._portoffset = 0
2411 self._ports = {}
2416 self._ports = {}
2412
2417
2413 def run(self, args, parser=None):
2418 def run(self, args, parser=None):
2414 """Run the test suite."""
2419 """Run the test suite."""
2415 oldmask = os.umask(0o22)
2420 oldmask = os.umask(0o22)
2416 try:
2421 try:
2417 parser = parser or getparser()
2422 parser = parser or getparser()
2418 options = parseargs(args, parser)
2423 options = parseargs(args, parser)
2419 tests = [_bytespath(a) for a in options.tests]
2424 tests = [_bytespath(a) for a in options.tests]
2420 if options.test_list is not None:
2425 if options.test_list is not None:
2421 for listfile in options.test_list:
2426 for listfile in options.test_list:
2422 with open(listfile, 'rb') as f:
2427 with open(listfile, 'rb') as f:
2423 tests.extend(t for t in f.read().splitlines() if t)
2428 tests.extend(t for t in f.read().splitlines() if t)
2424 self.options = options
2429 self.options = options
2425
2430
2426 self._checktools()
2431 self._checktools()
2427 testdescs = self.findtests(tests)
2432 testdescs = self.findtests(tests)
2428 if options.profile_runner:
2433 if options.profile_runner:
2429 import statprof
2434 import statprof
2430 statprof.start()
2435 statprof.start()
2431 result = self._run(testdescs)
2436 result = self._run(testdescs)
2432 if options.profile_runner:
2437 if options.profile_runner:
2433 statprof.stop()
2438 statprof.stop()
2434 statprof.display()
2439 statprof.display()
2435 return result
2440 return result
2436
2441
2437 finally:
2442 finally:
2438 os.umask(oldmask)
2443 os.umask(oldmask)
2439
2444
2440 def _run(self, testdescs):
2445 def _run(self, testdescs):
2441 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2446 self._testdir = osenvironb[b'TESTDIR'] = getattr(
2442 os, 'getcwdb', os.getcwd)()
2447 os, 'getcwdb', os.getcwd)()
2443 # assume all tests in same folder for now
2448 # assume all tests in same folder for now
2444 if testdescs:
2449 if testdescs:
2445 pathname = os.path.dirname(testdescs[0]['path'])
2450 pathname = os.path.dirname(testdescs[0]['path'])
2446 if pathname:
2451 if pathname:
2447 osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'],
2452 osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'],
2448 pathname)
2453 pathname)
2449 if self.options.outputdir:
2454 if self.options.outputdir:
2450 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2455 self._outputdir = canonpath(_bytespath(self.options.outputdir))
2451 else:
2456 else:
2452 self._outputdir = self._testdir
2457 self._outputdir = self._testdir
2453 if testdescs and pathname:
2458 if testdescs and pathname:
2454 self._outputdir = os.path.join(self._outputdir, pathname)
2459 self._outputdir = os.path.join(self._outputdir, pathname)
2455 previoustimes = {}
2460 previoustimes = {}
2456 if self.options.order_by_runtime:
2461 if self.options.order_by_runtime:
2457 previoustimes = dict(loadtimes(self._outputdir))
2462 previoustimes = dict(loadtimes(self._outputdir))
2458 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2463 sorttests(testdescs, previoustimes, shuffle=self.options.random)
2459
2464
2460 if 'PYTHONHASHSEED' not in os.environ:
2465 if 'PYTHONHASHSEED' not in os.environ:
2461 # use a random python hash seed all the time
2466 # use a random python hash seed all the time
2462 # we do the randomness ourself to know what seed is used
2467 # we do the randomness ourself to know what seed is used
2463 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2468 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
2464
2469
2465 if self.options.tmpdir:
2470 if self.options.tmpdir:
2466 self.options.keep_tmpdir = True
2471 self.options.keep_tmpdir = True
2467 tmpdir = _bytespath(self.options.tmpdir)
2472 tmpdir = _bytespath(self.options.tmpdir)
2468 if os.path.exists(tmpdir):
2473 if os.path.exists(tmpdir):
2469 # Meaning of tmpdir has changed since 1.3: we used to create
2474 # Meaning of tmpdir has changed since 1.3: we used to create
2470 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2475 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
2471 # tmpdir already exists.
2476 # tmpdir already exists.
2472 print("error: temp dir %r already exists" % tmpdir)
2477 print("error: temp dir %r already exists" % tmpdir)
2473 return 1
2478 return 1
2474
2479
2475 os.makedirs(tmpdir)
2480 os.makedirs(tmpdir)
2476 else:
2481 else:
2477 d = None
2482 d = None
2478 if os.name == 'nt':
2483 if os.name == 'nt':
2479 # without this, we get the default temp dir location, but
2484 # without this, we get the default temp dir location, but
2480 # in all lowercase, which causes troubles with paths (issue3490)
2485 # in all lowercase, which causes troubles with paths (issue3490)
2481 d = osenvironb.get(b'TMP', None)
2486 d = osenvironb.get(b'TMP', None)
2482 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2487 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
2483
2488
2484 self._hgtmp = osenvironb[b'HGTMP'] = (
2489 self._hgtmp = osenvironb[b'HGTMP'] = (
2485 os.path.realpath(tmpdir))
2490 os.path.realpath(tmpdir))
2486
2491
2487 if self.options.with_hg:
2492 if self.options.with_hg:
2488 self._installdir = None
2493 self._installdir = None
2489 whg = self.options.with_hg
2494 whg = self.options.with_hg
2490 self._bindir = os.path.dirname(os.path.realpath(whg))
2495 self._bindir = os.path.dirname(os.path.realpath(whg))
2491 assert isinstance(self._bindir, bytes)
2496 assert isinstance(self._bindir, bytes)
2492 self._hgcommand = os.path.basename(whg)
2497 self._hgcommand = os.path.basename(whg)
2493 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2498 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
2494 os.makedirs(self._tmpbindir)
2499 os.makedirs(self._tmpbindir)
2495
2500
2496 normbin = os.path.normpath(os.path.abspath(whg))
2501 normbin = os.path.normpath(os.path.abspath(whg))
2497 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2502 normbin = normbin.replace(os.sep.encode('ascii'), b'/')
2498
2503
2499 # Other Python scripts in the test harness need to
2504 # Other Python scripts in the test harness need to
2500 # `import mercurial`. If `hg` is a Python script, we assume
2505 # `import mercurial`. If `hg` is a Python script, we assume
2501 # the Mercurial modules are relative to its path and tell the tests
2506 # the Mercurial modules are relative to its path and tell the tests
2502 # to load Python modules from its directory.
2507 # to load Python modules from its directory.
2503 with open(whg, 'rb') as fh:
2508 with open(whg, 'rb') as fh:
2504 initial = fh.read(1024)
2509 initial = fh.read(1024)
2505
2510
2506 if re.match(b'#!.*python', initial):
2511 if re.match(b'#!.*python', initial):
2507 self._pythondir = self._bindir
2512 self._pythondir = self._bindir
2508 # If it looks like our in-repo Rust binary, use the source root.
2513 # If it looks like our in-repo Rust binary, use the source root.
2509 # This is a bit hacky. But rhg is still not supported outside the
2514 # This is a bit hacky. But rhg is still not supported outside the
2510 # source directory. So until it is, do the simple thing.
2515 # source directory. So until it is, do the simple thing.
2511 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2516 elif re.search(b'/rust/target/[^/]+/hg', normbin):
2512 self._pythondir = os.path.dirname(self._testdir)
2517 self._pythondir = os.path.dirname(self._testdir)
2513 # Fall back to the legacy behavior.
2518 # Fall back to the legacy behavior.
2514 else:
2519 else:
2515 self._pythondir = self._bindir
2520 self._pythondir = self._bindir
2516
2521
2517 else:
2522 else:
2518 self._installdir = os.path.join(self._hgtmp, b"install")
2523 self._installdir = os.path.join(self._hgtmp, b"install")
2519 self._bindir = os.path.join(self._installdir, b"bin")
2524 self._bindir = os.path.join(self._installdir, b"bin")
2520 self._hgcommand = b'hg'
2525 self._hgcommand = b'hg'
2521 self._tmpbindir = self._bindir
2526 self._tmpbindir = self._bindir
2522 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2527 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
2523
2528
2524 # set CHGHG, then replace "hg" command by "chg"
2529 # set CHGHG, then replace "hg" command by "chg"
2525 chgbindir = self._bindir
2530 chgbindir = self._bindir
2526 if self.options.chg or self.options.with_chg:
2531 if self.options.chg or self.options.with_chg:
2527 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2532 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
2528 else:
2533 else:
2529 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2534 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
2530 if self.options.chg:
2535 if self.options.chg:
2531 self._hgcommand = b'chg'
2536 self._hgcommand = b'chg'
2532 elif self.options.with_chg:
2537 elif self.options.with_chg:
2533 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2538 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
2534 self._hgcommand = os.path.basename(self.options.with_chg)
2539 self._hgcommand = os.path.basename(self.options.with_chg)
2535
2540
2536 osenvironb[b"BINDIR"] = self._bindir
2541 osenvironb[b"BINDIR"] = self._bindir
2537 osenvironb[b"PYTHON"] = PYTHON
2542 osenvironb[b"PYTHON"] = PYTHON
2538
2543
2539 if self.options.with_python3:
2544 if self.options.with_python3:
2540 osenvironb[b'PYTHON3'] = self.options.with_python3
2545 osenvironb[b'PYTHON3'] = self.options.with_python3
2541
2546
2542 fileb = _bytespath(__file__)
2547 fileb = _bytespath(__file__)
2543 runtestdir = os.path.abspath(os.path.dirname(fileb))
2548 runtestdir = os.path.abspath(os.path.dirname(fileb))
2544 osenvironb[b'RUNTESTDIR'] = runtestdir
2549 osenvironb[b'RUNTESTDIR'] = runtestdir
2545 if PYTHON3:
2550 if PYTHON3:
2546 sepb = _bytespath(os.pathsep)
2551 sepb = _bytespath(os.pathsep)
2547 else:
2552 else:
2548 sepb = os.pathsep
2553 sepb = os.pathsep
2549 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2554 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
2550 if os.path.islink(__file__):
2555 if os.path.islink(__file__):
2551 # test helper will likely be at the end of the symlink
2556 # test helper will likely be at the end of the symlink
2552 realfile = os.path.realpath(fileb)
2557 realfile = os.path.realpath(fileb)
2553 realdir = os.path.abspath(os.path.dirname(realfile))
2558 realdir = os.path.abspath(os.path.dirname(realfile))
2554 path.insert(2, realdir)
2559 path.insert(2, realdir)
2555 if chgbindir != self._bindir:
2560 if chgbindir != self._bindir:
2556 path.insert(1, chgbindir)
2561 path.insert(1, chgbindir)
2557 if self._testdir != runtestdir:
2562 if self._testdir != runtestdir:
2558 path = [self._testdir] + path
2563 path = [self._testdir] + path
2559 if self._tmpbindir != self._bindir:
2564 if self._tmpbindir != self._bindir:
2560 path = [self._tmpbindir] + path
2565 path = [self._tmpbindir] + path
2561 osenvironb[b"PATH"] = sepb.join(path)
2566 osenvironb[b"PATH"] = sepb.join(path)
2562
2567
2563 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2568 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2564 # can run .../tests/run-tests.py test-foo where test-foo
2569 # can run .../tests/run-tests.py test-foo where test-foo
2565 # adds an extension to HGRC. Also include run-test.py directory to
2570 # adds an extension to HGRC. Also include run-test.py directory to
2566 # import modules like heredoctest.
2571 # import modules like heredoctest.
2567 pypath = [self._pythondir, self._testdir, runtestdir]
2572 pypath = [self._pythondir, self._testdir, runtestdir]
2568 # We have to augment PYTHONPATH, rather than simply replacing
2573 # We have to augment PYTHONPATH, rather than simply replacing
2569 # it, in case external libraries are only available via current
2574 # it, in case external libraries are only available via current
2570 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2575 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2571 # are in /opt/subversion.)
2576 # are in /opt/subversion.)
2572 oldpypath = osenvironb.get(IMPL_PATH)
2577 oldpypath = osenvironb.get(IMPL_PATH)
2573 if oldpypath:
2578 if oldpypath:
2574 pypath.append(oldpypath)
2579 pypath.append(oldpypath)
2575 osenvironb[IMPL_PATH] = sepb.join(pypath)
2580 osenvironb[IMPL_PATH] = sepb.join(pypath)
2576
2581
2577 if self.options.pure:
2582 if self.options.pure:
2578 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2583 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2579 os.environ["HGMODULEPOLICY"] = "py"
2584 os.environ["HGMODULEPOLICY"] = "py"
2580
2585
2581 if self.options.allow_slow_tests:
2586 if self.options.allow_slow_tests:
2582 os.environ["HGTEST_SLOW"] = "slow"
2587 os.environ["HGTEST_SLOW"] = "slow"
2583 elif 'HGTEST_SLOW' in os.environ:
2588 elif 'HGTEST_SLOW' in os.environ:
2584 del os.environ['HGTEST_SLOW']
2589 del os.environ['HGTEST_SLOW']
2585
2590
2586 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2591 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2587
2592
2588 if self.options.exceptions:
2593 if self.options.exceptions:
2589 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2594 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
2590 try:
2595 try:
2591 os.makedirs(exceptionsdir)
2596 os.makedirs(exceptionsdir)
2592 except OSError as e:
2597 except OSError as e:
2593 if e.errno != errno.EEXIST:
2598 if e.errno != errno.EEXIST:
2594 raise
2599 raise
2595
2600
2596 # Remove all existing exception reports.
2601 # Remove all existing exception reports.
2597 for f in os.listdir(exceptionsdir):
2602 for f in os.listdir(exceptionsdir):
2598 os.unlink(os.path.join(exceptionsdir, f))
2603 os.unlink(os.path.join(exceptionsdir, f))
2599
2604
2600 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2605 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
2601 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2606 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
2602 self.options.extra_config_opt.append(
2607 self.options.extra_config_opt.append(
2603 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2608 'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
2604
2609
2605 vlog("# Using TESTDIR", self._testdir)
2610 vlog("# Using TESTDIR", self._testdir)
2606 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2611 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2607 vlog("# Using HGTMP", self._hgtmp)
2612 vlog("# Using HGTMP", self._hgtmp)
2608 vlog("# Using PATH", os.environ["PATH"])
2613 vlog("# Using PATH", os.environ["PATH"])
2609 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2614 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2610 vlog("# Writing to directory", self._outputdir)
2615 vlog("# Writing to directory", self._outputdir)
2611
2616
2612 try:
2617 try:
2613 return self._runtests(testdescs) or 0
2618 return self._runtests(testdescs) or 0
2614 finally:
2619 finally:
2615 time.sleep(.1)
2620 time.sleep(.1)
2616 self._cleanup()
2621 self._cleanup()
2617
2622
2618 def findtests(self, args):
2623 def findtests(self, args):
2619 """Finds possible test files from arguments.
2624 """Finds possible test files from arguments.
2620
2625
2621 If you wish to inject custom tests into the test harness, this would
2626 If you wish to inject custom tests into the test harness, this would
2622 be a good function to monkeypatch or override in a derived class.
2627 be a good function to monkeypatch or override in a derived class.
2623 """
2628 """
2624 if not args:
2629 if not args:
2625 if self.options.changed:
2630 if self.options.changed:
2626 proc = Popen4('hg st --rev "%s" -man0 .' %
2631 proc = Popen4('hg st --rev "%s" -man0 .' %
2627 self.options.changed, None, 0)
2632 self.options.changed, None, 0)
2628 stdout, stderr = proc.communicate()
2633 stdout, stderr = proc.communicate()
2629 args = stdout.strip(b'\0').split(b'\0')
2634 args = stdout.strip(b'\0').split(b'\0')
2630 else:
2635 else:
2631 args = os.listdir(b'.')
2636 args = os.listdir(b'.')
2632
2637
2633 expanded_args = []
2638 expanded_args = []
2634 for arg in args:
2639 for arg in args:
2635 if os.path.isdir(arg):
2640 if os.path.isdir(arg):
2636 if not arg.endswith(b'/'):
2641 if not arg.endswith(b'/'):
2637 arg += b'/'
2642 arg += b'/'
2638 expanded_args.extend([arg + a for a in os.listdir(arg)])
2643 expanded_args.extend([arg + a for a in os.listdir(arg)])
2639 else:
2644 else:
2640 expanded_args.append(arg)
2645 expanded_args.append(arg)
2641 args = expanded_args
2646 args = expanded_args
2642
2647
2643 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.]+))')
2648 testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.]+))')
2644 tests = []
2649 tests = []
2645 for t in args:
2650 for t in args:
2646 case = None
2651 case = None
2647
2652
2648 if not (os.path.basename(t).startswith(b'test-')
2653 if not (os.path.basename(t).startswith(b'test-')
2649 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2654 and (t.endswith(b'.py') or t.endswith(b'.t'))):
2650
2655
2651 m = testcasepattern.match(t)
2656 m = testcasepattern.match(t)
2652 if m is not None:
2657 if m is not None:
2653 t, _, case = m.groups()
2658 t, _, case = m.groups()
2654 else:
2659 else:
2655 continue
2660 continue
2656
2661
2657 if t.endswith(b'.t'):
2662 if t.endswith(b'.t'):
2658 # .t file may contain multiple test cases
2663 # .t file may contain multiple test cases
2659 cases = sorted(parsettestcases(t))
2664 cases = sorted(parsettestcases(t))
2660 if cases:
2665 if cases:
2661 if case is not None and case in cases:
2666 if case is not None and case in cases:
2662 tests += [{'path': t, 'case': case}]
2667 tests += [{'path': t, 'case': case}]
2663 elif case is not None and case not in cases:
2668 elif case is not None and case not in cases:
2664 # Ignore invalid cases
2669 # Ignore invalid cases
2665 pass
2670 pass
2666 else:
2671 else:
2667 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2672 tests += [{'path': t, 'case': c} for c in sorted(cases)]
2668 else:
2673 else:
2669 tests.append({'path': t})
2674 tests.append({'path': t})
2670 else:
2675 else:
2671 tests.append({'path': t})
2676 tests.append({'path': t})
2672 return tests
2677 return tests
2673
2678
2674 def _runtests(self, testdescs):
2679 def _runtests(self, testdescs):
2675 def _reloadtest(test, i):
2680 def _reloadtest(test, i):
2676 # convert a test back to its description dict
2681 # convert a test back to its description dict
2677 desc = {'path': test.path}
2682 desc = {'path': test.path}
2678 case = getattr(test, '_case', None)
2683 case = getattr(test, '_case', None)
2679 if case:
2684 if case:
2680 desc['case'] = case
2685 desc['case'] = case
2681 return self._gettest(desc, i)
2686 return self._gettest(desc, i)
2682
2687
2683 try:
2688 try:
2684 if self.options.restart:
2689 if self.options.restart:
2685 orig = list(testdescs)
2690 orig = list(testdescs)
2686 while testdescs:
2691 while testdescs:
2687 desc = testdescs[0]
2692 desc = testdescs[0]
2688 # desc['path'] is a relative path
2693 # desc['path'] is a relative path
2689 if 'case' in desc:
2694 if 'case' in desc:
2690 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2695 errpath = b'%s.%s.err' % (desc['path'], desc['case'])
2691 else:
2696 else:
2692 errpath = b'%s.err' % desc['path']
2697 errpath = b'%s.err' % desc['path']
2693 errpath = os.path.join(self._outputdir, errpath)
2698 errpath = os.path.join(self._outputdir, errpath)
2694 if os.path.exists(errpath):
2699 if os.path.exists(errpath):
2695 break
2700 break
2696 testdescs.pop(0)
2701 testdescs.pop(0)
2697 if not testdescs:
2702 if not testdescs:
2698 print("running all tests")
2703 print("running all tests")
2699 testdescs = orig
2704 testdescs = orig
2700
2705
2701 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2706 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
2702
2707
2703 failed = False
2708 failed = False
2704 kws = self.options.keywords
2709 kws = self.options.keywords
2705 if kws is not None and PYTHON3:
2710 if kws is not None and PYTHON3:
2706 kws = kws.encode('utf-8')
2711 kws = kws.encode('utf-8')
2707
2712
2708 suite = TestSuite(self._testdir,
2713 suite = TestSuite(self._testdir,
2709 jobs=self.options.jobs,
2714 jobs=self.options.jobs,
2710 whitelist=self.options.whitelisted,
2715 whitelist=self.options.whitelisted,
2711 blacklist=self.options.blacklist,
2716 blacklist=self.options.blacklist,
2712 retest=self.options.retest,
2717 retest=self.options.retest,
2713 keywords=kws,
2718 keywords=kws,
2714 loop=self.options.loop,
2719 loop=self.options.loop,
2715 runs_per_test=self.options.runs_per_test,
2720 runs_per_test=self.options.runs_per_test,
2716 showchannels=self.options.showchannels,
2721 showchannels=self.options.showchannels,
2717 tests=tests, loadtest=_reloadtest)
2722 tests=tests, loadtest=_reloadtest)
2718 verbosity = 1
2723 verbosity = 1
2719 if self.options.verbose:
2724 if self.options.verbose:
2720 verbosity = 2
2725 verbosity = 2
2721 runner = TextTestRunner(self, verbosity=verbosity)
2726 runner = TextTestRunner(self, verbosity=verbosity)
2722
2727
2723 if self.options.list_tests:
2728 if self.options.list_tests:
2724 result = runner.listtests(suite)
2729 result = runner.listtests(suite)
2725 else:
2730 else:
2726 if self._installdir:
2731 if self._installdir:
2727 self._installhg()
2732 self._installhg()
2728 self._checkhglib("Testing")
2733 self._checkhglib("Testing")
2729 else:
2734 else:
2730 self._usecorrectpython()
2735 self._usecorrectpython()
2731 if self.options.chg:
2736 if self.options.chg:
2732 assert self._installdir
2737 assert self._installdir
2733 self._installchg()
2738 self._installchg()
2734
2739
2735 result = runner.run(suite)
2740 result = runner.run(suite)
2736
2741
2737 if result.failures:
2742 if result.failures:
2738 failed = True
2743 failed = True
2739
2744
2740 if self.options.anycoverage:
2745 if self.options.anycoverage:
2741 self._outputcoverage()
2746 self._outputcoverage()
2742 except KeyboardInterrupt:
2747 except KeyboardInterrupt:
2743 failed = True
2748 failed = True
2744 print("\ninterrupted!")
2749 print("\ninterrupted!")
2745
2750
2746 if failed:
2751 if failed:
2747 return 1
2752 return 1
2748
2753
2749 def _getport(self, count):
2754 def _getport(self, count):
2750 port = self._ports.get(count) # do we have a cached entry?
2755 port = self._ports.get(count) # do we have a cached entry?
2751 if port is None:
2756 if port is None:
2752 portneeded = 3
2757 portneeded = 3
2753 # above 100 tries we just give up and let test reports failure
2758 # above 100 tries we just give up and let test reports failure
2754 for tries in xrange(100):
2759 for tries in xrange(100):
2755 allfree = True
2760 allfree = True
2756 port = self.options.port + self._portoffset
2761 port = self.options.port + self._portoffset
2757 for idx in xrange(portneeded):
2762 for idx in xrange(portneeded):
2758 if not checkportisavailable(port + idx):
2763 if not checkportisavailable(port + idx):
2759 allfree = False
2764 allfree = False
2760 break
2765 break
2761 self._portoffset += portneeded
2766 self._portoffset += portneeded
2762 if allfree:
2767 if allfree:
2763 break
2768 break
2764 self._ports[count] = port
2769 self._ports[count] = port
2765 return port
2770 return port
2766
2771
2767 def _gettest(self, testdesc, count):
2772 def _gettest(self, testdesc, count):
2768 """Obtain a Test by looking at its filename.
2773 """Obtain a Test by looking at its filename.
2769
2774
2770 Returns a Test instance. The Test may not be runnable if it doesn't
2775 Returns a Test instance. The Test may not be runnable if it doesn't
2771 map to a known type.
2776 map to a known type.
2772 """
2777 """
2773 path = testdesc['path']
2778 path = testdesc['path']
2774 lctest = path.lower()
2779 lctest = path.lower()
2775 testcls = Test
2780 testcls = Test
2776
2781
2777 for ext, cls in self.TESTTYPES:
2782 for ext, cls in self.TESTTYPES:
2778 if lctest.endswith(ext):
2783 if lctest.endswith(ext):
2779 testcls = cls
2784 testcls = cls
2780 break
2785 break
2781
2786
2782 refpath = os.path.join(self._testdir, path)
2787 refpath = os.path.join(self._testdir, path)
2783 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2788 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2784
2789
2785 # extra keyword parameters. 'case' is used by .t tests
2790 # extra keyword parameters. 'case' is used by .t tests
2786 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2791 kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc)
2787
2792
2788 t = testcls(refpath, self._outputdir, tmpdir,
2793 t = testcls(refpath, self._outputdir, tmpdir,
2789 keeptmpdir=self.options.keep_tmpdir,
2794 keeptmpdir=self.options.keep_tmpdir,
2790 debug=self.options.debug,
2795 debug=self.options.debug,
2791 first=self.options.first,
2796 first=self.options.first,
2792 timeout=self.options.timeout,
2797 timeout=self.options.timeout,
2793 startport=self._getport(count),
2798 startport=self._getport(count),
2794 extraconfigopts=self.options.extra_config_opt,
2799 extraconfigopts=self.options.extra_config_opt,
2795 py3kwarnings=self.options.py3k_warnings,
2800 py3kwarnings=self.options.py3k_warnings,
2796 shell=self.options.shell,
2801 shell=self.options.shell,
2797 hgcommand=self._hgcommand,
2802 hgcommand=self._hgcommand,
2798 usechg=bool(self.options.with_chg or self.options.chg),
2803 usechg=bool(self.options.with_chg or self.options.chg),
2799 useipv6=useipv6, **kwds)
2804 useipv6=useipv6, **kwds)
2800 t.should_reload = True
2805 t.should_reload = True
2801 return t
2806 return t
2802
2807
2803 def _cleanup(self):
2808 def _cleanup(self):
2804 """Clean up state from this test invocation."""
2809 """Clean up state from this test invocation."""
2805 if self.options.keep_tmpdir:
2810 if self.options.keep_tmpdir:
2806 return
2811 return
2807
2812
2808 vlog("# Cleaning up HGTMP", self._hgtmp)
2813 vlog("# Cleaning up HGTMP", self._hgtmp)
2809 shutil.rmtree(self._hgtmp, True)
2814 shutil.rmtree(self._hgtmp, True)
2810 for f in self._createdfiles:
2815 for f in self._createdfiles:
2811 try:
2816 try:
2812 os.remove(f)
2817 os.remove(f)
2813 except OSError:
2818 except OSError:
2814 pass
2819 pass
2815
2820
2816 def _usecorrectpython(self):
2821 def _usecorrectpython(self):
2817 """Configure the environment to use the appropriate Python in tests."""
2822 """Configure the environment to use the appropriate Python in tests."""
2818 # Tests must use the same interpreter as us or bad things will happen.
2823 # Tests must use the same interpreter as us or bad things will happen.
2819 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2824 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2820 if getattr(os, 'symlink', None):
2825 if getattr(os, 'symlink', None):
2821 vlog("# Making python executable in test path a symlink to '%s'" %
2826 vlog("# Making python executable in test path a symlink to '%s'" %
2822 sys.executable)
2827 sys.executable)
2823 mypython = os.path.join(self._tmpbindir, pyexename)
2828 mypython = os.path.join(self._tmpbindir, pyexename)
2824 try:
2829 try:
2825 if os.readlink(mypython) == sys.executable:
2830 if os.readlink(mypython) == sys.executable:
2826 return
2831 return
2827 os.unlink(mypython)
2832 os.unlink(mypython)
2828 except OSError as err:
2833 except OSError as err:
2829 if err.errno != errno.ENOENT:
2834 if err.errno != errno.ENOENT:
2830 raise
2835 raise
2831 if self._findprogram(pyexename) != sys.executable:
2836 if self._findprogram(pyexename) != sys.executable:
2832 try:
2837 try:
2833 os.symlink(sys.executable, mypython)
2838 os.symlink(sys.executable, mypython)
2834 self._createdfiles.append(mypython)
2839 self._createdfiles.append(mypython)
2835 except OSError as err:
2840 except OSError as err:
2836 # child processes may race, which is harmless
2841 # child processes may race, which is harmless
2837 if err.errno != errno.EEXIST:
2842 if err.errno != errno.EEXIST:
2838 raise
2843 raise
2839 else:
2844 else:
2840 exedir, exename = os.path.split(sys.executable)
2845 exedir, exename = os.path.split(sys.executable)
2841 vlog("# Modifying search path to find %s as %s in '%s'" %
2846 vlog("# Modifying search path to find %s as %s in '%s'" %
2842 (exename, pyexename, exedir))
2847 (exename, pyexename, exedir))
2843 path = os.environ['PATH'].split(os.pathsep)
2848 path = os.environ['PATH'].split(os.pathsep)
2844 while exedir in path:
2849 while exedir in path:
2845 path.remove(exedir)
2850 path.remove(exedir)
2846 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2851 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2847 if not self._findprogram(pyexename):
2852 if not self._findprogram(pyexename):
2848 print("WARNING: Cannot find %s in search path" % pyexename)
2853 print("WARNING: Cannot find %s in search path" % pyexename)
2849
2854
2850 def _installhg(self):
2855 def _installhg(self):
2851 """Install hg into the test environment.
2856 """Install hg into the test environment.
2852
2857
2853 This will also configure hg with the appropriate testing settings.
2858 This will also configure hg with the appropriate testing settings.
2854 """
2859 """
2855 vlog("# Performing temporary installation of HG")
2860 vlog("# Performing temporary installation of HG")
2856 installerrs = os.path.join(self._hgtmp, b"install.err")
2861 installerrs = os.path.join(self._hgtmp, b"install.err")
2857 compiler = ''
2862 compiler = ''
2858 if self.options.compiler:
2863 if self.options.compiler:
2859 compiler = '--compiler ' + self.options.compiler
2864 compiler = '--compiler ' + self.options.compiler
2860 if self.options.pure:
2865 if self.options.pure:
2861 pure = b"--pure"
2866 pure = b"--pure"
2862 else:
2867 else:
2863 pure = b""
2868 pure = b""
2864
2869
2865 # Run installer in hg root
2870 # Run installer in hg root
2866 script = os.path.realpath(sys.argv[0])
2871 script = os.path.realpath(sys.argv[0])
2867 exe = sys.executable
2872 exe = sys.executable
2868 if PYTHON3:
2873 if PYTHON3:
2869 compiler = _bytespath(compiler)
2874 compiler = _bytespath(compiler)
2870 script = _bytespath(script)
2875 script = _bytespath(script)
2871 exe = _bytespath(exe)
2876 exe = _bytespath(exe)
2872 hgroot = os.path.dirname(os.path.dirname(script))
2877 hgroot = os.path.dirname(os.path.dirname(script))
2873 self._hgroot = hgroot
2878 self._hgroot = hgroot
2874 os.chdir(hgroot)
2879 os.chdir(hgroot)
2875 nohome = b'--home=""'
2880 nohome = b'--home=""'
2876 if os.name == 'nt':
2881 if os.name == 'nt':
2877 # The --home="" trick works only on OS where os.sep == '/'
2882 # The --home="" trick works only on OS where os.sep == '/'
2878 # because of a distutils convert_path() fast-path. Avoid it at
2883 # because of a distutils convert_path() fast-path. Avoid it at
2879 # least on Windows for now, deal with .pydistutils.cfg bugs
2884 # least on Windows for now, deal with .pydistutils.cfg bugs
2880 # when they happen.
2885 # when they happen.
2881 nohome = b''
2886 nohome = b''
2882 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2887 cmd = (b'%(exe)s setup.py %(pure)s clean --all'
2883 b' build %(compiler)s --build-base="%(base)s"'
2888 b' build %(compiler)s --build-base="%(base)s"'
2884 b' install --force --prefix="%(prefix)s"'
2889 b' install --force --prefix="%(prefix)s"'
2885 b' --install-lib="%(libdir)s"'
2890 b' --install-lib="%(libdir)s"'
2886 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2891 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2887 % {b'exe': exe, b'pure': pure,
2892 % {b'exe': exe, b'pure': pure,
2888 b'compiler': compiler,
2893 b'compiler': compiler,
2889 b'base': os.path.join(self._hgtmp, b"build"),
2894 b'base': os.path.join(self._hgtmp, b"build"),
2890 b'prefix': self._installdir, b'libdir': self._pythondir,
2895 b'prefix': self._installdir, b'libdir': self._pythondir,
2891 b'bindir': self._bindir,
2896 b'bindir': self._bindir,
2892 b'nohome': nohome, b'logfile': installerrs})
2897 b'nohome': nohome, b'logfile': installerrs})
2893
2898
2894 # setuptools requires install directories to exist.
2899 # setuptools requires install directories to exist.
2895 def makedirs(p):
2900 def makedirs(p):
2896 try:
2901 try:
2897 os.makedirs(p)
2902 os.makedirs(p)
2898 except OSError as e:
2903 except OSError as e:
2899 if e.errno != errno.EEXIST:
2904 if e.errno != errno.EEXIST:
2900 raise
2905 raise
2901 makedirs(self._pythondir)
2906 makedirs(self._pythondir)
2902 makedirs(self._bindir)
2907 makedirs(self._bindir)
2903
2908
2904 vlog("# Running", cmd)
2909 vlog("# Running", cmd)
2905 if os.system(cmd) == 0:
2910 if os.system(cmd) == 0:
2906 if not self.options.verbose:
2911 if not self.options.verbose:
2907 try:
2912 try:
2908 os.remove(installerrs)
2913 os.remove(installerrs)
2909 except OSError as e:
2914 except OSError as e:
2910 if e.errno != errno.ENOENT:
2915 if e.errno != errno.ENOENT:
2911 raise
2916 raise
2912 else:
2917 else:
2913 with open(installerrs, 'rb') as f:
2918 with open(installerrs, 'rb') as f:
2914 for line in f:
2919 for line in f:
2915 if PYTHON3:
2920 if PYTHON3:
2916 sys.stdout.buffer.write(line)
2921 sys.stdout.buffer.write(line)
2917 else:
2922 else:
2918 sys.stdout.write(line)
2923 sys.stdout.write(line)
2919 sys.exit(1)
2924 sys.exit(1)
2920 os.chdir(self._testdir)
2925 os.chdir(self._testdir)
2921
2926
2922 self._usecorrectpython()
2927 self._usecorrectpython()
2923
2928
2924 if self.options.py3k_warnings and not self.options.anycoverage:
2929 if self.options.py3k_warnings and not self.options.anycoverage:
2925 vlog("# Updating hg command to enable Py3k Warnings switch")
2930 vlog("# Updating hg command to enable Py3k Warnings switch")
2926 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
2931 with open(os.path.join(self._bindir, 'hg'), 'rb') as f:
2927 lines = [line.rstrip() for line in f]
2932 lines = [line.rstrip() for line in f]
2928 lines[0] += ' -3'
2933 lines[0] += ' -3'
2929 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
2934 with open(os.path.join(self._bindir, 'hg'), 'wb') as f:
2930 for line in lines:
2935 for line in lines:
2931 f.write(line + '\n')
2936 f.write(line + '\n')
2932
2937
2933 hgbat = os.path.join(self._bindir, b'hg.bat')
2938 hgbat = os.path.join(self._bindir, b'hg.bat')
2934 if os.path.isfile(hgbat):
2939 if os.path.isfile(hgbat):
2935 # hg.bat expects to be put in bin/scripts while run-tests.py
2940 # hg.bat expects to be put in bin/scripts while run-tests.py
2936 # installation layout put it in bin/ directly. Fix it
2941 # installation layout put it in bin/ directly. Fix it
2937 with open(hgbat, 'rb') as f:
2942 with open(hgbat, 'rb') as f:
2938 data = f.read()
2943 data = f.read()
2939 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2944 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2940 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2945 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2941 b'"%~dp0python" "%~dp0hg" %*')
2946 b'"%~dp0python" "%~dp0hg" %*')
2942 with open(hgbat, 'wb') as f:
2947 with open(hgbat, 'wb') as f:
2943 f.write(data)
2948 f.write(data)
2944 else:
2949 else:
2945 print('WARNING: cannot fix hg.bat reference to python.exe')
2950 print('WARNING: cannot fix hg.bat reference to python.exe')
2946
2951
2947 if self.options.anycoverage:
2952 if self.options.anycoverage:
2948 custom = os.path.join(self._testdir, 'sitecustomize.py')
2953 custom = os.path.join(self._testdir, 'sitecustomize.py')
2949 target = os.path.join(self._pythondir, 'sitecustomize.py')
2954 target = os.path.join(self._pythondir, 'sitecustomize.py')
2950 vlog('# Installing coverage trigger to %s' % target)
2955 vlog('# Installing coverage trigger to %s' % target)
2951 shutil.copyfile(custom, target)
2956 shutil.copyfile(custom, target)
2952 rc = os.path.join(self._testdir, '.coveragerc')
2957 rc = os.path.join(self._testdir, '.coveragerc')
2953 vlog('# Installing coverage rc to %s' % rc)
2958 vlog('# Installing coverage rc to %s' % rc)
2954 os.environ['COVERAGE_PROCESS_START'] = rc
2959 os.environ['COVERAGE_PROCESS_START'] = rc
2955 covdir = os.path.join(self._installdir, '..', 'coverage')
2960 covdir = os.path.join(self._installdir, '..', 'coverage')
2956 try:
2961 try:
2957 os.mkdir(covdir)
2962 os.mkdir(covdir)
2958 except OSError as e:
2963 except OSError as e:
2959 if e.errno != errno.EEXIST:
2964 if e.errno != errno.EEXIST:
2960 raise
2965 raise
2961
2966
2962 os.environ['COVERAGE_DIR'] = covdir
2967 os.environ['COVERAGE_DIR'] = covdir
2963
2968
2964 def _checkhglib(self, verb):
2969 def _checkhglib(self, verb):
2965 """Ensure that the 'mercurial' package imported by python is
2970 """Ensure that the 'mercurial' package imported by python is
2966 the one we expect it to be. If not, print a warning to stderr."""
2971 the one we expect it to be. If not, print a warning to stderr."""
2967 if ((self._bindir == self._pythondir) and
2972 if ((self._bindir == self._pythondir) and
2968 (self._bindir != self._tmpbindir)):
2973 (self._bindir != self._tmpbindir)):
2969 # The pythondir has been inferred from --with-hg flag.
2974 # The pythondir has been inferred from --with-hg flag.
2970 # We cannot expect anything sensible here.
2975 # We cannot expect anything sensible here.
2971 return
2976 return
2972 expecthg = os.path.join(self._pythondir, b'mercurial')
2977 expecthg = os.path.join(self._pythondir, b'mercurial')
2973 actualhg = self._gethgpath()
2978 actualhg = self._gethgpath()
2974 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2979 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2975 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2980 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2976 ' (expected %s)\n'
2981 ' (expected %s)\n'
2977 % (verb, actualhg, expecthg))
2982 % (verb, actualhg, expecthg))
2978 def _gethgpath(self):
2983 def _gethgpath(self):
2979 """Return the path to the mercurial package that is actually found by
2984 """Return the path to the mercurial package that is actually found by
2980 the current Python interpreter."""
2985 the current Python interpreter."""
2981 if self._hgpath is not None:
2986 if self._hgpath is not None:
2982 return self._hgpath
2987 return self._hgpath
2983
2988
2984 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2989 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2985 cmd = cmd % PYTHON
2990 cmd = cmd % PYTHON
2986 if PYTHON3:
2991 if PYTHON3:
2987 cmd = _strpath(cmd)
2992 cmd = _strpath(cmd)
2988 pipe = os.popen(cmd)
2993 pipe = os.popen(cmd)
2989 try:
2994 try:
2990 self._hgpath = _bytespath(pipe.read().strip())
2995 self._hgpath = _bytespath(pipe.read().strip())
2991 finally:
2996 finally:
2992 pipe.close()
2997 pipe.close()
2993
2998
2994 return self._hgpath
2999 return self._hgpath
2995
3000
2996 def _installchg(self):
3001 def _installchg(self):
2997 """Install chg into the test environment"""
3002 """Install chg into the test environment"""
2998 vlog('# Performing temporary installation of CHG')
3003 vlog('# Performing temporary installation of CHG')
2999 assert os.path.dirname(self._bindir) == self._installdir
3004 assert os.path.dirname(self._bindir) == self._installdir
3000 assert self._hgroot, 'must be called after _installhg()'
3005 assert self._hgroot, 'must be called after _installhg()'
3001 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3006 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
3002 % {b'make': 'make', # TODO: switch by option or environment?
3007 % {b'make': 'make', # TODO: switch by option or environment?
3003 b'prefix': self._installdir})
3008 b'prefix': self._installdir})
3004 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3009 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3005 vlog("# Running", cmd)
3010 vlog("# Running", cmd)
3006 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3011 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
3007 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3012 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
3008 stderr=subprocess.STDOUT)
3013 stderr=subprocess.STDOUT)
3009 out, _err = proc.communicate()
3014 out, _err = proc.communicate()
3010 if proc.returncode != 0:
3015 if proc.returncode != 0:
3011 if PYTHON3:
3016 if PYTHON3:
3012 sys.stdout.buffer.write(out)
3017 sys.stdout.buffer.write(out)
3013 else:
3018 else:
3014 sys.stdout.write(out)
3019 sys.stdout.write(out)
3015 sys.exit(1)
3020 sys.exit(1)
3016
3021
3017 def _outputcoverage(self):
3022 def _outputcoverage(self):
3018 """Produce code coverage output."""
3023 """Produce code coverage output."""
3019 import coverage
3024 import coverage
3020 coverage = coverage.coverage
3025 coverage = coverage.coverage
3021
3026
3022 vlog('# Producing coverage report')
3027 vlog('# Producing coverage report')
3023 # chdir is the easiest way to get short, relative paths in the
3028 # chdir is the easiest way to get short, relative paths in the
3024 # output.
3029 # output.
3025 os.chdir(self._hgroot)
3030 os.chdir(self._hgroot)
3026 covdir = os.path.join(self._installdir, '..', 'coverage')
3031 covdir = os.path.join(self._installdir, '..', 'coverage')
3027 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3032 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3028
3033
3029 # Map install directory paths back to source directory.
3034 # Map install directory paths back to source directory.
3030 cov.config.paths['srcdir'] = ['.', self._pythondir]
3035 cov.config.paths['srcdir'] = ['.', self._pythondir]
3031
3036
3032 cov.combine()
3037 cov.combine()
3033
3038
3034 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3039 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
3035 cov.report(ignore_errors=True, omit=omit)
3040 cov.report(ignore_errors=True, omit=omit)
3036
3041
3037 if self.options.htmlcov:
3042 if self.options.htmlcov:
3038 htmldir = os.path.join(self._outputdir, 'htmlcov')
3043 htmldir = os.path.join(self._outputdir, 'htmlcov')
3039 cov.html_report(directory=htmldir, omit=omit)
3044 cov.html_report(directory=htmldir, omit=omit)
3040 if self.options.annotate:
3045 if self.options.annotate:
3041 adir = os.path.join(self._outputdir, 'annotated')
3046 adir = os.path.join(self._outputdir, 'annotated')
3042 if not os.path.isdir(adir):
3047 if not os.path.isdir(adir):
3043 os.mkdir(adir)
3048 os.mkdir(adir)
3044 cov.annotate(directory=adir, omit=omit)
3049 cov.annotate(directory=adir, omit=omit)
3045
3050
3046 def _findprogram(self, program):
3051 def _findprogram(self, program):
3047 """Search PATH for a executable program"""
3052 """Search PATH for a executable program"""
3048 dpb = _bytespath(os.defpath)
3053 dpb = _bytespath(os.defpath)
3049 sepb = _bytespath(os.pathsep)
3054 sepb = _bytespath(os.pathsep)
3050 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3055 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3051 name = os.path.join(p, program)
3056 name = os.path.join(p, program)
3052 if os.name == 'nt' or os.access(name, os.X_OK):
3057 if os.name == 'nt' or os.access(name, os.X_OK):
3053 return name
3058 return name
3054 return None
3059 return None
3055
3060
3056 def _checktools(self):
3061 def _checktools(self):
3057 """Ensure tools required to run tests are present."""
3062 """Ensure tools required to run tests are present."""
3058 for p in self.REQUIREDTOOLS:
3063 for p in self.REQUIREDTOOLS:
3059 if os.name == 'nt' and not p.endswith('.exe'):
3064 if os.name == 'nt' and not p.endswith('.exe'):
3060 p += '.exe'
3065 p += '.exe'
3061 found = self._findprogram(p)
3066 found = self._findprogram(p)
3062 if found:
3067 if found:
3063 vlog("# Found prerequisite", p, "at", found)
3068 vlog("# Found prerequisite", p, "at", found)
3064 else:
3069 else:
3065 print("WARNING: Did not find prerequisite tool: %s " %
3070 print("WARNING: Did not find prerequisite tool: %s " %
3066 p.decode("utf-8"))
3071 p.decode("utf-8"))
3067
3072
3068 def aggregateexceptions(path):
3073 def aggregateexceptions(path):
3069 exceptioncounts = collections.Counter()
3074 exceptioncounts = collections.Counter()
3070 testsbyfailure = collections.defaultdict(set)
3075 testsbyfailure = collections.defaultdict(set)
3071 failuresbytest = collections.defaultdict(set)
3076 failuresbytest = collections.defaultdict(set)
3072
3077
3073 for f in os.listdir(path):
3078 for f in os.listdir(path):
3074 with open(os.path.join(path, f), 'rb') as fh:
3079 with open(os.path.join(path, f), 'rb') as fh:
3075 data = fh.read().split(b'\0')
3080 data = fh.read().split(b'\0')
3076 if len(data) != 5:
3081 if len(data) != 5:
3077 continue
3082 continue
3078
3083
3079 exc, mainframe, hgframe, hgline, testname = data
3084 exc, mainframe, hgframe, hgline, testname = data
3080 exc = exc.decode('utf-8')
3085 exc = exc.decode('utf-8')
3081 mainframe = mainframe.decode('utf-8')
3086 mainframe = mainframe.decode('utf-8')
3082 hgframe = hgframe.decode('utf-8')
3087 hgframe = hgframe.decode('utf-8')
3083 hgline = hgline.decode('utf-8')
3088 hgline = hgline.decode('utf-8')
3084 testname = testname.decode('utf-8')
3089 testname = testname.decode('utf-8')
3085
3090
3086 key = (hgframe, hgline, exc)
3091 key = (hgframe, hgline, exc)
3087 exceptioncounts[key] += 1
3092 exceptioncounts[key] += 1
3088 testsbyfailure[key].add(testname)
3093 testsbyfailure[key].add(testname)
3089 failuresbytest[testname].add(key)
3094 failuresbytest[testname].add(key)
3090
3095
3091 # Find test having fewest failures for each failure.
3096 # Find test having fewest failures for each failure.
3092 leastfailing = {}
3097 leastfailing = {}
3093 for key, tests in testsbyfailure.items():
3098 for key, tests in testsbyfailure.items():
3094 fewesttest = None
3099 fewesttest = None
3095 fewestcount = 99999999
3100 fewestcount = 99999999
3096 for test in sorted(tests):
3101 for test in sorted(tests):
3097 if len(failuresbytest[test]) < fewestcount:
3102 if len(failuresbytest[test]) < fewestcount:
3098 fewesttest = test
3103 fewesttest = test
3099 fewestcount = len(failuresbytest[test])
3104 fewestcount = len(failuresbytest[test])
3100
3105
3101 leastfailing[key] = (fewestcount, fewesttest)
3106 leastfailing[key] = (fewestcount, fewesttest)
3102
3107
3103 # Create a combined counter so we can sort by total occurrences and
3108 # Create a combined counter so we can sort by total occurrences and
3104 # impacted tests.
3109 # impacted tests.
3105 combined = {}
3110 combined = {}
3106 for key in exceptioncounts:
3111 for key in exceptioncounts:
3107 combined[key] = (exceptioncounts[key],
3112 combined[key] = (exceptioncounts[key],
3108 len(testsbyfailure[key]),
3113 len(testsbyfailure[key]),
3109 leastfailing[key][0],
3114 leastfailing[key][0],
3110 leastfailing[key][1])
3115 leastfailing[key][1])
3111
3116
3112 return {
3117 return {
3113 'exceptioncounts': exceptioncounts,
3118 'exceptioncounts': exceptioncounts,
3114 'total': sum(exceptioncounts.values()),
3119 'total': sum(exceptioncounts.values()),
3115 'combined': combined,
3120 'combined': combined,
3116 'leastfailing': leastfailing,
3121 'leastfailing': leastfailing,
3117 'byfailure': testsbyfailure,
3122 'byfailure': testsbyfailure,
3118 'bytest': failuresbytest,
3123 'bytest': failuresbytest,
3119 }
3124 }
3120
3125
3121 if __name__ == '__main__':
3126 if __name__ == '__main__':
3122 runner = TestRunner()
3127 runner = TestRunner()
3123
3128
3124 try:
3129 try:
3125 import msvcrt
3130 import msvcrt
3126 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3131 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3127 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3132 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3128 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3133 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3129 except ImportError:
3134 except ImportError:
3130 pass
3135 pass
3131
3136
3132 sys.exit(runner.run(sys.argv[1:]))
3137 sys.exit(runner.run(sys.argv[1:]))
@@ -1,108 +1,108 b''
1 """test line matching with some failing examples and some which warn
1 """test line matching with some failing examples and some which warn
2
2
3 run-test.t only checks positive matches and can not see warnings
3 run-test.t only checks positive matches and can not see warnings
4 (both by design)
4 (both by design)
5 """
5 """
6 from __future__ import absolute_import, print_function
6 from __future__ import absolute_import, print_function
7
7
8 import doctest
8 import doctest
9 import os
9 import os
10 import re
10 import re
11 # this is hack to make sure no escape characters are inserted into the output
11 # this is hack to make sure no escape characters are inserted into the output
12 if 'TERM' in os.environ:
12 if 'TERM' in os.environ:
13 del os.environ['TERM']
13 del os.environ['TERM']
14 run_tests = __import__('run-tests')
14 run_tests = __import__('run-tests')
15
15
16 def prn(ex):
16 def prn(ex):
17 m = ex.args[0]
17 m = ex.args[0]
18 if isinstance(m, str):
18 if isinstance(m, str):
19 print(m)
19 print(m)
20 else:
20 else:
21 print(m.decode('utf-8'))
21 print(m.decode('utf-8'))
22
22
23 def lm(expected, output):
23 def lm(expected, output):
24 r"""check if output matches expected
24 r"""check if output matches expected
25
25
26 does it generally work?
26 does it generally work?
27 >>> lm(b'H*e (glob)\n', b'Here\n')
27 >>> lm(b'H*e (glob)\n', b'Here\n')
28 True
28 True
29
29
30 fail on bad test data
30 fail on bad test data
31 >>> try: lm(b'a\n',b'a')
31 >>> try: lm(b'a\n',b'a')
32 ... except AssertionError as ex: print(ex)
32 ... except AssertionError as ex: print(ex)
33 missing newline
33 missing newline
34 >>> try: lm(b'single backslash\n', b'single \backslash\n')
34 >>> try: lm(b'single backslash\n', b'single \backslash\n')
35 ... except AssertionError as ex: prn(ex)
35 ... except AssertionError as ex: prn(ex)
36 single backslash or unknown char
36 single backslash or unknown char
37 """
37 """
38 assert (expected.endswith(b'\n')
38 assert (expected.endswith(b'\n')
39 and output.endswith(b'\n')), 'missing newline'
39 and output.endswith(b'\n')), 'missing newline'
40 assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
40 assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
41 b'single backslash or unknown char'
41 b'single backslash or unknown char'
42 test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
42 test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
43 match = test.linematch(expected, output)
43 match, exact = test.linematch(expected, output)
44 if isinstance(match, str):
44 if isinstance(match, str):
45 return 'special: ' + match
45 return 'special: ' + match
46 elif isinstance(match, bytes):
46 elif isinstance(match, bytes):
47 return 'special: ' + match.decode('utf-8')
47 return 'special: ' + match.decode('utf-8')
48 else:
48 else:
49 return bool(match) # do not return match object
49 return bool(match) # do not return match object
50
50
51 def wintests():
51 def wintests():
52 r"""test matching like running on windows
52 r"""test matching like running on windows
53
53
54 enable windows matching on any os
54 enable windows matching on any os
55 >>> _osaltsep = os.altsep
55 >>> _osaltsep = os.altsep
56 >>> os.altsep = True
56 >>> os.altsep = True
57 >>> _osname = os.name
57 >>> _osname = os.name
58 >>> os.name = 'nt'
58 >>> os.name = 'nt'
59
59
60 valid match on windows
60 valid match on windows
61 >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n')
61 >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n')
62 True
62 True
63
63
64 direct matching, glob unnecessary
64 direct matching, glob unnecessary
65 >>> lm(b'g/b (glob)\n', b'g/b\n')
65 >>> lm(b'g/b (glob)\n', b'g/b\n')
66 'special: -glob'
66 'special: -glob'
67
67
68 missing glob
68 missing glob
69 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n')
69 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n')
70 True
70 True
71 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d\\fg\r\n')
71 >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d\\fg\r\n')
72 True
72 True
73
73
74 restore os.altsep
74 restore os.altsep
75 >>> os.altsep = _osaltsep
75 >>> os.altsep = _osaltsep
76 >>> os.name = _osname
76 >>> os.name = _osname
77 """
77 """
78 pass
78 pass
79
79
80 def otherostests():
80 def otherostests():
81 r"""test matching like running on non-windows os
81 r"""test matching like running on non-windows os
82
82
83 disable windows matching on any os
83 disable windows matching on any os
84 >>> _osaltsep = os.altsep
84 >>> _osaltsep = os.altsep
85 >>> os.altsep = False
85 >>> os.altsep = False
86 >>> _osname = os.name
86 >>> _osname = os.name
87 >>> os.name = 'nt'
87 >>> os.name = 'nt'
88
88
89 backslash does not match slash
89 backslash does not match slash
90 >>> lm(b'h/a* (glob)\n', b'h\\ab\n')
90 >>> lm(b'h/a* (glob)\n', b'h\\ab\n')
91 False
91 False
92
92
93 direct matching glob can not be recognized
93 direct matching glob can not be recognized
94 >>> lm(b'h/b (glob)\n', b'h/b\n')
94 >>> lm(b'h/b (glob)\n', b'h/b\n')
95 True
95 True
96
96
97 missing glob can not not be recognized
97 missing glob can not not be recognized
98 >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n')
98 >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n')
99 False
99 False
100
100
101 restore os.altsep
101 restore os.altsep
102 >>> os.altsep = _osaltsep
102 >>> os.altsep = _osaltsep
103 >>> os.name = _osname
103 >>> os.name = _osname
104 """
104 """
105 pass
105 pass
106
106
107 if __name__ == '__main__':
107 if __name__ == '__main__':
108 doctest.testmod()
108 doctest.testmod()
@@ -1,1815 +1,1812 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
10
11 # Ran 0 tests, 0 skipped, 0 failed.
11 # Ran 0 tests, 0 skipped, 0 failed.
12
12
13 Define a helper to avoid the install step
13 Define a helper to avoid the install step
14 =============
14 =============
15 $ rt()
15 $ rt()
16 > {
16 > {
17 > $PYTHON $TESTDIR/run-tests.py --with-hg=`which hg` "$@"
17 > $PYTHON $TESTDIR/run-tests.py --with-hg=`which hg` "$@"
18 > }
18 > }
19
19
20 error paths
20 error paths
21
21
22 #if symlink
22 #if symlink
23 $ ln -s `which true` hg
23 $ ln -s `which true` hg
24 $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
24 $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
25 warning: --with-hg should specify an hg script
25 warning: --with-hg should specify an hg script
26
26
27 # Ran 0 tests, 0 skipped, 0 failed.
27 # Ran 0 tests, 0 skipped, 0 failed.
28 $ rm hg
28 $ rm hg
29 #endif
29 #endif
30
30
31 #if execbit
31 #if execbit
32 $ touch hg
32 $ touch hg
33 $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
33 $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
34 usage: run-tests.py [options] [tests]
34 usage: run-tests.py [options] [tests]
35 run-tests.py: error: --with-hg must specify an executable hg script
35 run-tests.py: error: --with-hg must specify an executable hg script
36 [2]
36 [2]
37 $ rm hg
37 $ rm hg
38 #endif
38 #endif
39
39
40 Features for testing optional lines
40 Features for testing optional lines
41 ===================================
41 ===================================
42
42
43 $ cat > hghaveaddon.py <<EOF
43 $ cat > hghaveaddon.py <<EOF
44 > import hghave
44 > import hghave
45 > @hghave.check("custom", "custom hghave feature")
45 > @hghave.check("custom", "custom hghave feature")
46 > def has_custom():
46 > def has_custom():
47 > return True
47 > return True
48 > @hghave.check("missing", "missing hghave feature")
48 > @hghave.check("missing", "missing hghave feature")
49 > def has_missing():
49 > def has_missing():
50 > return False
50 > return False
51 > EOF
51 > EOF
52
52
53 an empty test
53 an empty test
54 =======================
54 =======================
55
55
56 $ touch test-empty.t
56 $ touch test-empty.t
57 $ rt
57 $ rt
58 .
58 .
59 # Ran 1 tests, 0 skipped, 0 failed.
59 # Ran 1 tests, 0 skipped, 0 failed.
60 $ rm test-empty.t
60 $ rm test-empty.t
61
61
62 a succesful test
62 a succesful test
63 =======================
63 =======================
64
64
65 $ cat > test-success.t << EOF
65 $ cat > test-success.t << EOF
66 > $ echo babar
66 > $ echo babar
67 > babar
67 > babar
68 > $ echo xyzzy
68 > $ echo xyzzy
69 > dont_print (?)
69 > dont_print (?)
70 > nothing[42]line (re) (?)
70 > nothing[42]line (re) (?)
71 > never*happens (glob) (?)
71 > never*happens (glob) (?)
72 > more_nothing (?)
72 > more_nothing (?)
73 > xyzzy
73 > xyzzy
74 > nor this (?)
74 > nor this (?)
75 > $ printf 'abc\ndef\nxyz\n'
75 > $ printf 'abc\ndef\nxyz\n'
76 > 123 (?)
76 > 123 (?)
77 > abc
77 > abc
78 > def (?)
78 > def (?)
79 > 456 (?)
79 > 456 (?)
80 > xyz
80 > xyz
81 > $ printf 'zyx\nwvu\ntsr\n'
81 > $ printf 'zyx\nwvu\ntsr\n'
82 > abc (?)
82 > abc (?)
83 > zyx (custom !)
83 > zyx (custom !)
84 > wvu
84 > wvu
85 > no_print (no-custom !)
85 > no_print (no-custom !)
86 > tsr (no-missing !)
86 > tsr (no-missing !)
87 > missing (missing !)
87 > missing (missing !)
88 > EOF
88 > EOF
89
89
90 $ rt
90 $ rt
91 .
91 .
92 # Ran 1 tests, 0 skipped, 0 failed.
92 # Ran 1 tests, 0 skipped, 0 failed.
93
93
94 failing test
94 failing test
95 ==================
95 ==================
96
96
97 test churn with globs
97 test churn with globs
98 $ cat > test-failure.t <<EOF
98 $ cat > test-failure.t <<EOF
99 > $ echo "bar-baz"; echo "bar-bad"; echo foo
99 > $ echo "bar-baz"; echo "bar-bad"; echo foo
100 > bar*bad (glob)
100 > bar*bad (glob)
101 > bar*baz (glob)
101 > bar*baz (glob)
102 > | fo (re)
102 > | fo (re)
103 > EOF
103 > EOF
104 $ rt test-failure.t
104 $ rt test-failure.t
105
105
106 --- $TESTTMP/test-failure.t
106 --- $TESTTMP/test-failure.t
107 +++ $TESTTMP/test-failure.t.err
107 +++ $TESTTMP/test-failure.t.err
108 @@ -1,4 +1,4 @@
108 @@ -1,4 +1,4 @@
109 $ echo "bar-baz"; echo "bar-bad"; echo foo
109 $ echo "bar-baz"; echo "bar-bad"; echo foo
110 + bar*baz (glob)
110 + bar*baz (glob)
111 bar*bad (glob)
111 bar*bad (glob)
112 - bar*baz (glob)
112 - bar*baz (glob)
113 - | fo (re)
113 - | fo (re)
114 + foo
114 + foo
115
115
116 ERROR: test-failure.t output changed
116 ERROR: test-failure.t output changed
117 !
117 !
118 Failed test-failure.t: output changed
118 Failed test-failure.t: output changed
119 # Ran 1 tests, 0 skipped, 1 failed.
119 # Ran 1 tests, 0 skipped, 1 failed.
120 python hash seed: * (glob)
120 python hash seed: * (glob)
121 [1]
121 [1]
122
122
123 test how multiple globs gets matched with lines in output
123 test how multiple globs gets matched with lines in output
124 $ cat > test-failure-globs.t <<EOF
124 $ cat > test-failure-globs.t <<EOF
125 > $ echo "context"; echo "context"; \
125 > $ echo "context"; echo "context"; \
126 > echo "key: 1"; echo "value: not a"; \
126 > echo "key: 1"; echo "value: not a"; \
127 > echo "key: 2"; echo "value: not b"; \
127 > echo "key: 2"; echo "value: not b"; \
128 > echo "key: 3"; echo "value: c"; \
128 > echo "key: 3"; echo "value: c"; \
129 > echo "key: 4"; echo "value: d"
129 > echo "key: 4"; echo "value: d"
130 > context
130 > context
131 > context
131 > context
132 > key: 1
132 > key: 1
133 > value: a
133 > value: a
134 > key: 2
134 > key: 2
135 > value: b
135 > value: b
136 > key: 3
136 > key: 3
137 > value: * (glob)
137 > value: * (glob)
138 > key: 4
138 > key: 4
139 > value: * (glob)
139 > value: * (glob)
140 > EOF
140 > EOF
141 $ rt test-failure-globs.t
141 $ rt test-failure-globs.t
142
142
143 --- $TESTTMP/test-failure-globs.t
143 --- $TESTTMP/test-failure-globs.t
144 +++ $TESTTMP/test-failure-globs.t.err
144 +++ $TESTTMP/test-failure-globs.t.err
145 @@ -2,10 +2,10 @@
145 @@ -2,9 +2,9 @@
146 context
146 context
147 context
147 context
148 key: 1
148 key: 1
149 - value: a
149 - value: a
150 + value: * (glob)
150 + value: not a
151 key: 2
151 key: 2
152 - value: b
152 - value: b
153 + value: * (glob)
153 + value: not b
154 key: 3
154 key: 3
155 - value: * (glob)
155 value: * (glob)
156 + value: c
157 key: 4
156 key: 4
158 - value: * (glob)
159 + value: d
160
157
161 ERROR: test-failure-globs.t output changed
158 ERROR: test-failure-globs.t output changed
162 !
159 !
163 Failed test-failure-globs.t: output changed
160 Failed test-failure-globs.t: output changed
164 # Ran 1 tests, 0 skipped, 1 failed.
161 # Ran 1 tests, 0 skipped, 1 failed.
165 python hash seed: * (glob)
162 python hash seed: * (glob)
166 [1]
163 [1]
167 $ rm test-failure-globs.t
164 $ rm test-failure-globs.t
168
165
169 test diff colorisation
166 test diff colorisation
170
167
171 #if no-windows pygments
168 #if no-windows pygments
172 $ rt test-failure.t --color always
169 $ rt test-failure.t --color always
173
170
174 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
171 \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
175 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
172 \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
176 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
173 \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
177 $ echo "bar-baz"; echo "bar-bad"; echo foo
174 $ echo "bar-baz"; echo "bar-bad"; echo foo
178 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
175 \x1b[38;5;34m+ bar*baz (glob)\x1b[39m (esc)
179 bar*bad (glob)
176 bar*bad (glob)
180 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
177 \x1b[38;5;124m- bar*baz (glob)\x1b[39m (esc)
181 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
178 \x1b[38;5;124m- | fo (re)\x1b[39m (esc)
182 \x1b[38;5;34m+ foo\x1b[39m (esc)
179 \x1b[38;5;34m+ foo\x1b[39m (esc)
183
180
184 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
181 \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
185 !
182 !
186 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
183 \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
187 # Ran 1 tests, 0 skipped, 1 failed.
184 # Ran 1 tests, 0 skipped, 1 failed.
188 python hash seed: * (glob)
185 python hash seed: * (glob)
189 [1]
186 [1]
190
187
191 $ rt test-failure.t 2> tmp.log
188 $ rt test-failure.t 2> tmp.log
192 [1]
189 [1]
193 $ cat tmp.log
190 $ cat tmp.log
194
191
195 --- $TESTTMP/test-failure.t
192 --- $TESTTMP/test-failure.t
196 +++ $TESTTMP/test-failure.t.err
193 +++ $TESTTMP/test-failure.t.err
197 @@ -1,4 +1,4 @@
194 @@ -1,4 +1,4 @@
198 $ echo "bar-baz"; echo "bar-bad"; echo foo
195 $ echo "bar-baz"; echo "bar-bad"; echo foo
199 + bar*baz (glob)
196 + bar*baz (glob)
200 bar*bad (glob)
197 bar*bad (glob)
201 - bar*baz (glob)
198 - bar*baz (glob)
202 - | fo (re)
199 - | fo (re)
203 + foo
200 + foo
204
201
205 ERROR: test-failure.t output changed
202 ERROR: test-failure.t output changed
206 !
203 !
207 Failed test-failure.t: output changed
204 Failed test-failure.t: output changed
208 # Ran 1 tests, 0 skipped, 1 failed.
205 # Ran 1 tests, 0 skipped, 1 failed.
209 python hash seed: * (glob)
206 python hash seed: * (glob)
210 #endif
207 #endif
211
208
212 $ cat > test-failure.t << EOF
209 $ cat > test-failure.t << EOF
213 > $ true
210 > $ true
214 > should go away (true !)
211 > should go away (true !)
215 > $ true
212 > $ true
216 > should stay (false !)
213 > should stay (false !)
217 >
214 >
218 > Should remove first line, not second or third
215 > Should remove first line, not second or third
219 > $ echo 'testing'
216 > $ echo 'testing'
220 > baz*foo (glob) (true !)
217 > baz*foo (glob) (true !)
221 > foobar*foo (glob) (false !)
218 > foobar*foo (glob) (false !)
222 > te*ting (glob) (true !)
219 > te*ting (glob) (true !)
223 >
220 >
224 > Should keep first two lines, remove third and last
221 > Should keep first two lines, remove third and last
225 > $ echo 'testing'
222 > $ echo 'testing'
226 > test.ng (re) (true !)
223 > test.ng (re) (true !)
227 > foo.ar (re) (false !)
224 > foo.ar (re) (false !)
228 > b.r (re) (true !)
225 > b.r (re) (true !)
229 > missing (?)
226 > missing (?)
230 > awol (true !)
227 > awol (true !)
231 >
228 >
232 > The "missing" line should stay, even though awol is dropped
229 > The "missing" line should stay, even though awol is dropped
233 > $ echo 'testing'
230 > $ echo 'testing'
234 > test.ng (re) (true !)
231 > test.ng (re) (true !)
235 > foo.ar (?)
232 > foo.ar (?)
236 > awol
233 > awol
237 > missing (?)
234 > missing (?)
238 > EOF
235 > EOF
239 $ rt test-failure.t
236 $ rt test-failure.t
240
237
241 --- $TESTTMP/test-failure.t
238 --- $TESTTMP/test-failure.t
242 +++ $TESTTMP/test-failure.t.err
239 +++ $TESTTMP/test-failure.t.err
243 @@ -1,11 +1,9 @@
240 @@ -1,11 +1,9 @@
244 $ true
241 $ true
245 - should go away (true !)
242 - should go away (true !)
246 $ true
243 $ true
247 should stay (false !)
244 should stay (false !)
248
245
249 Should remove first line, not second or third
246 Should remove first line, not second or third
250 $ echo 'testing'
247 $ echo 'testing'
251 - baz*foo (glob) (true !)
248 - baz*foo (glob) (true !)
252 foobar*foo (glob) (false !)
249 foobar*foo (glob) (false !)
253 te*ting (glob) (true !)
250 te*ting (glob) (true !)
254
251
255 foo.ar (re) (false !)
252 foo.ar (re) (false !)
256 missing (?)
253 missing (?)
257 @@ -13,13 +11,10 @@
254 @@ -13,13 +11,10 @@
258 $ echo 'testing'
255 $ echo 'testing'
259 test.ng (re) (true !)
256 test.ng (re) (true !)
260 foo.ar (re) (false !)
257 foo.ar (re) (false !)
261 - b.r (re) (true !)
258 - b.r (re) (true !)
262 missing (?)
259 missing (?)
263 - awol (true !)
260 - awol (true !)
264
261
265 The "missing" line should stay, even though awol is dropped
262 The "missing" line should stay, even though awol is dropped
266 $ echo 'testing'
263 $ echo 'testing'
267 test.ng (re) (true !)
264 test.ng (re) (true !)
268 foo.ar (?)
265 foo.ar (?)
269 - awol
266 - awol
270 missing (?)
267 missing (?)
271
268
272 ERROR: test-failure.t output changed
269 ERROR: test-failure.t output changed
273 !
270 !
274 Failed test-failure.t: output changed
271 Failed test-failure.t: output changed
275 # Ran 1 tests, 0 skipped, 1 failed.
272 # Ran 1 tests, 0 skipped, 1 failed.
276 python hash seed: * (glob)
273 python hash seed: * (glob)
277 [1]
274 [1]
278
275
279 basic failing test
276 basic failing test
280 $ cat > test-failure.t << EOF
277 $ cat > test-failure.t << EOF
281 > $ echo babar
278 > $ echo babar
282 > rataxes
279 > rataxes
283 > This is a noop statement so that
280 > This is a noop statement so that
284 > this test is still more bytes than success.
281 > this test is still more bytes than success.
285 > pad pad pad pad............................................................
282 > pad pad pad pad............................................................
286 > pad pad pad pad............................................................
283 > pad pad pad pad............................................................
287 > pad pad pad pad............................................................
284 > pad pad pad pad............................................................
288 > pad pad pad pad............................................................
285 > pad pad pad pad............................................................
289 > pad pad pad pad............................................................
286 > pad pad pad pad............................................................
290 > pad pad pad pad............................................................
287 > pad pad pad pad............................................................
291 > EOF
288 > EOF
292
289
293 >>> fh = open('test-failure-unicode.t', 'wb')
290 >>> fh = open('test-failure-unicode.t', 'wb')
294 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
291 >>> fh.write(u' $ echo babar\u03b1\n'.encode('utf-8')) and None
295 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
292 >>> fh.write(u' l\u03b5\u03b5t\n'.encode('utf-8')) and None
296
293
297 $ rt
294 $ rt
298
295
299 --- $TESTTMP/test-failure.t
296 --- $TESTTMP/test-failure.t
300 +++ $TESTTMP/test-failure.t.err
297 +++ $TESTTMP/test-failure.t.err
301 @@ -1,5 +1,5 @@
298 @@ -1,5 +1,5 @@
302 $ echo babar
299 $ echo babar
303 - rataxes
300 - rataxes
304 + babar
301 + babar
305 This is a noop statement so that
302 This is a noop statement so that
306 this test is still more bytes than success.
303 this test is still more bytes than success.
307 pad pad pad pad............................................................
304 pad pad pad pad............................................................
308
305
309 ERROR: test-failure.t output changed
306 ERROR: test-failure.t output changed
310 !.
307 !.
311 --- $TESTTMP/test-failure-unicode.t
308 --- $TESTTMP/test-failure-unicode.t
312 +++ $TESTTMP/test-failure-unicode.t.err
309 +++ $TESTTMP/test-failure-unicode.t.err
313 @@ -1,2 +1,2 @@
310 @@ -1,2 +1,2 @@
314 $ echo babar\xce\xb1 (esc)
311 $ echo babar\xce\xb1 (esc)
315 - l\xce\xb5\xce\xb5t (esc)
312 - l\xce\xb5\xce\xb5t (esc)
316 + babar\xce\xb1 (esc)
313 + babar\xce\xb1 (esc)
317
314
318 ERROR: test-failure-unicode.t output changed
315 ERROR: test-failure-unicode.t output changed
319 !
316 !
320 Failed test-failure.t: output changed
317 Failed test-failure.t: output changed
321 Failed test-failure-unicode.t: output changed
318 Failed test-failure-unicode.t: output changed
322 # Ran 3 tests, 0 skipped, 2 failed.
319 # Ran 3 tests, 0 skipped, 2 failed.
323 python hash seed: * (glob)
320 python hash seed: * (glob)
324 [1]
321 [1]
325
322
326 test --outputdir
323 test --outputdir
327 $ mkdir output
324 $ mkdir output
328 $ rt --outputdir output
325 $ rt --outputdir output
329
326
330 --- $TESTTMP/test-failure.t
327 --- $TESTTMP/test-failure.t
331 +++ $TESTTMP/output/test-failure.t.err
328 +++ $TESTTMP/output/test-failure.t.err
332 @@ -1,5 +1,5 @@
329 @@ -1,5 +1,5 @@
333 $ echo babar
330 $ echo babar
334 - rataxes
331 - rataxes
335 + babar
332 + babar
336 This is a noop statement so that
333 This is a noop statement so that
337 this test is still more bytes than success.
334 this test is still more bytes than success.
338 pad pad pad pad............................................................
335 pad pad pad pad............................................................
339
336
340 ERROR: test-failure.t output changed
337 ERROR: test-failure.t output changed
341 !.
338 !.
342 --- $TESTTMP/test-failure-unicode.t
339 --- $TESTTMP/test-failure-unicode.t
343 +++ $TESTTMP/output/test-failure-unicode.t.err
340 +++ $TESTTMP/output/test-failure-unicode.t.err
344 @@ -1,2 +1,2 @@
341 @@ -1,2 +1,2 @@
345 $ echo babar\xce\xb1 (esc)
342 $ echo babar\xce\xb1 (esc)
346 - l\xce\xb5\xce\xb5t (esc)
343 - l\xce\xb5\xce\xb5t (esc)
347 + babar\xce\xb1 (esc)
344 + babar\xce\xb1 (esc)
348
345
349 ERROR: test-failure-unicode.t output changed
346 ERROR: test-failure-unicode.t output changed
350 !
347 !
351 Failed test-failure.t: output changed
348 Failed test-failure.t: output changed
352 Failed test-failure-unicode.t: output changed
349 Failed test-failure-unicode.t: output changed
353 # Ran 3 tests, 0 skipped, 2 failed.
350 # Ran 3 tests, 0 skipped, 2 failed.
354 python hash seed: * (glob)
351 python hash seed: * (glob)
355 [1]
352 [1]
356 $ ls -a output
353 $ ls -a output
357 .
354 .
358 ..
355 ..
359 .testtimes
356 .testtimes
360 test-failure-unicode.t.err
357 test-failure-unicode.t.err
361 test-failure.t.err
358 test-failure.t.err
362
359
363 test --xunit support
360 test --xunit support
364 $ rt --xunit=xunit.xml
361 $ rt --xunit=xunit.xml
365
362
366 --- $TESTTMP/test-failure.t
363 --- $TESTTMP/test-failure.t
367 +++ $TESTTMP/test-failure.t.err
364 +++ $TESTTMP/test-failure.t.err
368 @@ -1,5 +1,5 @@
365 @@ -1,5 +1,5 @@
369 $ echo babar
366 $ echo babar
370 - rataxes
367 - rataxes
371 + babar
368 + babar
372 This is a noop statement so that
369 This is a noop statement so that
373 this test is still more bytes than success.
370 this test is still more bytes than success.
374 pad pad pad pad............................................................
371 pad pad pad pad............................................................
375
372
376 ERROR: test-failure.t output changed
373 ERROR: test-failure.t output changed
377 !.
374 !.
378 --- $TESTTMP/test-failure-unicode.t
375 --- $TESTTMP/test-failure-unicode.t
379 +++ $TESTTMP/test-failure-unicode.t.err
376 +++ $TESTTMP/test-failure-unicode.t.err
380 @@ -1,2 +1,2 @@
377 @@ -1,2 +1,2 @@
381 $ echo babar\xce\xb1 (esc)
378 $ echo babar\xce\xb1 (esc)
382 - l\xce\xb5\xce\xb5t (esc)
379 - l\xce\xb5\xce\xb5t (esc)
383 + babar\xce\xb1 (esc)
380 + babar\xce\xb1 (esc)
384
381
385 ERROR: test-failure-unicode.t output changed
382 ERROR: test-failure-unicode.t output changed
386 !
383 !
387 Failed test-failure.t: output changed
384 Failed test-failure.t: output changed
388 Failed test-failure-unicode.t: output changed
385 Failed test-failure-unicode.t: output changed
389 # Ran 3 tests, 0 skipped, 2 failed.
386 # Ran 3 tests, 0 skipped, 2 failed.
390 python hash seed: * (glob)
387 python hash seed: * (glob)
391 [1]
388 [1]
392 $ cat xunit.xml
389 $ cat xunit.xml
393 <?xml version="1.0" encoding="utf-8"?>
390 <?xml version="1.0" encoding="utf-8"?>
394 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
391 <testsuite errors="0" failures="2" name="run-tests" skipped="0" tests="3">
395 <testcase name="test-success.t" time="*"/> (glob)
392 <testcase name="test-success.t" time="*"/> (glob)
396 <testcase name="test-failure-unicode.t" time="*"> (glob)
393 <testcase name="test-failure-unicode.t" time="*"> (glob)
397 <failure message="output changed" type="output-mismatch">
394 <failure message="output changed" type="output-mismatch">
398 <![CDATA[--- $TESTTMP/test-failure-unicode.t
395 <![CDATA[--- $TESTTMP/test-failure-unicode.t
399 +++ $TESTTMP/test-failure-unicode.t.err
396 +++ $TESTTMP/test-failure-unicode.t.err
400 @@ -1,2 +1,2 @@
397 @@ -1,2 +1,2 @@
401 $ echo babar\xce\xb1 (esc)
398 $ echo babar\xce\xb1 (esc)
402 - l\xce\xb5\xce\xb5t (esc)
399 - l\xce\xb5\xce\xb5t (esc)
403 + babar\xce\xb1 (esc)
400 + babar\xce\xb1 (esc)
404 ]]> </failure>
401 ]]> </failure>
405 </testcase>
402 </testcase>
406 <testcase name="test-failure.t" time="*"> (glob)
403 <testcase name="test-failure.t" time="*"> (glob)
407 <failure message="output changed" type="output-mismatch">
404 <failure message="output changed" type="output-mismatch">
408 <![CDATA[--- $TESTTMP/test-failure.t
405 <![CDATA[--- $TESTTMP/test-failure.t
409 +++ $TESTTMP/test-failure.t.err
406 +++ $TESTTMP/test-failure.t.err
410 @@ -1,5 +1,5 @@
407 @@ -1,5 +1,5 @@
411 $ echo babar
408 $ echo babar
412 - rataxes
409 - rataxes
413 + babar
410 + babar
414 This is a noop statement so that
411 This is a noop statement so that
415 this test is still more bytes than success.
412 this test is still more bytes than success.
416 pad pad pad pad............................................................
413 pad pad pad pad............................................................
417 ]]> </failure>
414 ]]> </failure>
418 </testcase>
415 </testcase>
419 </testsuite>
416 </testsuite>
420
417
421 $ cat .testtimes
418 $ cat .testtimes
422 test-empty.t * (glob)
419 test-empty.t * (glob)
423 test-failure-globs.t * (glob)
420 test-failure-globs.t * (glob)
424 test-failure-unicode.t * (glob)
421 test-failure-unicode.t * (glob)
425 test-failure.t * (glob)
422 test-failure.t * (glob)
426 test-success.t * (glob)
423 test-success.t * (glob)
427
424
428 $ rt --list-tests
425 $ rt --list-tests
429 test-failure-unicode.t
426 test-failure-unicode.t
430 test-failure.t
427 test-failure.t
431 test-success.t
428 test-success.t
432
429
433 $ rt --list-tests --json
430 $ rt --list-tests --json
434 test-failure-unicode.t
431 test-failure-unicode.t
435 test-failure.t
432 test-failure.t
436 test-success.t
433 test-success.t
437 $ cat report.json
434 $ cat report.json
438 testreport ={
435 testreport ={
439 "test-failure-unicode.t": {
436 "test-failure-unicode.t": {
440 "result": "success"
437 "result": "success"
441 },
438 },
442 "test-failure.t": {
439 "test-failure.t": {
443 "result": "success"
440 "result": "success"
444 },
441 },
445 "test-success.t": {
442 "test-success.t": {
446 "result": "success"
443 "result": "success"
447 }
444 }
448 } (no-eol)
445 } (no-eol)
449
446
450 $ rt --list-tests --xunit=xunit.xml
447 $ rt --list-tests --xunit=xunit.xml
451 test-failure-unicode.t
448 test-failure-unicode.t
452 test-failure.t
449 test-failure.t
453 test-success.t
450 test-success.t
454 $ cat xunit.xml
451 $ cat xunit.xml
455 <?xml version="1.0" encoding="utf-8"?>
452 <?xml version="1.0" encoding="utf-8"?>
456 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
453 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
457 <testcase name="test-failure-unicode.t"/>
454 <testcase name="test-failure-unicode.t"/>
458 <testcase name="test-failure.t"/>
455 <testcase name="test-failure.t"/>
459 <testcase name="test-success.t"/>
456 <testcase name="test-success.t"/>
460 </testsuite>
457 </testsuite>
461
458
462 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
459 $ rt --list-tests test-failure* --json --xunit=xunit.xml --outputdir output
463 test-failure-unicode.t
460 test-failure-unicode.t
464 test-failure.t
461 test-failure.t
465 $ cat output/report.json
462 $ cat output/report.json
466 testreport ={
463 testreport ={
467 "test-failure-unicode.t": {
464 "test-failure-unicode.t": {
468 "result": "success"
465 "result": "success"
469 },
466 },
470 "test-failure.t": {
467 "test-failure.t": {
471 "result": "success"
468 "result": "success"
472 }
469 }
473 } (no-eol)
470 } (no-eol)
474 $ cat xunit.xml
471 $ cat xunit.xml
475 <?xml version="1.0" encoding="utf-8"?>
472 <?xml version="1.0" encoding="utf-8"?>
476 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
473 <testsuite errors="0" failures="0" name="run-tests" skipped="0" tests="0">
477 <testcase name="test-failure-unicode.t"/>
474 <testcase name="test-failure-unicode.t"/>
478 <testcase name="test-failure.t"/>
475 <testcase name="test-failure.t"/>
479 </testsuite>
476 </testsuite>
480
477
481 $ rm test-failure-unicode.t
478 $ rm test-failure-unicode.t
482
479
483 test for --retest
480 test for --retest
484 ====================
481 ====================
485
482
486 $ rt --retest
483 $ rt --retest
487
484
488 --- $TESTTMP/test-failure.t
485 --- $TESTTMP/test-failure.t
489 +++ $TESTTMP/test-failure.t.err
486 +++ $TESTTMP/test-failure.t.err
490 @@ -1,5 +1,5 @@
487 @@ -1,5 +1,5 @@
491 $ echo babar
488 $ echo babar
492 - rataxes
489 - rataxes
493 + babar
490 + babar
494 This is a noop statement so that
491 This is a noop statement so that
495 this test is still more bytes than success.
492 this test is still more bytes than success.
496 pad pad pad pad............................................................
493 pad pad pad pad............................................................
497
494
498 ERROR: test-failure.t output changed
495 ERROR: test-failure.t output changed
499 !
496 !
500 Failed test-failure.t: output changed
497 Failed test-failure.t: output changed
501 # Ran 2 tests, 1 skipped, 1 failed.
498 # Ran 2 tests, 1 skipped, 1 failed.
502 python hash seed: * (glob)
499 python hash seed: * (glob)
503 [1]
500 [1]
504
501
505 --retest works with --outputdir
502 --retest works with --outputdir
506 $ rm -r output
503 $ rm -r output
507 $ mkdir output
504 $ mkdir output
508 $ mv test-failure.t.err output
505 $ mv test-failure.t.err output
509 $ rt --retest --outputdir output
506 $ rt --retest --outputdir output
510
507
511 --- $TESTTMP/test-failure.t
508 --- $TESTTMP/test-failure.t
512 +++ $TESTTMP/output/test-failure.t.err
509 +++ $TESTTMP/output/test-failure.t.err
513 @@ -1,5 +1,5 @@
510 @@ -1,5 +1,5 @@
514 $ echo babar
511 $ echo babar
515 - rataxes
512 - rataxes
516 + babar
513 + babar
517 This is a noop statement so that
514 This is a noop statement so that
518 this test is still more bytes than success.
515 this test is still more bytes than success.
519 pad pad pad pad............................................................
516 pad pad pad pad............................................................
520
517
521 ERROR: test-failure.t output changed
518 ERROR: test-failure.t output changed
522 !
519 !
523 Failed test-failure.t: output changed
520 Failed test-failure.t: output changed
524 # Ran 2 tests, 1 skipped, 1 failed.
521 # Ran 2 tests, 1 skipped, 1 failed.
525 python hash seed: * (glob)
522 python hash seed: * (glob)
526 [1]
523 [1]
527
524
528 Selecting Tests To Run
525 Selecting Tests To Run
529 ======================
526 ======================
530
527
531 successful
528 successful
532
529
533 $ rt test-success.t
530 $ rt test-success.t
534 .
531 .
535 # Ran 1 tests, 0 skipped, 0 failed.
532 # Ran 1 tests, 0 skipped, 0 failed.
536
533
537 success w/ keyword
534 success w/ keyword
538 $ rt -k xyzzy
535 $ rt -k xyzzy
539 .
536 .
540 # Ran 2 tests, 1 skipped, 0 failed.
537 # Ran 2 tests, 1 skipped, 0 failed.
541
538
542 failed
539 failed
543
540
544 $ rt test-failure.t
541 $ rt test-failure.t
545
542
546 --- $TESTTMP/test-failure.t
543 --- $TESTTMP/test-failure.t
547 +++ $TESTTMP/test-failure.t.err
544 +++ $TESTTMP/test-failure.t.err
548 @@ -1,5 +1,5 @@
545 @@ -1,5 +1,5 @@
549 $ echo babar
546 $ echo babar
550 - rataxes
547 - rataxes
551 + babar
548 + babar
552 This is a noop statement so that
549 This is a noop statement so that
553 this test is still more bytes than success.
550 this test is still more bytes than success.
554 pad pad pad pad............................................................
551 pad pad pad pad............................................................
555
552
556 ERROR: test-failure.t output changed
553 ERROR: test-failure.t output changed
557 !
554 !
558 Failed test-failure.t: output changed
555 Failed test-failure.t: output changed
559 # Ran 1 tests, 0 skipped, 1 failed.
556 # Ran 1 tests, 0 skipped, 1 failed.
560 python hash seed: * (glob)
557 python hash seed: * (glob)
561 [1]
558 [1]
562
559
563 failure w/ keyword
560 failure w/ keyword
564 $ rt -k rataxes
561 $ rt -k rataxes
565
562
566 --- $TESTTMP/test-failure.t
563 --- $TESTTMP/test-failure.t
567 +++ $TESTTMP/test-failure.t.err
564 +++ $TESTTMP/test-failure.t.err
568 @@ -1,5 +1,5 @@
565 @@ -1,5 +1,5 @@
569 $ echo babar
566 $ echo babar
570 - rataxes
567 - rataxes
571 + babar
568 + babar
572 This is a noop statement so that
569 This is a noop statement so that
573 this test is still more bytes than success.
570 this test is still more bytes than success.
574 pad pad pad pad............................................................
571 pad pad pad pad............................................................
575
572
576 ERROR: test-failure.t output changed
573 ERROR: test-failure.t output changed
577 !
574 !
578 Failed test-failure.t: output changed
575 Failed test-failure.t: output changed
579 # Ran 2 tests, 1 skipped, 1 failed.
576 # Ran 2 tests, 1 skipped, 1 failed.
580 python hash seed: * (glob)
577 python hash seed: * (glob)
581 [1]
578 [1]
582
579
583 Verify that when a process fails to start we show a useful message
580 Verify that when a process fails to start we show a useful message
584 ==================================================================
581 ==================================================================
585
582
586 $ cat > test-serve-fail.t <<EOF
583 $ cat > test-serve-fail.t <<EOF
587 > $ echo 'abort: child process failed to start blah'
584 > $ echo 'abort: child process failed to start blah'
588 > EOF
585 > EOF
589 $ rt test-serve-fail.t
586 $ rt test-serve-fail.t
590
587
591 --- $TESTTMP/test-serve-fail.t
588 --- $TESTTMP/test-serve-fail.t
592 +++ $TESTTMP/test-serve-fail.t.err
589 +++ $TESTTMP/test-serve-fail.t.err
593 @@ -1* +1,2 @@ (glob)
590 @@ -1* +1,2 @@ (glob)
594 $ echo 'abort: child process failed to start blah'
591 $ echo 'abort: child process failed to start blah'
595 + abort: child process failed to start blah
592 + abort: child process failed to start blah
596
593
597 ERROR: test-serve-fail.t output changed
594 ERROR: test-serve-fail.t output changed
598 !
595 !
599 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
596 Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
600 # Ran 1 tests, 0 skipped, 1 failed.
597 # Ran 1 tests, 0 skipped, 1 failed.
601 python hash seed: * (glob)
598 python hash seed: * (glob)
602 [1]
599 [1]
603 $ rm test-serve-fail.t
600 $ rm test-serve-fail.t
604
601
605 Verify that we can try other ports
602 Verify that we can try other ports
606 ===================================
603 ===================================
607
604
608 Extensions aren't inherited by the invoked run-tests.py. An extension
605 Extensions aren't inherited by the invoked run-tests.py. An extension
609 introducing a repository requirement could cause this to fail. So we force
606 introducing a repository requirement could cause this to fail. So we force
610 HGRCPATH to get a clean environment.
607 HGRCPATH to get a clean environment.
611
608
612 $ HGRCPATH= hg init inuse
609 $ HGRCPATH= hg init inuse
613 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
610 $ hg serve -R inuse -p $HGPORT -d --pid-file=blocks.pid
614 $ cat blocks.pid >> $DAEMON_PIDS
611 $ cat blocks.pid >> $DAEMON_PIDS
615 $ cat > test-serve-inuse.t <<EOF
612 $ cat > test-serve-inuse.t <<EOF
616 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
613 > $ hg serve -R `pwd`/inuse -p \$HGPORT -d --pid-file=hg.pid
617 > $ cat hg.pid >> \$DAEMON_PIDS
614 > $ cat hg.pid >> \$DAEMON_PIDS
618 > EOF
615 > EOF
619 $ rt test-serve-inuse.t
616 $ rt test-serve-inuse.t
620 .
617 .
621 # Ran 1 tests, 0 skipped, 0 failed.
618 # Ran 1 tests, 0 skipped, 0 failed.
622 $ rm test-serve-inuse.t
619 $ rm test-serve-inuse.t
623 $ killdaemons.py $DAEMON_PIDS
620 $ killdaemons.py $DAEMON_PIDS
624
621
625 Running In Debug Mode
622 Running In Debug Mode
626 ======================
623 ======================
627
624
628 $ rt --debug 2>&1 | grep -v pwd
625 $ rt --debug 2>&1 | grep -v pwd
629 + echo *SALT* 0 0 (glob)
626 + echo *SALT* 0 0 (glob)
630 *SALT* 0 0 (glob)
627 *SALT* 0 0 (glob)
631 + echo babar
628 + echo babar
632 babar
629 babar
633 + echo *SALT* 10 0 (glob)
630 + echo *SALT* 10 0 (glob)
634 *SALT* 10 0 (glob)
631 *SALT* 10 0 (glob)
635 *+ echo *SALT* 0 0 (glob)
632 *+ echo *SALT* 0 0 (glob)
636 *SALT* 0 0 (glob)
633 *SALT* 0 0 (glob)
637 + echo babar
634 + echo babar
638 babar
635 babar
639 + echo *SALT* 2 0 (glob)
636 + echo *SALT* 2 0 (glob)
640 *SALT* 2 0 (glob)
637 *SALT* 2 0 (glob)
641 + echo xyzzy
638 + echo xyzzy
642 xyzzy
639 xyzzy
643 + echo *SALT* 9 0 (glob)
640 + echo *SALT* 9 0 (glob)
644 *SALT* 9 0 (glob)
641 *SALT* 9 0 (glob)
645 + printf *abc\ndef\nxyz\n* (glob)
642 + printf *abc\ndef\nxyz\n* (glob)
646 abc
643 abc
647 def
644 def
648 xyz
645 xyz
649 + echo *SALT* 15 0 (glob)
646 + echo *SALT* 15 0 (glob)
650 *SALT* 15 0 (glob)
647 *SALT* 15 0 (glob)
651 + printf *zyx\nwvu\ntsr\n* (glob)
648 + printf *zyx\nwvu\ntsr\n* (glob)
652 zyx
649 zyx
653 wvu
650 wvu
654 tsr
651 tsr
655 + echo *SALT* 22 0 (glob)
652 + echo *SALT* 22 0 (glob)
656 *SALT* 22 0 (glob)
653 *SALT* 22 0 (glob)
657 .
654 .
658 # Ran 2 tests, 0 skipped, 0 failed.
655 # Ran 2 tests, 0 skipped, 0 failed.
659
656
660 Parallel runs
657 Parallel runs
661 ==============
658 ==============
662
659
663 (duplicate the failing test to get predictable output)
660 (duplicate the failing test to get predictable output)
664 $ cp test-failure.t test-failure-copy.t
661 $ cp test-failure.t test-failure-copy.t
665
662
666 $ rt --jobs 2 test-failure*.t -n
663 $ rt --jobs 2 test-failure*.t -n
667 !!
664 !!
668 Failed test-failure*.t: output changed (glob)
665 Failed test-failure*.t: output changed (glob)
669 Failed test-failure*.t: output changed (glob)
666 Failed test-failure*.t: output changed (glob)
670 # Ran 2 tests, 0 skipped, 2 failed.
667 # Ran 2 tests, 0 skipped, 2 failed.
671 python hash seed: * (glob)
668 python hash seed: * (glob)
672 [1]
669 [1]
673
670
674 failures in parallel with --first should only print one failure
671 failures in parallel with --first should only print one failure
675 $ rt --jobs 2 --first test-failure*.t
672 $ rt --jobs 2 --first test-failure*.t
676
673
677 --- $TESTTMP/test-failure*.t (glob)
674 --- $TESTTMP/test-failure*.t (glob)
678 +++ $TESTTMP/test-failure*.t.err (glob)
675 +++ $TESTTMP/test-failure*.t.err (glob)
679 @@ -1,5 +1,5 @@
676 @@ -1,5 +1,5 @@
680 $ echo babar
677 $ echo babar
681 - rataxes
678 - rataxes
682 + babar
679 + babar
683 This is a noop statement so that
680 This is a noop statement so that
684 this test is still more bytes than success.
681 this test is still more bytes than success.
685 pad pad pad pad............................................................
682 pad pad pad pad............................................................
686
683
687 Failed test-failure*.t: output changed (glob)
684 Failed test-failure*.t: output changed (glob)
688 Failed test-failure*.t: output changed (glob)
685 Failed test-failure*.t: output changed (glob)
689 # Ran 2 tests, 0 skipped, 2 failed.
686 # Ran 2 tests, 0 skipped, 2 failed.
690 python hash seed: * (glob)
687 python hash seed: * (glob)
691 [1]
688 [1]
692
689
693
690
694 (delete the duplicated test file)
691 (delete the duplicated test file)
695 $ rm test-failure-copy.t
692 $ rm test-failure-copy.t
696
693
697
694
698 Interactive run
695 Interactive run
699 ===============
696 ===============
700
697
701 (backup the failing test)
698 (backup the failing test)
702 $ cp test-failure.t backup
699 $ cp test-failure.t backup
703
700
704 Refuse the fix
701 Refuse the fix
705
702
706 $ echo 'n' | rt -i
703 $ echo 'n' | rt -i
707
704
708 --- $TESTTMP/test-failure.t
705 --- $TESTTMP/test-failure.t
709 +++ $TESTTMP/test-failure.t.err
706 +++ $TESTTMP/test-failure.t.err
710 @@ -1,5 +1,5 @@
707 @@ -1,5 +1,5 @@
711 $ echo babar
708 $ echo babar
712 - rataxes
709 - rataxes
713 + babar
710 + babar
714 This is a noop statement so that
711 This is a noop statement so that
715 this test is still more bytes than success.
712 this test is still more bytes than success.
716 pad pad pad pad............................................................
713 pad pad pad pad............................................................
717 Accept this change? [n]
714 Accept this change? [n]
718 ERROR: test-failure.t output changed
715 ERROR: test-failure.t output changed
719 !.
716 !.
720 Failed test-failure.t: output changed
717 Failed test-failure.t: output changed
721 # Ran 2 tests, 0 skipped, 1 failed.
718 # Ran 2 tests, 0 skipped, 1 failed.
722 python hash seed: * (glob)
719 python hash seed: * (glob)
723 [1]
720 [1]
724
721
725 $ cat test-failure.t
722 $ cat test-failure.t
726 $ echo babar
723 $ echo babar
727 rataxes
724 rataxes
728 This is a noop statement so that
725 This is a noop statement so that
729 this test is still more bytes than success.
726 this test is still more bytes than success.
730 pad pad pad pad............................................................
727 pad pad pad pad............................................................
731 pad pad pad pad............................................................
728 pad pad pad pad............................................................
732 pad pad pad pad............................................................
729 pad pad pad pad............................................................
733 pad pad pad pad............................................................
730 pad pad pad pad............................................................
734 pad pad pad pad............................................................
731 pad pad pad pad............................................................
735 pad pad pad pad............................................................
732 pad pad pad pad............................................................
736
733
737 Interactive with custom view
734 Interactive with custom view
738
735
739 $ echo 'n' | rt -i --view echo
736 $ echo 'n' | rt -i --view echo
740 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
737 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
741 Accept this change? [n]* (glob)
738 Accept this change? [n]* (glob)
742 ERROR: test-failure.t output changed
739 ERROR: test-failure.t output changed
743 !.
740 !.
744 Failed test-failure.t: output changed
741 Failed test-failure.t: output changed
745 # Ran 2 tests, 0 skipped, 1 failed.
742 # Ran 2 tests, 0 skipped, 1 failed.
746 python hash seed: * (glob)
743 python hash seed: * (glob)
747 [1]
744 [1]
748
745
749 View the fix
746 View the fix
750
747
751 $ echo 'y' | rt --view echo
748 $ echo 'y' | rt --view echo
752 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
749 $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
753
750
754 ERROR: test-failure.t output changed
751 ERROR: test-failure.t output changed
755 !.
752 !.
756 Failed test-failure.t: output changed
753 Failed test-failure.t: output changed
757 # Ran 2 tests, 0 skipped, 1 failed.
754 # Ran 2 tests, 0 skipped, 1 failed.
758 python hash seed: * (glob)
755 python hash seed: * (glob)
759 [1]
756 [1]
760
757
761 Accept the fix
758 Accept the fix
762
759
763 $ cat >> test-failure.t <<EOF
760 $ cat >> test-failure.t <<EOF
764 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
761 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
765 > saved backup bundle to \$TESTTMP/foo.hg
762 > saved backup bundle to \$TESTTMP/foo.hg
766 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
763 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
767 > saved backup bundle to $TESTTMP\\foo.hg
764 > saved backup bundle to $TESTTMP\\foo.hg
768 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
765 > $ echo 'saved backup bundle to \$TESTTMP/foo.hg'
769 > saved backup bundle to \$TESTTMP/*.hg (glob)
766 > saved backup bundle to \$TESTTMP/*.hg (glob)
770 > EOF
767 > EOF
771 $ echo 'y' | rt -i 2>&1
768 $ echo 'y' | rt -i 2>&1
772
769
773 --- $TESTTMP/test-failure.t
770 --- $TESTTMP/test-failure.t
774 +++ $TESTTMP/test-failure.t.err
771 +++ $TESTTMP/test-failure.t.err
775 @@ -1,5 +1,5 @@
772 @@ -1,5 +1,5 @@
776 $ echo babar
773 $ echo babar
777 - rataxes
774 - rataxes
778 + babar
775 + babar
779 This is a noop statement so that
776 This is a noop statement so that
780 this test is still more bytes than success.
777 this test is still more bytes than success.
781 pad pad pad pad............................................................
778 pad pad pad pad............................................................
782 @@ -11,6 +11,6 @@
779 @@ -11,6 +11,6 @@
783 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
780 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
784 saved backup bundle to $TESTTMP/foo.hg
781 saved backup bundle to $TESTTMP/foo.hg
785 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
782 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
786 - saved backup bundle to $TESTTMP\foo.hg
783 - saved backup bundle to $TESTTMP\foo.hg
787 + saved backup bundle to $TESTTMP/foo.hg
784 + saved backup bundle to $TESTTMP/foo.hg
788 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
785 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
789 saved backup bundle to $TESTTMP/*.hg (glob)
786 saved backup bundle to $TESTTMP/*.hg (glob)
790 Accept this change? [n] ..
787 Accept this change? [n] ..
791 # Ran 2 tests, 0 skipped, 0 failed.
788 # Ran 2 tests, 0 skipped, 0 failed.
792
789
793 $ sed -e 's,(glob)$,&<,g' test-failure.t
790 $ sed -e 's,(glob)$,&<,g' test-failure.t
794 $ echo babar
791 $ echo babar
795 babar
792 babar
796 This is a noop statement so that
793 This is a noop statement so that
797 this test is still more bytes than success.
794 this test is still more bytes than success.
798 pad pad pad pad............................................................
795 pad pad pad pad............................................................
799 pad pad pad pad............................................................
796 pad pad pad pad............................................................
800 pad pad pad pad............................................................
797 pad pad pad pad............................................................
801 pad pad pad pad............................................................
798 pad pad pad pad............................................................
802 pad pad pad pad............................................................
799 pad pad pad pad............................................................
803 pad pad pad pad............................................................
800 pad pad pad pad............................................................
804 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
801 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
805 saved backup bundle to $TESTTMP/foo.hg
802 saved backup bundle to $TESTTMP/foo.hg
806 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
803 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
807 saved backup bundle to $TESTTMP/foo.hg
804 saved backup bundle to $TESTTMP/foo.hg
808 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
805 $ echo 'saved backup bundle to $TESTTMP/foo.hg'
809 saved backup bundle to $TESTTMP/*.hg (glob)<
806 saved backup bundle to $TESTTMP/*.hg (glob)<
810
807
811 Race condition - test file was modified when test is running
808 Race condition - test file was modified when test is running
812
809
813 $ TESTRACEDIR=`pwd`
810 $ TESTRACEDIR=`pwd`
814 $ export TESTRACEDIR
811 $ export TESTRACEDIR
815 $ cat > test-race.t <<EOF
812 $ cat > test-race.t <<EOF
816 > $ echo 1
813 > $ echo 1
817 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
814 > $ echo "# a new line" >> $TESTRACEDIR/test-race.t
818 > EOF
815 > EOF
819
816
820 $ rt -i test-race.t
817 $ rt -i test-race.t
821
818
822 --- $TESTTMP/test-race.t
819 --- $TESTTMP/test-race.t
823 +++ $TESTTMP/test-race.t.err
820 +++ $TESTTMP/test-race.t.err
824 @@ -1,2 +1,3 @@
821 @@ -1,2 +1,3 @@
825 $ echo 1
822 $ echo 1
826 + 1
823 + 1
827 $ echo "# a new line" >> $TESTTMP/test-race.t
824 $ echo "# a new line" >> $TESTTMP/test-race.t
828 Reference output has changed (run again to prompt changes)
825 Reference output has changed (run again to prompt changes)
829 ERROR: test-race.t output changed
826 ERROR: test-race.t output changed
830 !
827 !
831 Failed test-race.t: output changed
828 Failed test-race.t: output changed
832 # Ran 1 tests, 0 skipped, 1 failed.
829 # Ran 1 tests, 0 skipped, 1 failed.
833 python hash seed: * (glob)
830 python hash seed: * (glob)
834 [1]
831 [1]
835
832
836 $ rm test-race.t
833 $ rm test-race.t
837
834
838 When "#testcases" is used in .t files
835 When "#testcases" is used in .t files
839
836
840 $ cat >> test-cases.t <<EOF
837 $ cat >> test-cases.t <<EOF
841 > #testcases a b
838 > #testcases a b
842 > #if a
839 > #if a
843 > $ echo 1
840 > $ echo 1
844 > #endif
841 > #endif
845 > #if b
842 > #if b
846 > $ echo 2
843 > $ echo 2
847 > #endif
844 > #endif
848 > EOF
845 > EOF
849
846
850 $ cat <<EOF | rt -i test-cases.t 2>&1
847 $ cat <<EOF | rt -i test-cases.t 2>&1
851 > y
848 > y
852 > y
849 > y
853 > EOF
850 > EOF
854
851
855 --- $TESTTMP/test-cases.t
852 --- $TESTTMP/test-cases.t
856 +++ $TESTTMP/test-cases.t.a.err
853 +++ $TESTTMP/test-cases.t.a.err
857 @@ -1,6 +1,7 @@
854 @@ -1,6 +1,7 @@
858 #testcases a b
855 #testcases a b
859 #if a
856 #if a
860 $ echo 1
857 $ echo 1
861 + 1
858 + 1
862 #endif
859 #endif
863 #if b
860 #if b
864 $ echo 2
861 $ echo 2
865 Accept this change? [n] .
862 Accept this change? [n] .
866 --- $TESTTMP/test-cases.t
863 --- $TESTTMP/test-cases.t
867 +++ $TESTTMP/test-cases.t.b.err
864 +++ $TESTTMP/test-cases.t.b.err
868 @@ -5,4 +5,5 @@
865 @@ -5,4 +5,5 @@
869 #endif
866 #endif
870 #if b
867 #if b
871 $ echo 2
868 $ echo 2
872 + 2
869 + 2
873 #endif
870 #endif
874 Accept this change? [n] .
871 Accept this change? [n] .
875 # Ran 2 tests, 0 skipped, 0 failed.
872 # Ran 2 tests, 0 skipped, 0 failed.
876
873
877 $ cat test-cases.t
874 $ cat test-cases.t
878 #testcases a b
875 #testcases a b
879 #if a
876 #if a
880 $ echo 1
877 $ echo 1
881 1
878 1
882 #endif
879 #endif
883 #if b
880 #if b
884 $ echo 2
881 $ echo 2
885 2
882 2
886 #endif
883 #endif
887
884
888 $ cat >> test-cases.t <<'EOF'
885 $ cat >> test-cases.t <<'EOF'
889 > #if a
886 > #if a
890 > $ NAME=A
887 > $ NAME=A
891 > #else
888 > #else
892 > $ NAME=B
889 > $ NAME=B
893 > #endif
890 > #endif
894 > $ echo $NAME
891 > $ echo $NAME
895 > A (a !)
892 > A (a !)
896 > B (b !)
893 > B (b !)
897 > EOF
894 > EOF
898 $ rt test-cases.t
895 $ rt test-cases.t
899 ..
896 ..
900 # Ran 2 tests, 0 skipped, 0 failed.
897 # Ran 2 tests, 0 skipped, 0 failed.
901
898
902 $ rm test-cases.t
899 $ rm test-cases.t
903
900
904 (reinstall)
901 (reinstall)
905 $ mv backup test-failure.t
902 $ mv backup test-failure.t
906
903
907 No Diff
904 No Diff
908 ===============
905 ===============
909
906
910 $ rt --nodiff
907 $ rt --nodiff
911 !.
908 !.
912 Failed test-failure.t: output changed
909 Failed test-failure.t: output changed
913 # Ran 2 tests, 0 skipped, 1 failed.
910 # Ran 2 tests, 0 skipped, 1 failed.
914 python hash seed: * (glob)
911 python hash seed: * (glob)
915 [1]
912 [1]
916
913
917 test --tmpdir support
914 test --tmpdir support
918 $ rt --tmpdir=$TESTTMP/keep test-success.t
915 $ rt --tmpdir=$TESTTMP/keep test-success.t
919
916
920 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
917 Keeping testtmp dir: $TESTTMP/keep/child1/test-success.t
921 Keeping threadtmp dir: $TESTTMP/keep/child1
918 Keeping threadtmp dir: $TESTTMP/keep/child1
922 .
919 .
923 # Ran 1 tests, 0 skipped, 0 failed.
920 # Ran 1 tests, 0 skipped, 0 failed.
924
921
925 timeouts
922 timeouts
926 ========
923 ========
927 $ cat > test-timeout.t <<EOF
924 $ cat > test-timeout.t <<EOF
928 > $ sleep 2
925 > $ sleep 2
929 > $ echo pass
926 > $ echo pass
930 > pass
927 > pass
931 > EOF
928 > EOF
932 > echo '#require slow' > test-slow-timeout.t
929 > echo '#require slow' > test-slow-timeout.t
933 > cat test-timeout.t >> test-slow-timeout.t
930 > cat test-timeout.t >> test-slow-timeout.t
934 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
931 $ rt --timeout=1 --slowtimeout=3 test-timeout.t test-slow-timeout.t
935 st
932 st
936 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
933 Skipped test-slow-timeout.t: missing feature: allow slow tests (use --allow-slow-tests)
937 Failed test-timeout.t: timed out
934 Failed test-timeout.t: timed out
938 # Ran 1 tests, 1 skipped, 1 failed.
935 # Ran 1 tests, 1 skipped, 1 failed.
939 python hash seed: * (glob)
936 python hash seed: * (glob)
940 [1]
937 [1]
941 $ rt --timeout=1 --slowtimeout=3 \
938 $ rt --timeout=1 --slowtimeout=3 \
942 > test-timeout.t test-slow-timeout.t --allow-slow-tests
939 > test-timeout.t test-slow-timeout.t --allow-slow-tests
943 .t
940 .t
944 Failed test-timeout.t: timed out
941 Failed test-timeout.t: timed out
945 # Ran 2 tests, 0 skipped, 1 failed.
942 # Ran 2 tests, 0 skipped, 1 failed.
946 python hash seed: * (glob)
943 python hash seed: * (glob)
947 [1]
944 [1]
948 $ rm test-timeout.t test-slow-timeout.t
945 $ rm test-timeout.t test-slow-timeout.t
949
946
950 test for --time
947 test for --time
951 ==================
948 ==================
952
949
953 $ rt test-success.t --time
950 $ rt test-success.t --time
954 .
951 .
955 # Ran 1 tests, 0 skipped, 0 failed.
952 # Ran 1 tests, 0 skipped, 0 failed.
956 # Producing time report
953 # Producing time report
957 start end cuser csys real Test
954 start end cuser csys real Test
958 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
955 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
959
956
960 test for --time with --job enabled
957 test for --time with --job enabled
961 ====================================
958 ====================================
962
959
963 $ rt test-success.t --time --jobs 2
960 $ rt test-success.t --time --jobs 2
964 .
961 .
965 # Ran 1 tests, 0 skipped, 0 failed.
962 # Ran 1 tests, 0 skipped, 0 failed.
966 # Producing time report
963 # Producing time report
967 start end cuser csys real Test
964 start end cuser csys real Test
968 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
965 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
969
966
970 Skips
967 Skips
971 ================
968 ================
972 $ cat > test-skip.t <<EOF
969 $ cat > test-skip.t <<EOF
973 > $ echo xyzzy
970 > $ echo xyzzy
974 > #if true
971 > #if true
975 > #require false
972 > #require false
976 > #end
973 > #end
977 > EOF
974 > EOF
978 $ cat > test-noskip.t <<EOF
975 $ cat > test-noskip.t <<EOF
979 > #if false
976 > #if false
980 > #require false
977 > #require false
981 > #endif
978 > #endif
982 > EOF
979 > EOF
983 $ rt --nodiff
980 $ rt --nodiff
984 !.s.
981 !.s.
985 Skipped test-skip.t: missing feature: nail clipper
982 Skipped test-skip.t: missing feature: nail clipper
986 Failed test-failure.t: output changed
983 Failed test-failure.t: output changed
987 # Ran 3 tests, 1 skipped, 1 failed.
984 # Ran 3 tests, 1 skipped, 1 failed.
988 python hash seed: * (glob)
985 python hash seed: * (glob)
989 [1]
986 [1]
990
987
991 $ rm test-noskip.t
988 $ rm test-noskip.t
992 $ rt --keyword xyzzy
989 $ rt --keyword xyzzy
993 .s
990 .s
994 Skipped test-skip.t: missing feature: nail clipper
991 Skipped test-skip.t: missing feature: nail clipper
995 # Ran 2 tests, 2 skipped, 0 failed.
992 # Ran 2 tests, 2 skipped, 0 failed.
996
993
997 Skips with xml
994 Skips with xml
998 $ rt --keyword xyzzy \
995 $ rt --keyword xyzzy \
999 > --xunit=xunit.xml
996 > --xunit=xunit.xml
1000 .s
997 .s
1001 Skipped test-skip.t: missing feature: nail clipper
998 Skipped test-skip.t: missing feature: nail clipper
1002 # Ran 2 tests, 2 skipped, 0 failed.
999 # Ran 2 tests, 2 skipped, 0 failed.
1003 $ cat xunit.xml
1000 $ cat xunit.xml
1004 <?xml version="1.0" encoding="utf-8"?>
1001 <?xml version="1.0" encoding="utf-8"?>
1005 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1002 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
1006 <testcase name="test-success.t" time="*"/> (glob)
1003 <testcase name="test-success.t" time="*"/> (glob)
1007 <testcase name="test-skip.t">
1004 <testcase name="test-skip.t">
1008 <skipped>
1005 <skipped>
1009 <![CDATA[missing feature: nail clipper]]> </skipped>
1006 <![CDATA[missing feature: nail clipper]]> </skipped>
1010 </testcase>
1007 </testcase>
1011 </testsuite>
1008 </testsuite>
1012
1009
1013 Missing skips or blacklisted skips don't count as executed:
1010 Missing skips or blacklisted skips don't count as executed:
1014 $ echo test-failure.t > blacklist
1011 $ echo test-failure.t > blacklist
1015 $ rt --blacklist=blacklist --json\
1012 $ rt --blacklist=blacklist --json\
1016 > test-failure.t test-bogus.t
1013 > test-failure.t test-bogus.t
1017 ss
1014 ss
1018 Skipped test-bogus.t: Doesn't exist
1015 Skipped test-bogus.t: Doesn't exist
1019 Skipped test-failure.t: blacklisted
1016 Skipped test-failure.t: blacklisted
1020 # Ran 0 tests, 2 skipped, 0 failed.
1017 # Ran 0 tests, 2 skipped, 0 failed.
1021 $ cat report.json
1018 $ cat report.json
1022 testreport ={
1019 testreport ={
1023 "test-bogus.t": {
1020 "test-bogus.t": {
1024 "result": "skip"
1021 "result": "skip"
1025 },
1022 },
1026 "test-failure.t": {
1023 "test-failure.t": {
1027 "result": "skip"
1024 "result": "skip"
1028 }
1025 }
1029 } (no-eol)
1026 } (no-eol)
1030
1027
1031 Whitelist trumps blacklist
1028 Whitelist trumps blacklist
1032 $ echo test-failure.t > whitelist
1029 $ echo test-failure.t > whitelist
1033 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1030 $ rt --blacklist=blacklist --whitelist=whitelist --json\
1034 > test-failure.t test-bogus.t
1031 > test-failure.t test-bogus.t
1035 s
1032 s
1036 --- $TESTTMP/test-failure.t
1033 --- $TESTTMP/test-failure.t
1037 +++ $TESTTMP/test-failure.t.err
1034 +++ $TESTTMP/test-failure.t.err
1038 @@ -1,5 +1,5 @@
1035 @@ -1,5 +1,5 @@
1039 $ echo babar
1036 $ echo babar
1040 - rataxes
1037 - rataxes
1041 + babar
1038 + babar
1042 This is a noop statement so that
1039 This is a noop statement so that
1043 this test is still more bytes than success.
1040 this test is still more bytes than success.
1044 pad pad pad pad............................................................
1041 pad pad pad pad............................................................
1045
1042
1046 ERROR: test-failure.t output changed
1043 ERROR: test-failure.t output changed
1047 !
1044 !
1048 Skipped test-bogus.t: Doesn't exist
1045 Skipped test-bogus.t: Doesn't exist
1049 Failed test-failure.t: output changed
1046 Failed test-failure.t: output changed
1050 # Ran 1 tests, 1 skipped, 1 failed.
1047 # Ran 1 tests, 1 skipped, 1 failed.
1051 python hash seed: * (glob)
1048 python hash seed: * (glob)
1052 [1]
1049 [1]
1053
1050
1054 Ensure that --test-list causes only the tests listed in that file to
1051 Ensure that --test-list causes only the tests listed in that file to
1055 be executed.
1052 be executed.
1056 $ echo test-success.t >> onlytest
1053 $ echo test-success.t >> onlytest
1057 $ rt --test-list=onlytest
1054 $ rt --test-list=onlytest
1058 .
1055 .
1059 # Ran 1 tests, 0 skipped, 0 failed.
1056 # Ran 1 tests, 0 skipped, 0 failed.
1060 $ echo test-bogus.t >> anothertest
1057 $ echo test-bogus.t >> anothertest
1061 $ rt --test-list=onlytest --test-list=anothertest
1058 $ rt --test-list=onlytest --test-list=anothertest
1062 s.
1059 s.
1063 Skipped test-bogus.t: Doesn't exist
1060 Skipped test-bogus.t: Doesn't exist
1064 # Ran 1 tests, 1 skipped, 0 failed.
1061 # Ran 1 tests, 1 skipped, 0 failed.
1065 $ rm onlytest anothertest
1062 $ rm onlytest anothertest
1066
1063
1067 test for --json
1064 test for --json
1068 ==================
1065 ==================
1069
1066
1070 $ rt --json
1067 $ rt --json
1071
1068
1072 --- $TESTTMP/test-failure.t
1069 --- $TESTTMP/test-failure.t
1073 +++ $TESTTMP/test-failure.t.err
1070 +++ $TESTTMP/test-failure.t.err
1074 @@ -1,5 +1,5 @@
1071 @@ -1,5 +1,5 @@
1075 $ echo babar
1072 $ echo babar
1076 - rataxes
1073 - rataxes
1077 + babar
1074 + babar
1078 This is a noop statement so that
1075 This is a noop statement so that
1079 this test is still more bytes than success.
1076 this test is still more bytes than success.
1080 pad pad pad pad............................................................
1077 pad pad pad pad............................................................
1081
1078
1082 ERROR: test-failure.t output changed
1079 ERROR: test-failure.t output changed
1083 !.s
1080 !.s
1084 Skipped test-skip.t: missing feature: nail clipper
1081 Skipped test-skip.t: missing feature: nail clipper
1085 Failed test-failure.t: output changed
1082 Failed test-failure.t: output changed
1086 # Ran 2 tests, 1 skipped, 1 failed.
1083 # Ran 2 tests, 1 skipped, 1 failed.
1087 python hash seed: * (glob)
1084 python hash seed: * (glob)
1088 [1]
1085 [1]
1089
1086
1090 $ cat report.json
1087 $ cat report.json
1091 testreport ={
1088 testreport ={
1092 "test-failure.t": [\{] (re)
1089 "test-failure.t": [\{] (re)
1093 "csys": "\s*[\d\.]{4,5}", ? (re)
1090 "csys": "\s*[\d\.]{4,5}", ? (re)
1094 "cuser": "\s*[\d\.]{4,5}", ? (re)
1091 "cuser": "\s*[\d\.]{4,5}", ? (re)
1095 "diff": "---.+\+\+\+.+", ? (re)
1092 "diff": "---.+\+\+\+.+", ? (re)
1096 "end": "\s*[\d\.]{4,5}", ? (re)
1093 "end": "\s*[\d\.]{4,5}", ? (re)
1097 "result": "failure", ? (re)
1094 "result": "failure", ? (re)
1098 "start": "\s*[\d\.]{4,5}", ? (re)
1095 "start": "\s*[\d\.]{4,5}", ? (re)
1099 "time": "\s*[\d\.]{4,5}" (re)
1096 "time": "\s*[\d\.]{4,5}" (re)
1100 }, ? (re)
1097 }, ? (re)
1101 "test-skip.t": {
1098 "test-skip.t": {
1102 "csys": "\s*[\d\.]{4,5}", ? (re)
1099 "csys": "\s*[\d\.]{4,5}", ? (re)
1103 "cuser": "\s*[\d\.]{4,5}", ? (re)
1100 "cuser": "\s*[\d\.]{4,5}", ? (re)
1104 "diff": "", ? (re)
1101 "diff": "", ? (re)
1105 "end": "\s*[\d\.]{4,5}", ? (re)
1102 "end": "\s*[\d\.]{4,5}", ? (re)
1106 "result": "skip", ? (re)
1103 "result": "skip", ? (re)
1107 "start": "\s*[\d\.]{4,5}", ? (re)
1104 "start": "\s*[\d\.]{4,5}", ? (re)
1108 "time": "\s*[\d\.]{4,5}" (re)
1105 "time": "\s*[\d\.]{4,5}" (re)
1109 }, ? (re)
1106 }, ? (re)
1110 "test-success.t": [\{] (re)
1107 "test-success.t": [\{] (re)
1111 "csys": "\s*[\d\.]{4,5}", ? (re)
1108 "csys": "\s*[\d\.]{4,5}", ? (re)
1112 "cuser": "\s*[\d\.]{4,5}", ? (re)
1109 "cuser": "\s*[\d\.]{4,5}", ? (re)
1113 "diff": "", ? (re)
1110 "diff": "", ? (re)
1114 "end": "\s*[\d\.]{4,5}", ? (re)
1111 "end": "\s*[\d\.]{4,5}", ? (re)
1115 "result": "success", ? (re)
1112 "result": "success", ? (re)
1116 "start": "\s*[\d\.]{4,5}", ? (re)
1113 "start": "\s*[\d\.]{4,5}", ? (re)
1117 "time": "\s*[\d\.]{4,5}" (re)
1114 "time": "\s*[\d\.]{4,5}" (re)
1118 }
1115 }
1119 } (no-eol)
1116 } (no-eol)
1120 --json with --outputdir
1117 --json with --outputdir
1121
1118
1122 $ rm report.json
1119 $ rm report.json
1123 $ rm -r output
1120 $ rm -r output
1124 $ mkdir output
1121 $ mkdir output
1125 $ rt --json --outputdir output
1122 $ rt --json --outputdir output
1126
1123
1127 --- $TESTTMP/test-failure.t
1124 --- $TESTTMP/test-failure.t
1128 +++ $TESTTMP/output/test-failure.t.err
1125 +++ $TESTTMP/output/test-failure.t.err
1129 @@ -1,5 +1,5 @@
1126 @@ -1,5 +1,5 @@
1130 $ echo babar
1127 $ echo babar
1131 - rataxes
1128 - rataxes
1132 + babar
1129 + babar
1133 This is a noop statement so that
1130 This is a noop statement so that
1134 this test is still more bytes than success.
1131 this test is still more bytes than success.
1135 pad pad pad pad............................................................
1132 pad pad pad pad............................................................
1136
1133
1137 ERROR: test-failure.t output changed
1134 ERROR: test-failure.t output changed
1138 !.s
1135 !.s
1139 Skipped test-skip.t: missing feature: nail clipper
1136 Skipped test-skip.t: missing feature: nail clipper
1140 Failed test-failure.t: output changed
1137 Failed test-failure.t: output changed
1141 # Ran 2 tests, 1 skipped, 1 failed.
1138 # Ran 2 tests, 1 skipped, 1 failed.
1142 python hash seed: * (glob)
1139 python hash seed: * (glob)
1143 [1]
1140 [1]
1144 $ f report.json
1141 $ f report.json
1145 report.json: file not found
1142 report.json: file not found
1146 $ cat output/report.json
1143 $ cat output/report.json
1147 testreport ={
1144 testreport ={
1148 "test-failure.t": [\{] (re)
1145 "test-failure.t": [\{] (re)
1149 "csys": "\s*[\d\.]{4,5}", ? (re)
1146 "csys": "\s*[\d\.]{4,5}", ? (re)
1150 "cuser": "\s*[\d\.]{4,5}", ? (re)
1147 "cuser": "\s*[\d\.]{4,5}", ? (re)
1151 "diff": "---.+\+\+\+.+", ? (re)
1148 "diff": "---.+\+\+\+.+", ? (re)
1152 "end": "\s*[\d\.]{4,5}", ? (re)
1149 "end": "\s*[\d\.]{4,5}", ? (re)
1153 "result": "failure", ? (re)
1150 "result": "failure", ? (re)
1154 "start": "\s*[\d\.]{4,5}", ? (re)
1151 "start": "\s*[\d\.]{4,5}", ? (re)
1155 "time": "\s*[\d\.]{4,5}" (re)
1152 "time": "\s*[\d\.]{4,5}" (re)
1156 }, ? (re)
1153 }, ? (re)
1157 "test-skip.t": {
1154 "test-skip.t": {
1158 "csys": "\s*[\d\.]{4,5}", ? (re)
1155 "csys": "\s*[\d\.]{4,5}", ? (re)
1159 "cuser": "\s*[\d\.]{4,5}", ? (re)
1156 "cuser": "\s*[\d\.]{4,5}", ? (re)
1160 "diff": "", ? (re)
1157 "diff": "", ? (re)
1161 "end": "\s*[\d\.]{4,5}", ? (re)
1158 "end": "\s*[\d\.]{4,5}", ? (re)
1162 "result": "skip", ? (re)
1159 "result": "skip", ? (re)
1163 "start": "\s*[\d\.]{4,5}", ? (re)
1160 "start": "\s*[\d\.]{4,5}", ? (re)
1164 "time": "\s*[\d\.]{4,5}" (re)
1161 "time": "\s*[\d\.]{4,5}" (re)
1165 }, ? (re)
1162 }, ? (re)
1166 "test-success.t": [\{] (re)
1163 "test-success.t": [\{] (re)
1167 "csys": "\s*[\d\.]{4,5}", ? (re)
1164 "csys": "\s*[\d\.]{4,5}", ? (re)
1168 "cuser": "\s*[\d\.]{4,5}", ? (re)
1165 "cuser": "\s*[\d\.]{4,5}", ? (re)
1169 "diff": "", ? (re)
1166 "diff": "", ? (re)
1170 "end": "\s*[\d\.]{4,5}", ? (re)
1167 "end": "\s*[\d\.]{4,5}", ? (re)
1171 "result": "success", ? (re)
1168 "result": "success", ? (re)
1172 "start": "\s*[\d\.]{4,5}", ? (re)
1169 "start": "\s*[\d\.]{4,5}", ? (re)
1173 "time": "\s*[\d\.]{4,5}" (re)
1170 "time": "\s*[\d\.]{4,5}" (re)
1174 }
1171 }
1175 } (no-eol)
1172 } (no-eol)
1176 $ ls -a output
1173 $ ls -a output
1177 .
1174 .
1178 ..
1175 ..
1179 .testtimes
1176 .testtimes
1180 report.json
1177 report.json
1181 test-failure.t.err
1178 test-failure.t.err
1182
1179
1183 Test that failed test accepted through interactive are properly reported:
1180 Test that failed test accepted through interactive are properly reported:
1184
1181
1185 $ cp test-failure.t backup
1182 $ cp test-failure.t backup
1186 $ echo y | rt --json -i
1183 $ echo y | rt --json -i
1187
1184
1188 --- $TESTTMP/test-failure.t
1185 --- $TESTTMP/test-failure.t
1189 +++ $TESTTMP/test-failure.t.err
1186 +++ $TESTTMP/test-failure.t.err
1190 @@ -1,5 +1,5 @@
1187 @@ -1,5 +1,5 @@
1191 $ echo babar
1188 $ echo babar
1192 - rataxes
1189 - rataxes
1193 + babar
1190 + babar
1194 This is a noop statement so that
1191 This is a noop statement so that
1195 this test is still more bytes than success.
1192 this test is still more bytes than success.
1196 pad pad pad pad............................................................
1193 pad pad pad pad............................................................
1197 Accept this change? [n] ..s
1194 Accept this change? [n] ..s
1198 Skipped test-skip.t: missing feature: nail clipper
1195 Skipped test-skip.t: missing feature: nail clipper
1199 # Ran 2 tests, 1 skipped, 0 failed.
1196 # Ran 2 tests, 1 skipped, 0 failed.
1200
1197
1201 $ cat report.json
1198 $ cat report.json
1202 testreport ={
1199 testreport ={
1203 "test-failure.t": [\{] (re)
1200 "test-failure.t": [\{] (re)
1204 "csys": "\s*[\d\.]{4,5}", ? (re)
1201 "csys": "\s*[\d\.]{4,5}", ? (re)
1205 "cuser": "\s*[\d\.]{4,5}", ? (re)
1202 "cuser": "\s*[\d\.]{4,5}", ? (re)
1206 "diff": "", ? (re)
1203 "diff": "", ? (re)
1207 "end": "\s*[\d\.]{4,5}", ? (re)
1204 "end": "\s*[\d\.]{4,5}", ? (re)
1208 "result": "success", ? (re)
1205 "result": "success", ? (re)
1209 "start": "\s*[\d\.]{4,5}", ? (re)
1206 "start": "\s*[\d\.]{4,5}", ? (re)
1210 "time": "\s*[\d\.]{4,5}" (re)
1207 "time": "\s*[\d\.]{4,5}" (re)
1211 }, ? (re)
1208 }, ? (re)
1212 "test-skip.t": {
1209 "test-skip.t": {
1213 "csys": "\s*[\d\.]{4,5}", ? (re)
1210 "csys": "\s*[\d\.]{4,5}", ? (re)
1214 "cuser": "\s*[\d\.]{4,5}", ? (re)
1211 "cuser": "\s*[\d\.]{4,5}", ? (re)
1215 "diff": "", ? (re)
1212 "diff": "", ? (re)
1216 "end": "\s*[\d\.]{4,5}", ? (re)
1213 "end": "\s*[\d\.]{4,5}", ? (re)
1217 "result": "skip", ? (re)
1214 "result": "skip", ? (re)
1218 "start": "\s*[\d\.]{4,5}", ? (re)
1215 "start": "\s*[\d\.]{4,5}", ? (re)
1219 "time": "\s*[\d\.]{4,5}" (re)
1216 "time": "\s*[\d\.]{4,5}" (re)
1220 }, ? (re)
1217 }, ? (re)
1221 "test-success.t": [\{] (re)
1218 "test-success.t": [\{] (re)
1222 "csys": "\s*[\d\.]{4,5}", ? (re)
1219 "csys": "\s*[\d\.]{4,5}", ? (re)
1223 "cuser": "\s*[\d\.]{4,5}", ? (re)
1220 "cuser": "\s*[\d\.]{4,5}", ? (re)
1224 "diff": "", ? (re)
1221 "diff": "", ? (re)
1225 "end": "\s*[\d\.]{4,5}", ? (re)
1222 "end": "\s*[\d\.]{4,5}", ? (re)
1226 "result": "success", ? (re)
1223 "result": "success", ? (re)
1227 "start": "\s*[\d\.]{4,5}", ? (re)
1224 "start": "\s*[\d\.]{4,5}", ? (re)
1228 "time": "\s*[\d\.]{4,5}" (re)
1225 "time": "\s*[\d\.]{4,5}" (re)
1229 }
1226 }
1230 } (no-eol)
1227 } (no-eol)
1231 $ mv backup test-failure.t
1228 $ mv backup test-failure.t
1232
1229
1233 backslash on end of line with glob matching is handled properly
1230 backslash on end of line with glob matching is handled properly
1234
1231
1235 $ cat > test-glob-backslash.t << EOF
1232 $ cat > test-glob-backslash.t << EOF
1236 > $ echo 'foo bar \\'
1233 > $ echo 'foo bar \\'
1237 > foo * \ (glob)
1234 > foo * \ (glob)
1238 > EOF
1235 > EOF
1239
1236
1240 $ rt test-glob-backslash.t
1237 $ rt test-glob-backslash.t
1241 .
1238 .
1242 # Ran 1 tests, 0 skipped, 0 failed.
1239 # Ran 1 tests, 0 skipped, 0 failed.
1243
1240
1244 $ rm -f test-glob-backslash.t
1241 $ rm -f test-glob-backslash.t
1245
1242
1246 Test globbing of local IP addresses
1243 Test globbing of local IP addresses
1247 $ echo 172.16.18.1
1244 $ echo 172.16.18.1
1248 $LOCALIP (glob)
1245 $LOCALIP (glob)
1249 $ echo dead:beef::1
1246 $ echo dead:beef::1
1250 $LOCALIP (glob)
1247 $LOCALIP (glob)
1251
1248
1252 Test reusability for third party tools
1249 Test reusability for third party tools
1253 ======================================
1250 ======================================
1254
1251
1255 $ mkdir "$TESTTMP"/anothertests
1252 $ mkdir "$TESTTMP"/anothertests
1256 $ cd "$TESTTMP"/anothertests
1253 $ cd "$TESTTMP"/anothertests
1257
1254
1258 test that `run-tests.py` can execute hghave, even if it runs not in
1255 test that `run-tests.py` can execute hghave, even if it runs not in
1259 Mercurial source tree.
1256 Mercurial source tree.
1260
1257
1261 $ cat > test-hghave.t <<EOF
1258 $ cat > test-hghave.t <<EOF
1262 > #require true
1259 > #require true
1263 > $ echo foo
1260 > $ echo foo
1264 > foo
1261 > foo
1265 > EOF
1262 > EOF
1266 $ rt test-hghave.t
1263 $ rt test-hghave.t
1267 .
1264 .
1268 # Ran 1 tests, 0 skipped, 0 failed.
1265 # Ran 1 tests, 0 skipped, 0 failed.
1269
1266
1270 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1267 test that RUNTESTDIR refers the directory, in which `run-tests.py` now
1271 running is placed.
1268 running is placed.
1272
1269
1273 $ cat > test-runtestdir.t <<EOF
1270 $ cat > test-runtestdir.t <<EOF
1274 > - $TESTDIR, in which test-run-tests.t is placed
1271 > - $TESTDIR, in which test-run-tests.t is placed
1275 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1272 > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
1276 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1273 > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
1277 >
1274 >
1278 > #if windows
1275 > #if windows
1279 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1276 > $ test "\$TESTDIR" = "$TESTTMP\anothertests"
1280 > #else
1277 > #else
1281 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1278 > $ test "\$TESTDIR" = "$TESTTMP"/anothertests
1282 > #endif
1279 > #endif
1283 > If this prints a path, that means RUNTESTDIR didn't equal
1280 > If this prints a path, that means RUNTESTDIR didn't equal
1284 > TESTDIR as it should have.
1281 > TESTDIR as it should have.
1285 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1282 > $ test "\$RUNTESTDIR" = "$TESTDIR" || echo "\$RUNTESTDIR"
1286 > This should print the start of check-code. If this passes but the
1283 > This should print the start of check-code. If this passes but the
1287 > previous check failed, that means we found a copy of check-code at whatever
1284 > previous check failed, that means we found a copy of check-code at whatever
1288 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1285 > RUNTESTSDIR ended up containing, even though it doesn't match TESTDIR.
1289 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1286 > $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py | sed 's@.!.*python@#!USRBINENVPY@'
1290 > #!USRBINENVPY
1287 > #!USRBINENVPY
1291 > #
1288 > #
1292 > # check-code - a style and portability checker for Mercurial
1289 > # check-code - a style and portability checker for Mercurial
1293 > EOF
1290 > EOF
1294 $ rt test-runtestdir.t
1291 $ rt test-runtestdir.t
1295 .
1292 .
1296 # Ran 1 tests, 0 skipped, 0 failed.
1293 # Ran 1 tests, 0 skipped, 0 failed.
1297
1294
1298 #if execbit
1295 #if execbit
1299
1296
1300 test that TESTDIR is referred in PATH
1297 test that TESTDIR is referred in PATH
1301
1298
1302 $ cat > custom-command.sh <<EOF
1299 $ cat > custom-command.sh <<EOF
1303 > #!/bin/sh
1300 > #!/bin/sh
1304 > echo "hello world"
1301 > echo "hello world"
1305 > EOF
1302 > EOF
1306 $ chmod +x custom-command.sh
1303 $ chmod +x custom-command.sh
1307 $ cat > test-testdir-path.t <<EOF
1304 $ cat > test-testdir-path.t <<EOF
1308 > $ custom-command.sh
1305 > $ custom-command.sh
1309 > hello world
1306 > hello world
1310 > EOF
1307 > EOF
1311 $ rt test-testdir-path.t
1308 $ rt test-testdir-path.t
1312 .
1309 .
1313 # Ran 1 tests, 0 skipped, 0 failed.
1310 # Ran 1 tests, 0 skipped, 0 failed.
1314
1311
1315 #endif
1312 #endif
1316
1313
1317 test support for --allow-slow-tests
1314 test support for --allow-slow-tests
1318 $ cat > test-very-slow-test.t <<EOF
1315 $ cat > test-very-slow-test.t <<EOF
1319 > #require slow
1316 > #require slow
1320 > $ echo pass
1317 > $ echo pass
1321 > pass
1318 > pass
1322 > EOF
1319 > EOF
1323 $ rt test-very-slow-test.t
1320 $ rt test-very-slow-test.t
1324 s
1321 s
1325 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1322 Skipped test-very-slow-test.t: missing feature: allow slow tests (use --allow-slow-tests)
1326 # Ran 0 tests, 1 skipped, 0 failed.
1323 # Ran 0 tests, 1 skipped, 0 failed.
1327 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1324 $ rt $HGTEST_RUN_TESTS_PURE --allow-slow-tests test-very-slow-test.t
1328 .
1325 .
1329 # Ran 1 tests, 0 skipped, 0 failed.
1326 # Ran 1 tests, 0 skipped, 0 failed.
1330
1327
1331 support for running a test outside the current directory
1328 support for running a test outside the current directory
1332 $ mkdir nonlocal
1329 $ mkdir nonlocal
1333 $ cat > nonlocal/test-is-not-here.t << EOF
1330 $ cat > nonlocal/test-is-not-here.t << EOF
1334 > $ echo pass
1331 > $ echo pass
1335 > pass
1332 > pass
1336 > EOF
1333 > EOF
1337 $ rt nonlocal/test-is-not-here.t
1334 $ rt nonlocal/test-is-not-here.t
1338 .
1335 .
1339 # Ran 1 tests, 0 skipped, 0 failed.
1336 # Ran 1 tests, 0 skipped, 0 failed.
1340
1337
1341 support for automatically discovering test if arg is a folder
1338 support for automatically discovering test if arg is a folder
1342 $ mkdir tmp && cd tmp
1339 $ mkdir tmp && cd tmp
1343
1340
1344 $ cat > test-uno.t << EOF
1341 $ cat > test-uno.t << EOF
1345 > $ echo line
1342 > $ echo line
1346 > line
1343 > line
1347 > EOF
1344 > EOF
1348
1345
1349 $ cp test-uno.t test-dos.t
1346 $ cp test-uno.t test-dos.t
1350 $ cd ..
1347 $ cd ..
1351 $ cp -R tmp tmpp
1348 $ cp -R tmp tmpp
1352 $ cp tmp/test-uno.t test-solo.t
1349 $ cp tmp/test-uno.t test-solo.t
1353
1350
1354 $ rt tmp/ test-solo.t tmpp
1351 $ rt tmp/ test-solo.t tmpp
1355 .....
1352 .....
1356 # Ran 5 tests, 0 skipped, 0 failed.
1353 # Ran 5 tests, 0 skipped, 0 failed.
1357 $ rm -rf tmp tmpp
1354 $ rm -rf tmp tmpp
1358
1355
1359 support for running run-tests.py from another directory
1356 support for running run-tests.py from another directory
1360 $ mkdir tmp && cd tmp
1357 $ mkdir tmp && cd tmp
1361
1358
1362 $ cat > useful-file.sh << EOF
1359 $ cat > useful-file.sh << EOF
1363 > important command
1360 > important command
1364 > EOF
1361 > EOF
1365
1362
1366 $ cat > test-folder.t << EOF
1363 $ cat > test-folder.t << EOF
1367 > $ cat \$TESTDIR/useful-file.sh
1364 > $ cat \$TESTDIR/useful-file.sh
1368 > important command
1365 > important command
1369 > EOF
1366 > EOF
1370
1367
1371 $ cat > test-folder-fail.t << EOF
1368 $ cat > test-folder-fail.t << EOF
1372 > $ cat \$TESTDIR/useful-file.sh
1369 > $ cat \$TESTDIR/useful-file.sh
1373 > important commando
1370 > important commando
1374 > EOF
1371 > EOF
1375
1372
1376 $ cd ..
1373 $ cd ..
1377 $ rt tmp/test-*.t
1374 $ rt tmp/test-*.t
1378
1375
1379 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1376 --- $TESTTMP/anothertests/tmp/test-folder-fail.t
1380 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1377 +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
1381 @@ -1,2 +1,2 @@
1378 @@ -1,2 +1,2 @@
1382 $ cat $TESTDIR/useful-file.sh
1379 $ cat $TESTDIR/useful-file.sh
1383 - important commando
1380 - important commando
1384 + important command
1381 + important command
1385
1382
1386 ERROR: test-folder-fail.t output changed
1383 ERROR: test-folder-fail.t output changed
1387 !.
1384 !.
1388 Failed test-folder-fail.t: output changed
1385 Failed test-folder-fail.t: output changed
1389 # Ran 2 tests, 0 skipped, 1 failed.
1386 # Ran 2 tests, 0 skipped, 1 failed.
1390 python hash seed: * (glob)
1387 python hash seed: * (glob)
1391 [1]
1388 [1]
1392
1389
1393 support for bisecting failed tests automatically
1390 support for bisecting failed tests automatically
1394 $ hg init bisect
1391 $ hg init bisect
1395 $ cd bisect
1392 $ cd bisect
1396 $ cat >> test-bisect.t <<EOF
1393 $ cat >> test-bisect.t <<EOF
1397 > $ echo pass
1394 > $ echo pass
1398 > pass
1395 > pass
1399 > EOF
1396 > EOF
1400 $ hg add test-bisect.t
1397 $ hg add test-bisect.t
1401 $ hg ci -m 'good'
1398 $ hg ci -m 'good'
1402 $ cat >> test-bisect.t <<EOF
1399 $ cat >> test-bisect.t <<EOF
1403 > $ echo pass
1400 > $ echo pass
1404 > fail
1401 > fail
1405 > EOF
1402 > EOF
1406 $ hg ci -m 'bad'
1403 $ hg ci -m 'bad'
1407 $ rt --known-good-rev=0 test-bisect.t
1404 $ rt --known-good-rev=0 test-bisect.t
1408
1405
1409 --- $TESTTMP/anothertests/bisect/test-bisect.t
1406 --- $TESTTMP/anothertests/bisect/test-bisect.t
1410 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1407 +++ $TESTTMP/anothertests/bisect/test-bisect.t.err
1411 @@ -1,4 +1,4 @@
1408 @@ -1,4 +1,4 @@
1412 $ echo pass
1409 $ echo pass
1413 pass
1410 pass
1414 $ echo pass
1411 $ echo pass
1415 - fail
1412 - fail
1416 + pass
1413 + pass
1417
1414
1418 ERROR: test-bisect.t output changed
1415 ERROR: test-bisect.t output changed
1419 !
1416 !
1420 Failed test-bisect.t: output changed
1417 Failed test-bisect.t: output changed
1421 test-bisect.t broken by 72cbf122d116 (bad)
1418 test-bisect.t broken by 72cbf122d116 (bad)
1422 # Ran 1 tests, 0 skipped, 1 failed.
1419 # Ran 1 tests, 0 skipped, 1 failed.
1423 python hash seed: * (glob)
1420 python hash seed: * (glob)
1424 [1]
1421 [1]
1425
1422
1426 $ cd ..
1423 $ cd ..
1427
1424
1428 support bisecting a separate repo
1425 support bisecting a separate repo
1429
1426
1430 $ hg init bisect-dependent
1427 $ hg init bisect-dependent
1431 $ cd bisect-dependent
1428 $ cd bisect-dependent
1432 $ cat > test-bisect-dependent.t <<EOF
1429 $ cat > test-bisect-dependent.t <<EOF
1433 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1430 > $ tail -1 \$TESTDIR/../bisect/test-bisect.t
1434 > pass
1431 > pass
1435 > EOF
1432 > EOF
1436 $ hg commit -Am dependent test-bisect-dependent.t
1433 $ hg commit -Am dependent test-bisect-dependent.t
1437
1434
1438 $ rt --known-good-rev=0 test-bisect-dependent.t
1435 $ rt --known-good-rev=0 test-bisect-dependent.t
1439
1436
1440 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1437 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1441 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1438 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1442 @@ -1,2 +1,2 @@
1439 @@ -1,2 +1,2 @@
1443 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1440 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1444 - pass
1441 - pass
1445 + fail
1442 + fail
1446
1443
1447 ERROR: test-bisect-dependent.t output changed
1444 ERROR: test-bisect-dependent.t output changed
1448 !
1445 !
1449 Failed test-bisect-dependent.t: output changed
1446 Failed test-bisect-dependent.t: output changed
1450 Failed to identify failure point for test-bisect-dependent.t
1447 Failed to identify failure point for test-bisect-dependent.t
1451 # Ran 1 tests, 0 skipped, 1 failed.
1448 # Ran 1 tests, 0 skipped, 1 failed.
1452 python hash seed: * (glob)
1449 python hash seed: * (glob)
1453 [1]
1450 [1]
1454
1451
1455 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1452 $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
1456 usage: run-tests.py [options] [tests]
1453 usage: run-tests.py [options] [tests]
1457 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1454 run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
1458 [2]
1455 [2]
1459
1456
1460 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1457 $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
1461
1458
1462 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1459 --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
1463 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1460 +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
1464 @@ -1,2 +1,2 @@
1461 @@ -1,2 +1,2 @@
1465 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1462 $ tail -1 $TESTDIR/../bisect/test-bisect.t
1466 - pass
1463 - pass
1467 + fail
1464 + fail
1468
1465
1469 ERROR: test-bisect-dependent.t output changed
1466 ERROR: test-bisect-dependent.t output changed
1470 !
1467 !
1471 Failed test-bisect-dependent.t: output changed
1468 Failed test-bisect-dependent.t: output changed
1472 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1469 test-bisect-dependent.t broken by 72cbf122d116 (bad)
1473 # Ran 1 tests, 0 skipped, 1 failed.
1470 # Ran 1 tests, 0 skipped, 1 failed.
1474 python hash seed: * (glob)
1471 python hash seed: * (glob)
1475 [1]
1472 [1]
1476
1473
1477 $ cd ..
1474 $ cd ..
1478
1475
1479 Test a broken #if statement doesn't break run-tests threading.
1476 Test a broken #if statement doesn't break run-tests threading.
1480 ==============================================================
1477 ==============================================================
1481 $ mkdir broken
1478 $ mkdir broken
1482 $ cd broken
1479 $ cd broken
1483 $ cat > test-broken.t <<EOF
1480 $ cat > test-broken.t <<EOF
1484 > true
1481 > true
1485 > #if notarealhghavefeature
1482 > #if notarealhghavefeature
1486 > $ false
1483 > $ false
1487 > #endif
1484 > #endif
1488 > EOF
1485 > EOF
1489 $ for f in 1 2 3 4 ; do
1486 $ for f in 1 2 3 4 ; do
1490 > cat > test-works-$f.t <<EOF
1487 > cat > test-works-$f.t <<EOF
1491 > This is test case $f
1488 > This is test case $f
1492 > $ sleep 1
1489 > $ sleep 1
1493 > EOF
1490 > EOF
1494 > done
1491 > done
1495 $ rt -j 2
1492 $ rt -j 2
1496 ....
1493 ....
1497 # Ran 5 tests, 0 skipped, 0 failed.
1494 # Ran 5 tests, 0 skipped, 0 failed.
1498 skipped: unknown feature: notarealhghavefeature
1495 skipped: unknown feature: notarealhghavefeature
1499
1496
1500 $ cd ..
1497 $ cd ..
1501 $ rm -rf broken
1498 $ rm -rf broken
1502
1499
1503 Test cases in .t files
1500 Test cases in .t files
1504 ======================
1501 ======================
1505 $ mkdir cases
1502 $ mkdir cases
1506 $ cd cases
1503 $ cd cases
1507 $ cat > test-cases-abc.t <<'EOF'
1504 $ cat > test-cases-abc.t <<'EOF'
1508 > #testcases A B C
1505 > #testcases A B C
1509 > $ V=B
1506 > $ V=B
1510 > #if A
1507 > #if A
1511 > $ V=A
1508 > $ V=A
1512 > #endif
1509 > #endif
1513 > #if C
1510 > #if C
1514 > $ V=C
1511 > $ V=C
1515 > #endif
1512 > #endif
1516 > $ echo $V | sed 's/A/C/'
1513 > $ echo $V | sed 's/A/C/'
1517 > C
1514 > C
1518 > #if C
1515 > #if C
1519 > $ [ $V = C ]
1516 > $ [ $V = C ]
1520 > #endif
1517 > #endif
1521 > #if A
1518 > #if A
1522 > $ [ $V = C ]
1519 > $ [ $V = C ]
1523 > [1]
1520 > [1]
1524 > #endif
1521 > #endif
1525 > #if no-C
1522 > #if no-C
1526 > $ [ $V = C ]
1523 > $ [ $V = C ]
1527 > [1]
1524 > [1]
1528 > #endif
1525 > #endif
1529 > $ [ $V = D ]
1526 > $ [ $V = D ]
1530 > [1]
1527 > [1]
1531 > EOF
1528 > EOF
1532 $ rt
1529 $ rt
1533 .
1530 .
1534 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1531 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1535 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1532 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1536 @@ -7,7 +7,7 @@
1533 @@ -7,7 +7,7 @@
1537 $ V=C
1534 $ V=C
1538 #endif
1535 #endif
1539 $ echo $V | sed 's/A/C/'
1536 $ echo $V | sed 's/A/C/'
1540 - C
1537 - C
1541 + B
1538 + B
1542 #if C
1539 #if C
1543 $ [ $V = C ]
1540 $ [ $V = C ]
1544 #endif
1541 #endif
1545
1542
1546 ERROR: test-cases-abc.t#B output changed
1543 ERROR: test-cases-abc.t#B output changed
1547 !.
1544 !.
1548 Failed test-cases-abc.t#B: output changed
1545 Failed test-cases-abc.t#B: output changed
1549 # Ran 3 tests, 0 skipped, 1 failed.
1546 # Ran 3 tests, 0 skipped, 1 failed.
1550 python hash seed: * (glob)
1547 python hash seed: * (glob)
1551 [1]
1548 [1]
1552
1549
1553 --restart works
1550 --restart works
1554
1551
1555 $ rt --restart
1552 $ rt --restart
1556
1553
1557 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1554 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1558 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1555 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1559 @@ -7,7 +7,7 @@
1556 @@ -7,7 +7,7 @@
1560 $ V=C
1557 $ V=C
1561 #endif
1558 #endif
1562 $ echo $V | sed 's/A/C/'
1559 $ echo $V | sed 's/A/C/'
1563 - C
1560 - C
1564 + B
1561 + B
1565 #if C
1562 #if C
1566 $ [ $V = C ]
1563 $ [ $V = C ]
1567 #endif
1564 #endif
1568
1565
1569 ERROR: test-cases-abc.t#B output changed
1566 ERROR: test-cases-abc.t#B output changed
1570 !.
1567 !.
1571 Failed test-cases-abc.t#B: output changed
1568 Failed test-cases-abc.t#B: output changed
1572 # Ran 2 tests, 0 skipped, 1 failed.
1569 # Ran 2 tests, 0 skipped, 1 failed.
1573 python hash seed: * (glob)
1570 python hash seed: * (glob)
1574 [1]
1571 [1]
1575
1572
1576 --restart works with outputdir
1573 --restart works with outputdir
1577
1574
1578 $ mkdir output
1575 $ mkdir output
1579 $ mv test-cases-abc.t.B.err output
1576 $ mv test-cases-abc.t.B.err output
1580 $ rt --restart --outputdir output
1577 $ rt --restart --outputdir output
1581
1578
1582 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1579 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1583 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t.B.err
1580 +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t.B.err
1584 @@ -7,7 +7,7 @@
1581 @@ -7,7 +7,7 @@
1585 $ V=C
1582 $ V=C
1586 #endif
1583 #endif
1587 $ echo $V | sed 's/A/C/'
1584 $ echo $V | sed 's/A/C/'
1588 - C
1585 - C
1589 + B
1586 + B
1590 #if C
1587 #if C
1591 $ [ $V = C ]
1588 $ [ $V = C ]
1592 #endif
1589 #endif
1593
1590
1594 ERROR: test-cases-abc.t#B output changed
1591 ERROR: test-cases-abc.t#B output changed
1595 !.
1592 !.
1596 Failed test-cases-abc.t#B: output changed
1593 Failed test-cases-abc.t#B: output changed
1597 # Ran 2 tests, 0 skipped, 1 failed.
1594 # Ran 2 tests, 0 skipped, 1 failed.
1598 python hash seed: * (glob)
1595 python hash seed: * (glob)
1599 [1]
1596 [1]
1600
1597
1601 Test TESTCASE variable
1598 Test TESTCASE variable
1602
1599
1603 $ cat > test-cases-ab.t <<'EOF'
1600 $ cat > test-cases-ab.t <<'EOF'
1604 > $ dostuff() {
1601 > $ dostuff() {
1605 > > echo "In case $TESTCASE"
1602 > > echo "In case $TESTCASE"
1606 > > }
1603 > > }
1607 > #testcases A B
1604 > #testcases A B
1608 > #if A
1605 > #if A
1609 > $ dostuff
1606 > $ dostuff
1610 > In case A
1607 > In case A
1611 > #endif
1608 > #endif
1612 > #if B
1609 > #if B
1613 > $ dostuff
1610 > $ dostuff
1614 > In case B
1611 > In case B
1615 > #endif
1612 > #endif
1616 > EOF
1613 > EOF
1617 $ rt test-cases-ab.t
1614 $ rt test-cases-ab.t
1618 ..
1615 ..
1619 # Ran 2 tests, 0 skipped, 0 failed.
1616 # Ran 2 tests, 0 skipped, 0 failed.
1620
1617
1621 Support running a specific test case
1618 Support running a specific test case
1622
1619
1623 $ rt "test-cases-abc.t#B"
1620 $ rt "test-cases-abc.t#B"
1624
1621
1625 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1622 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1626 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1623 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1627 @@ -7,7 +7,7 @@
1624 @@ -7,7 +7,7 @@
1628 $ V=C
1625 $ V=C
1629 #endif
1626 #endif
1630 $ echo $V | sed 's/A/C/'
1627 $ echo $V | sed 's/A/C/'
1631 - C
1628 - C
1632 + B
1629 + B
1633 #if C
1630 #if C
1634 $ [ $V = C ]
1631 $ [ $V = C ]
1635 #endif
1632 #endif
1636
1633
1637 ERROR: test-cases-abc.t#B output changed
1634 ERROR: test-cases-abc.t#B output changed
1638 !
1635 !
1639 Failed test-cases-abc.t#B: output changed
1636 Failed test-cases-abc.t#B: output changed
1640 # Ran 1 tests, 0 skipped, 1 failed.
1637 # Ran 1 tests, 0 skipped, 1 failed.
1641 python hash seed: * (glob)
1638 python hash seed: * (glob)
1642 [1]
1639 [1]
1643
1640
1644 Support running multiple test cases in the same file
1641 Support running multiple test cases in the same file
1645
1642
1646 $ rt test-cases-abc.t#B test-cases-abc.t#C
1643 $ rt test-cases-abc.t#B test-cases-abc.t#C
1647
1644
1648 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1645 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1649 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1646 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1650 @@ -7,7 +7,7 @@
1647 @@ -7,7 +7,7 @@
1651 $ V=C
1648 $ V=C
1652 #endif
1649 #endif
1653 $ echo $V | sed 's/A/C/'
1650 $ echo $V | sed 's/A/C/'
1654 - C
1651 - C
1655 + B
1652 + B
1656 #if C
1653 #if C
1657 $ [ $V = C ]
1654 $ [ $V = C ]
1658 #endif
1655 #endif
1659
1656
1660 ERROR: test-cases-abc.t#B output changed
1657 ERROR: test-cases-abc.t#B output changed
1661 !.
1658 !.
1662 Failed test-cases-abc.t#B: output changed
1659 Failed test-cases-abc.t#B: output changed
1663 # Ran 2 tests, 0 skipped, 1 failed.
1660 # Ran 2 tests, 0 skipped, 1 failed.
1664 python hash seed: * (glob)
1661 python hash seed: * (glob)
1665 [1]
1662 [1]
1666
1663
1667 Support ignoring invalid test cases
1664 Support ignoring invalid test cases
1668
1665
1669 $ rt test-cases-abc.t#B test-cases-abc.t#D
1666 $ rt test-cases-abc.t#B test-cases-abc.t#D
1670
1667
1671 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1668 --- $TESTTMP/anothertests/cases/test-cases-abc.t
1672 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1669 +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
1673 @@ -7,7 +7,7 @@
1670 @@ -7,7 +7,7 @@
1674 $ V=C
1671 $ V=C
1675 #endif
1672 #endif
1676 $ echo $V | sed 's/A/C/'
1673 $ echo $V | sed 's/A/C/'
1677 - C
1674 - C
1678 + B
1675 + B
1679 #if C
1676 #if C
1680 $ [ $V = C ]
1677 $ [ $V = C ]
1681 #endif
1678 #endif
1682
1679
1683 ERROR: test-cases-abc.t#B output changed
1680 ERROR: test-cases-abc.t#B output changed
1684 !
1681 !
1685 Failed test-cases-abc.t#B: output changed
1682 Failed test-cases-abc.t#B: output changed
1686 # Ran 1 tests, 0 skipped, 1 failed.
1683 # Ran 1 tests, 0 skipped, 1 failed.
1687 python hash seed: * (glob)
1684 python hash seed: * (glob)
1688 [1]
1685 [1]
1689
1686
1690 Support running complex test cases names
1687 Support running complex test cases names
1691
1688
1692 $ cat > test-cases-advanced-cases.t <<'EOF'
1689 $ cat > test-cases-advanced-cases.t <<'EOF'
1693 > #testcases simple case-with-dashes casewith_-.chars
1690 > #testcases simple case-with-dashes casewith_-.chars
1694 > $ echo $TESTCASE
1691 > $ echo $TESTCASE
1695 > simple
1692 > simple
1696 > EOF
1693 > EOF
1697
1694
1698 $ cat test-cases-advanced-cases.t
1695 $ cat test-cases-advanced-cases.t
1699 #testcases simple case-with-dashes casewith_-.chars
1696 #testcases simple case-with-dashes casewith_-.chars
1700 $ echo $TESTCASE
1697 $ echo $TESTCASE
1701 simple
1698 simple
1702
1699
1703 $ rt test-cases-advanced-cases.t
1700 $ rt test-cases-advanced-cases.t
1704
1701
1705 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1702 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1706 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
1703 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
1707 @@ -1,3 +1,3 @@
1704 @@ -1,3 +1,3 @@
1708 #testcases simple case-with-dashes casewith_-.chars
1705 #testcases simple case-with-dashes casewith_-.chars
1709 $ echo $TESTCASE
1706 $ echo $TESTCASE
1710 - simple
1707 - simple
1711 + case-with-dashes
1708 + case-with-dashes
1712
1709
1713 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1710 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1714 !
1711 !
1715 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1712 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1716 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
1713 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
1717 @@ -1,3 +1,3 @@
1714 @@ -1,3 +1,3 @@
1718 #testcases simple case-with-dashes casewith_-.chars
1715 #testcases simple case-with-dashes casewith_-.chars
1719 $ echo $TESTCASE
1716 $ echo $TESTCASE
1720 - simple
1717 - simple
1721 + casewith_-.chars
1718 + casewith_-.chars
1722
1719
1723 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1720 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1724 !.
1721 !.
1725 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1722 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1726 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1723 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1727 # Ran 3 tests, 0 skipped, 2 failed.
1724 # Ran 3 tests, 0 skipped, 2 failed.
1728 python hash seed: * (glob)
1725 python hash seed: * (glob)
1729 [1]
1726 [1]
1730
1727
1731 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1728 $ rt "test-cases-advanced-cases.t#case-with-dashes"
1732
1729
1733 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1730 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1734 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
1731 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
1735 @@ -1,3 +1,3 @@
1732 @@ -1,3 +1,3 @@
1736 #testcases simple case-with-dashes casewith_-.chars
1733 #testcases simple case-with-dashes casewith_-.chars
1737 $ echo $TESTCASE
1734 $ echo $TESTCASE
1738 - simple
1735 - simple
1739 + case-with-dashes
1736 + case-with-dashes
1740
1737
1741 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1738 ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
1742 !
1739 !
1743 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1740 Failed test-cases-advanced-cases.t#case-with-dashes: output changed
1744 # Ran 1 tests, 0 skipped, 1 failed.
1741 # Ran 1 tests, 0 skipped, 1 failed.
1745 python hash seed: * (glob)
1742 python hash seed: * (glob)
1746 [1]
1743 [1]
1747
1744
1748 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1745 $ rt "test-cases-advanced-cases.t#casewith_-.chars"
1749
1746
1750 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1747 --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
1751 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
1748 +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
1752 @@ -1,3 +1,3 @@
1749 @@ -1,3 +1,3 @@
1753 #testcases simple case-with-dashes casewith_-.chars
1750 #testcases simple case-with-dashes casewith_-.chars
1754 $ echo $TESTCASE
1751 $ echo $TESTCASE
1755 - simple
1752 - simple
1756 + casewith_-.chars
1753 + casewith_-.chars
1757
1754
1758 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1755 ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
1759 !
1756 !
1760 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1757 Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
1761 # Ran 1 tests, 0 skipped, 1 failed.
1758 # Ran 1 tests, 0 skipped, 1 failed.
1762 python hash seed: * (glob)
1759 python hash seed: * (glob)
1763 [1]
1760 [1]
1764
1761
1765 Test automatic pattern replacement
1762 Test automatic pattern replacement
1766 ==================================
1763 ==================================
1767
1764
1768 $ cat << EOF >> common-pattern.py
1765 $ cat << EOF >> common-pattern.py
1769 > substitutions = [
1766 > substitutions = [
1770 > (br'foo-(.*)\\b',
1767 > (br'foo-(.*)\\b',
1771 > br'\$XXX=\\1\$'),
1768 > br'\$XXX=\\1\$'),
1772 > (br'bar\\n',
1769 > (br'bar\\n',
1773 > br'\$YYY$\\n'),
1770 > br'\$YYY$\\n'),
1774 > ]
1771 > ]
1775 > EOF
1772 > EOF
1776
1773
1777 $ cat << EOF >> test-substitution.t
1774 $ cat << EOF >> test-substitution.t
1778 > $ echo foo-12
1775 > $ echo foo-12
1779 > \$XXX=12$
1776 > \$XXX=12$
1780 > $ echo foo-42
1777 > $ echo foo-42
1781 > \$XXX=42$
1778 > \$XXX=42$
1782 > $ echo bar prior
1779 > $ echo bar prior
1783 > bar prior
1780 > bar prior
1784 > $ echo lastbar
1781 > $ echo lastbar
1785 > last\$YYY$
1782 > last\$YYY$
1786 > $ echo foo-bar foo-baz
1783 > $ echo foo-bar foo-baz
1787 > EOF
1784 > EOF
1788
1785
1789 $ rt test-substitution.t
1786 $ rt test-substitution.t
1790
1787
1791 --- $TESTTMP/anothertests/cases/test-substitution.t
1788 --- $TESTTMP/anothertests/cases/test-substitution.t
1792 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1789 +++ $TESTTMP/anothertests/cases/test-substitution.t.err
1793 @@ -7,3 +7,4 @@
1790 @@ -7,3 +7,4 @@
1794 $ echo lastbar
1791 $ echo lastbar
1795 last$YYY$
1792 last$YYY$
1796 $ echo foo-bar foo-baz
1793 $ echo foo-bar foo-baz
1797 + $XXX=bar foo-baz$
1794 + $XXX=bar foo-baz$
1798
1795
1799 ERROR: test-substitution.t output changed
1796 ERROR: test-substitution.t output changed
1800 !
1797 !
1801 Failed test-substitution.t: output changed
1798 Failed test-substitution.t: output changed
1802 # Ran 1 tests, 0 skipped, 1 failed.
1799 # Ran 1 tests, 0 skipped, 1 failed.
1803 python hash seed: * (glob)
1800 python hash seed: * (glob)
1804 [1]
1801 [1]
1805
1802
1806 --extra-config-opt works
1803 --extra-config-opt works
1807
1804
1808 $ cat << EOF >> test-config-opt.t
1805 $ cat << EOF >> test-config-opt.t
1809 > $ hg init test-config-opt
1806 > $ hg init test-config-opt
1810 > $ hg -R test-config-opt purge
1807 > $ hg -R test-config-opt purge
1811 > EOF
1808 > EOF
1812
1809
1813 $ rt --extra-config-opt extensions.purge= test-config-opt.t
1810 $ rt --extra-config-opt extensions.purge= test-config-opt.t
1814 .
1811 .
1815 # Ran 1 tests, 0 skipped, 0 failed.
1812 # Ran 1 tests, 0 skipped, 0 failed.
General Comments 0
You need to be logged in to leave comments. Login now