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