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