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