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