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