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