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