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