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