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