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