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