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