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