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