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