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