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