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