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