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