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