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