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