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