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