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