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