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