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