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