##// END OF EJS Templates
run-tests: use items() instead of iteritems()...
Augie Fackler -
r25261:27600986 default
parent child Browse files
Show More
@@ -1,2212 +1,2212
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 = true\n')
761 hgrc.write(b'all = 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 tdir = self._testdir.replace(b'\\', b'/')
905 tdir = self._testdir.replace(b'\\', b'/')
906 proc = Popen4(b'%s -c "%s/hghave %s"' %
906 proc = Popen4(b'%s -c "%s/hghave %s"' %
907 (self._shell, tdir, b' '.join(reqs)),
907 (self._shell, tdir, b' '.join(reqs)),
908 self._testtmp, 0, self._getenv())
908 self._testtmp, 0, self._getenv())
909 stdout, stderr = proc.communicate()
909 stdout, stderr = proc.communicate()
910 ret = proc.wait()
910 ret = proc.wait()
911 if wifexited(ret):
911 if wifexited(ret):
912 ret = os.WEXITSTATUS(ret)
912 ret = os.WEXITSTATUS(ret)
913 if ret == 2:
913 if ret == 2:
914 print(stdout)
914 print(stdout)
915 sys.exit(1)
915 sys.exit(1)
916
916
917 return ret == 0
917 return ret == 0
918
918
919 def _parsetest(self, lines):
919 def _parsetest(self, lines):
920 # We generate a shell script which outputs unique markers to line
920 # We generate a shell script which outputs unique markers to line
921 # up script results with our source. These markers include input
921 # up script results with our source. These markers include input
922 # line number and the last return code.
922 # line number and the last return code.
923 salt = b"SALT%d" % time.time()
923 salt = b"SALT%d" % time.time()
924 def addsalt(line, inpython):
924 def addsalt(line, inpython):
925 if inpython:
925 if inpython:
926 script.append(b'%s %d 0\n' % (salt, line))
926 script.append(b'%s %d 0\n' % (salt, line))
927 else:
927 else:
928 script.append(b'echo %s %d $?\n' % (salt, line))
928 script.append(b'echo %s %d $?\n' % (salt, line))
929
929
930 script = []
930 script = []
931
931
932 # After we run the shell script, we re-unify the script output
932 # After we run the shell script, we re-unify the script output
933 # with non-active parts of the source, with synchronization by our
933 # with non-active parts of the source, with synchronization by our
934 # SALT line number markers. The after table contains the non-active
934 # SALT line number markers. The after table contains the non-active
935 # components, ordered by line number.
935 # components, ordered by line number.
936 after = {}
936 after = {}
937
937
938 # Expected shell script output.
938 # Expected shell script output.
939 expected = {}
939 expected = {}
940
940
941 pos = prepos = -1
941 pos = prepos = -1
942
942
943 # True or False when in a true or false conditional section
943 # True or False when in a true or false conditional section
944 skipping = None
944 skipping = None
945
945
946 # We keep track of whether or not we're in a Python block so we
946 # We keep track of whether or not we're in a Python block so we
947 # can generate the surrounding doctest magic.
947 # can generate the surrounding doctest magic.
948 inpython = False
948 inpython = False
949
949
950 if self._debug:
950 if self._debug:
951 script.append(b'set -x\n')
951 script.append(b'set -x\n')
952 if os.getenv('MSYSTEM'):
952 if os.getenv('MSYSTEM'):
953 script.append(b'alias pwd="pwd -W"\n')
953 script.append(b'alias pwd="pwd -W"\n')
954
954
955 for n, l in enumerate(lines):
955 for n, l in enumerate(lines):
956 if not l.endswith(b'\n'):
956 if not l.endswith(b'\n'):
957 l += b'\n'
957 l += b'\n'
958 if l.startswith(b'#require'):
958 if l.startswith(b'#require'):
959 lsplit = l.split()
959 lsplit = l.split()
960 if len(lsplit) < 2 or lsplit[0] != b'#require':
960 if len(lsplit) < 2 or lsplit[0] != b'#require':
961 after.setdefault(pos, []).append(' !!! invalid #require\n')
961 after.setdefault(pos, []).append(' !!! invalid #require\n')
962 if not self._hghave(lsplit[1:]):
962 if not self._hghave(lsplit[1:]):
963 script = [b"exit 80\n"]
963 script = [b"exit 80\n"]
964 break
964 break
965 after.setdefault(pos, []).append(l)
965 after.setdefault(pos, []).append(l)
966 elif l.startswith(b'#if'):
966 elif l.startswith(b'#if'):
967 lsplit = l.split()
967 lsplit = l.split()
968 if len(lsplit) < 2 or lsplit[0] != b'#if':
968 if len(lsplit) < 2 or lsplit[0] != b'#if':
969 after.setdefault(pos, []).append(' !!! invalid #if\n')
969 after.setdefault(pos, []).append(' !!! invalid #if\n')
970 if skipping is not None:
970 if skipping is not None:
971 after.setdefault(pos, []).append(' !!! nested #if\n')
971 after.setdefault(pos, []).append(' !!! nested #if\n')
972 skipping = not self._hghave(lsplit[1:])
972 skipping = not self._hghave(lsplit[1:])
973 after.setdefault(pos, []).append(l)
973 after.setdefault(pos, []).append(l)
974 elif l.startswith(b'#else'):
974 elif l.startswith(b'#else'):
975 if skipping is None:
975 if skipping is None:
976 after.setdefault(pos, []).append(' !!! missing #if\n')
976 after.setdefault(pos, []).append(' !!! missing #if\n')
977 skipping = not skipping
977 skipping = not skipping
978 after.setdefault(pos, []).append(l)
978 after.setdefault(pos, []).append(l)
979 elif l.startswith(b'#endif'):
979 elif l.startswith(b'#endif'):
980 if skipping is None:
980 if skipping is None:
981 after.setdefault(pos, []).append(' !!! missing #if\n')
981 after.setdefault(pos, []).append(' !!! missing #if\n')
982 skipping = None
982 skipping = None
983 after.setdefault(pos, []).append(l)
983 after.setdefault(pos, []).append(l)
984 elif skipping:
984 elif skipping:
985 after.setdefault(pos, []).append(l)
985 after.setdefault(pos, []).append(l)
986 elif l.startswith(b' >>> '): # python inlines
986 elif l.startswith(b' >>> '): # python inlines
987 after.setdefault(pos, []).append(l)
987 after.setdefault(pos, []).append(l)
988 prepos = pos
988 prepos = pos
989 pos = n
989 pos = n
990 if not inpython:
990 if not inpython:
991 # We've just entered a Python block. Add the header.
991 # We've just entered a Python block. Add the header.
992 inpython = True
992 inpython = True
993 addsalt(prepos, False) # Make sure we report the exit code.
993 addsalt(prepos, False) # Make sure we report the exit code.
994 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
994 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
995 addsalt(n, True)
995 addsalt(n, True)
996 script.append(l[2:])
996 script.append(l[2:])
997 elif l.startswith(b' ... '): # python inlines
997 elif l.startswith(b' ... '): # python inlines
998 after.setdefault(prepos, []).append(l)
998 after.setdefault(prepos, []).append(l)
999 script.append(l[2:])
999 script.append(l[2:])
1000 elif l.startswith(b' $ '): # commands
1000 elif l.startswith(b' $ '): # commands
1001 if inpython:
1001 if inpython:
1002 script.append(b'EOF\n')
1002 script.append(b'EOF\n')
1003 inpython = False
1003 inpython = False
1004 after.setdefault(pos, []).append(l)
1004 after.setdefault(pos, []).append(l)
1005 prepos = pos
1005 prepos = pos
1006 pos = n
1006 pos = n
1007 addsalt(n, False)
1007 addsalt(n, False)
1008 cmd = l[4:].split()
1008 cmd = l[4:].split()
1009 if len(cmd) == 2 and cmd[0] == b'cd':
1009 if len(cmd) == 2 and cmd[0] == b'cd':
1010 l = b' $ cd %s || exit 1\n' % cmd[1]
1010 l = b' $ cd %s || exit 1\n' % cmd[1]
1011 script.append(l[4:])
1011 script.append(l[4:])
1012 elif l.startswith(b' > '): # continuations
1012 elif l.startswith(b' > '): # continuations
1013 after.setdefault(prepos, []).append(l)
1013 after.setdefault(prepos, []).append(l)
1014 script.append(l[4:])
1014 script.append(l[4:])
1015 elif l.startswith(b' '): # results
1015 elif l.startswith(b' '): # results
1016 # Queue up a list of expected results.
1016 # Queue up a list of expected results.
1017 expected.setdefault(pos, []).append(l[2:])
1017 expected.setdefault(pos, []).append(l[2:])
1018 else:
1018 else:
1019 if inpython:
1019 if inpython:
1020 script.append(b'EOF\n')
1020 script.append(b'EOF\n')
1021 inpython = False
1021 inpython = False
1022 # Non-command/result. Queue up for merged output.
1022 # Non-command/result. Queue up for merged output.
1023 after.setdefault(pos, []).append(l)
1023 after.setdefault(pos, []).append(l)
1024
1024
1025 if inpython:
1025 if inpython:
1026 script.append(b'EOF\n')
1026 script.append(b'EOF\n')
1027 if skipping is not None:
1027 if skipping is not None:
1028 after.setdefault(pos, []).append(' !!! missing #endif\n')
1028 after.setdefault(pos, []).append(' !!! missing #endif\n')
1029 addsalt(n + 1, False)
1029 addsalt(n + 1, False)
1030
1030
1031 return salt, script, after, expected
1031 return salt, script, after, expected
1032
1032
1033 def _processoutput(self, exitcode, output, salt, after, expected):
1033 def _processoutput(self, exitcode, output, salt, after, expected):
1034 # Merge the script output back into a unified test.
1034 # Merge the script output back into a unified test.
1035 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1035 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1036 if exitcode != 0:
1036 if exitcode != 0:
1037 warnonly = 3
1037 warnonly = 3
1038
1038
1039 pos = -1
1039 pos = -1
1040 postout = []
1040 postout = []
1041 for l in output:
1041 for l in output:
1042 lout, lcmd = l, None
1042 lout, lcmd = l, None
1043 if salt in l:
1043 if salt in l:
1044 lout, lcmd = l.split(salt, 1)
1044 lout, lcmd = l.split(salt, 1)
1045
1045
1046 if lout:
1046 if lout:
1047 if not lout.endswith(b'\n'):
1047 if not lout.endswith(b'\n'):
1048 lout += b' (no-eol)\n'
1048 lout += b' (no-eol)\n'
1049
1049
1050 # Find the expected output at the current position.
1050 # Find the expected output at the current position.
1051 el = None
1051 el = None
1052 if expected.get(pos, None):
1052 if expected.get(pos, None):
1053 el = expected[pos].pop(0)
1053 el = expected[pos].pop(0)
1054
1054
1055 r = TTest.linematch(el, lout)
1055 r = TTest.linematch(el, lout)
1056 if isinstance(r, str):
1056 if isinstance(r, str):
1057 if r == '+glob':
1057 if r == '+glob':
1058 lout = el[:-1] + ' (glob)\n'
1058 lout = el[:-1] + ' (glob)\n'
1059 r = '' # Warn only this line.
1059 r = '' # Warn only this line.
1060 elif r == '-glob':
1060 elif r == '-glob':
1061 lout = ''.join(el.rsplit(' (glob)', 1))
1061 lout = ''.join(el.rsplit(' (glob)', 1))
1062 r = '' # Warn only this line.
1062 r = '' # Warn only this line.
1063 else:
1063 else:
1064 log('\ninfo, unknown linematch result: %r\n' % r)
1064 log('\ninfo, unknown linematch result: %r\n' % r)
1065 r = False
1065 r = False
1066 if r:
1066 if r:
1067 postout.append(b' ' + el)
1067 postout.append(b' ' + el)
1068 else:
1068 else:
1069 if self.NEEDESCAPE(lout):
1069 if self.NEEDESCAPE(lout):
1070 lout = TTest._stringescape(b'%s (esc)\n' %
1070 lout = TTest._stringescape(b'%s (esc)\n' %
1071 lout.rstrip(b'\n'))
1071 lout.rstrip(b'\n'))
1072 postout.append(b' ' + lout) # Let diff deal with it.
1072 postout.append(b' ' + lout) # Let diff deal with it.
1073 if r != '': # If line failed.
1073 if r != '': # If line failed.
1074 warnonly = 3 # for sure not
1074 warnonly = 3 # for sure not
1075 elif warnonly == 1: # Is "not yet" and line is warn only.
1075 elif warnonly == 1: # Is "not yet" and line is warn only.
1076 warnonly = 2 # Yes do warn.
1076 warnonly = 2 # Yes do warn.
1077
1077
1078 if lcmd:
1078 if lcmd:
1079 # Add on last return code.
1079 # Add on last return code.
1080 ret = int(lcmd.split()[1])
1080 ret = int(lcmd.split()[1])
1081 if ret != 0:
1081 if ret != 0:
1082 postout.append(b' [%d]\n' % ret)
1082 postout.append(b' [%d]\n' % ret)
1083 if pos in after:
1083 if pos in after:
1084 # Merge in non-active test bits.
1084 # Merge in non-active test bits.
1085 postout += after.pop(pos)
1085 postout += after.pop(pos)
1086 pos = int(lcmd.split()[0])
1086 pos = int(lcmd.split()[0])
1087
1087
1088 if pos in after:
1088 if pos in after:
1089 postout += after.pop(pos)
1089 postout += after.pop(pos)
1090
1090
1091 if warnonly == 2:
1091 if warnonly == 2:
1092 exitcode = False # Set exitcode to warned.
1092 exitcode = False # Set exitcode to warned.
1093
1093
1094 return exitcode, postout
1094 return exitcode, postout
1095
1095
1096 @staticmethod
1096 @staticmethod
1097 def rematch(el, l):
1097 def rematch(el, l):
1098 try:
1098 try:
1099 # use \Z to ensure that the regex matches to the end of the string
1099 # use \Z to ensure that the regex matches to the end of the string
1100 if os.name == 'nt':
1100 if os.name == 'nt':
1101 return re.match(el + br'\r?\n\Z', l)
1101 return re.match(el + br'\r?\n\Z', l)
1102 return re.match(el + br'\n\Z', l)
1102 return re.match(el + br'\n\Z', l)
1103 except re.error:
1103 except re.error:
1104 # el is an invalid regex
1104 # el is an invalid regex
1105 return False
1105 return False
1106
1106
1107 @staticmethod
1107 @staticmethod
1108 def globmatch(el, l):
1108 def globmatch(el, l):
1109 # The only supported special characters are * and ? plus / which also
1109 # The only supported special characters are * and ? plus / which also
1110 # matches \ on windows. Escaping of these characters is supported.
1110 # matches \ on windows. Escaping of these characters is supported.
1111 if el + b'\n' == l:
1111 if el + b'\n' == l:
1112 if os.altsep:
1112 if os.altsep:
1113 # matching on "/" is not needed for this line
1113 # matching on "/" is not needed for this line
1114 for pat in checkcodeglobpats:
1114 for pat in checkcodeglobpats:
1115 if pat.match(el):
1115 if pat.match(el):
1116 return True
1116 return True
1117 return b'-glob'
1117 return b'-glob'
1118 return True
1118 return True
1119 i, n = 0, len(el)
1119 i, n = 0, len(el)
1120 res = b''
1120 res = b''
1121 while i < n:
1121 while i < n:
1122 c = el[i:i + 1]
1122 c = el[i:i + 1]
1123 i += 1
1123 i += 1
1124 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1124 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1125 res += el[i - 1:i + 1]
1125 res += el[i - 1:i + 1]
1126 i += 1
1126 i += 1
1127 elif c == b'*':
1127 elif c == b'*':
1128 res += b'.*'
1128 res += b'.*'
1129 elif c == b'?':
1129 elif c == b'?':
1130 res += b'.'
1130 res += b'.'
1131 elif c == b'/' and os.altsep:
1131 elif c == b'/' and os.altsep:
1132 res += b'[/\\\\]'
1132 res += b'[/\\\\]'
1133 else:
1133 else:
1134 res += re.escape(c)
1134 res += re.escape(c)
1135 return TTest.rematch(res, l)
1135 return TTest.rematch(res, l)
1136
1136
1137 @staticmethod
1137 @staticmethod
1138 def linematch(el, l):
1138 def linematch(el, l):
1139 if el == l: # perfect match (fast)
1139 if el == l: # perfect match (fast)
1140 return True
1140 return True
1141 if el:
1141 if el:
1142 if el.endswith(b" (esc)\n"):
1142 if el.endswith(b" (esc)\n"):
1143 if PYTHON3:
1143 if PYTHON3:
1144 el = el[:-7].decode('unicode_escape') + '\n'
1144 el = el[:-7].decode('unicode_escape') + '\n'
1145 el = el.encode('utf-8')
1145 el = el.encode('utf-8')
1146 else:
1146 else:
1147 el = el[:-7].decode('string-escape') + '\n'
1147 el = el[:-7].decode('string-escape') + '\n'
1148 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1148 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1149 return True
1149 return True
1150 if el.endswith(b" (re)\n"):
1150 if el.endswith(b" (re)\n"):
1151 return TTest.rematch(el[:-6], l)
1151 return TTest.rematch(el[:-6], l)
1152 if el.endswith(b" (glob)\n"):
1152 if el.endswith(b" (glob)\n"):
1153 # ignore '(glob)' added to l by 'replacements'
1153 # ignore '(glob)' added to l by 'replacements'
1154 if l.endswith(b" (glob)\n"):
1154 if l.endswith(b" (glob)\n"):
1155 l = l[:-8] + b"\n"
1155 l = l[:-8] + b"\n"
1156 return TTest.globmatch(el[:-8], l)
1156 return TTest.globmatch(el[:-8], l)
1157 if os.altsep and l.replace(b'\\', b'/') == el:
1157 if os.altsep and l.replace(b'\\', b'/') == el:
1158 return b'+glob'
1158 return b'+glob'
1159 return False
1159 return False
1160
1160
1161 @staticmethod
1161 @staticmethod
1162 def parsehghaveoutput(lines):
1162 def parsehghaveoutput(lines):
1163 '''Parse hghave log lines.
1163 '''Parse hghave log lines.
1164
1164
1165 Return tuple of lists (missing, failed):
1165 Return tuple of lists (missing, failed):
1166 * the missing/unknown features
1166 * the missing/unknown features
1167 * the features for which existence check failed'''
1167 * the features for which existence check failed'''
1168 missing = []
1168 missing = []
1169 failed = []
1169 failed = []
1170 for line in lines:
1170 for line in lines:
1171 if line.startswith(TTest.SKIPPED_PREFIX):
1171 if line.startswith(TTest.SKIPPED_PREFIX):
1172 line = line.splitlines()[0]
1172 line = line.splitlines()[0]
1173 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1173 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1174 elif line.startswith(TTest.FAILED_PREFIX):
1174 elif line.startswith(TTest.FAILED_PREFIX):
1175 line = line.splitlines()[0]
1175 line = line.splitlines()[0]
1176 failed.append(line[len(TTest.FAILED_PREFIX):])
1176 failed.append(line[len(TTest.FAILED_PREFIX):])
1177
1177
1178 return missing, failed
1178 return missing, failed
1179
1179
1180 @staticmethod
1180 @staticmethod
1181 def _escapef(m):
1181 def _escapef(m):
1182 return TTest.ESCAPEMAP[m.group(0)]
1182 return TTest.ESCAPEMAP[m.group(0)]
1183
1183
1184 @staticmethod
1184 @staticmethod
1185 def _stringescape(s):
1185 def _stringescape(s):
1186 return TTest.ESCAPESUB(TTest._escapef, s)
1186 return TTest.ESCAPESUB(TTest._escapef, s)
1187
1187
1188 iolock = threading.RLock()
1188 iolock = threading.RLock()
1189
1189
1190 class SkipTest(Exception):
1190 class SkipTest(Exception):
1191 """Raised to indicate that a test is to be skipped."""
1191 """Raised to indicate that a test is to be skipped."""
1192
1192
1193 class IgnoreTest(Exception):
1193 class IgnoreTest(Exception):
1194 """Raised to indicate that a test is to be ignored."""
1194 """Raised to indicate that a test is to be ignored."""
1195
1195
1196 class WarnTest(Exception):
1196 class WarnTest(Exception):
1197 """Raised to indicate that a test warned."""
1197 """Raised to indicate that a test warned."""
1198
1198
1199 class TestResult(unittest._TextTestResult):
1199 class TestResult(unittest._TextTestResult):
1200 """Holds results when executing via unittest."""
1200 """Holds results when executing via unittest."""
1201 # Don't worry too much about accessing the non-public _TextTestResult.
1201 # Don't worry too much about accessing the non-public _TextTestResult.
1202 # It is relatively common in Python testing tools.
1202 # It is relatively common in Python testing tools.
1203 def __init__(self, options, *args, **kwargs):
1203 def __init__(self, options, *args, **kwargs):
1204 super(TestResult, self).__init__(*args, **kwargs)
1204 super(TestResult, self).__init__(*args, **kwargs)
1205
1205
1206 self._options = options
1206 self._options = options
1207
1207
1208 # unittest.TestResult didn't have skipped until 2.7. We need to
1208 # unittest.TestResult didn't have skipped until 2.7. We need to
1209 # polyfill it.
1209 # polyfill it.
1210 self.skipped = []
1210 self.skipped = []
1211
1211
1212 # We have a custom "ignored" result that isn't present in any Python
1212 # We have a custom "ignored" result that isn't present in any Python
1213 # unittest implementation. It is very similar to skipped. It may make
1213 # unittest implementation. It is very similar to skipped. It may make
1214 # sense to map it into skip some day.
1214 # sense to map it into skip some day.
1215 self.ignored = []
1215 self.ignored = []
1216
1216
1217 # We have a custom "warned" result that isn't present in any Python
1217 # We have a custom "warned" result that isn't present in any Python
1218 # unittest implementation. It is very similar to failed. It may make
1218 # unittest implementation. It is very similar to failed. It may make
1219 # sense to map it into fail some day.
1219 # sense to map it into fail some day.
1220 self.warned = []
1220 self.warned = []
1221
1221
1222 self.times = []
1222 self.times = []
1223 self._firststarttime = None
1223 self._firststarttime = None
1224 # Data stored for the benefit of generating xunit reports.
1224 # Data stored for the benefit of generating xunit reports.
1225 self.successes = []
1225 self.successes = []
1226 self.faildata = {}
1226 self.faildata = {}
1227
1227
1228 def addFailure(self, test, reason):
1228 def addFailure(self, test, reason):
1229 self.failures.append((test, reason))
1229 self.failures.append((test, reason))
1230
1230
1231 if self._options.first:
1231 if self._options.first:
1232 self.stop()
1232 self.stop()
1233 else:
1233 else:
1234 with iolock:
1234 with iolock:
1235 if not self._options.nodiff:
1235 if not self._options.nodiff:
1236 self.stream.write('\nERROR: %s output changed\n' % test)
1236 self.stream.write('\nERROR: %s output changed\n' % test)
1237
1237
1238 self.stream.write('!')
1238 self.stream.write('!')
1239 self.stream.flush()
1239 self.stream.flush()
1240
1240
1241 def addSuccess(self, test):
1241 def addSuccess(self, test):
1242 with iolock:
1242 with iolock:
1243 super(TestResult, self).addSuccess(test)
1243 super(TestResult, self).addSuccess(test)
1244 self.successes.append(test)
1244 self.successes.append(test)
1245
1245
1246 def addError(self, test, err):
1246 def addError(self, test, err):
1247 super(TestResult, self).addError(test, err)
1247 super(TestResult, self).addError(test, err)
1248 if self._options.first:
1248 if self._options.first:
1249 self.stop()
1249 self.stop()
1250
1250
1251 # Polyfill.
1251 # Polyfill.
1252 def addSkip(self, test, reason):
1252 def addSkip(self, test, reason):
1253 self.skipped.append((test, reason))
1253 self.skipped.append((test, reason))
1254 with iolock:
1254 with iolock:
1255 if self.showAll:
1255 if self.showAll:
1256 self.stream.writeln('skipped %s' % reason)
1256 self.stream.writeln('skipped %s' % reason)
1257 else:
1257 else:
1258 self.stream.write('s')
1258 self.stream.write('s')
1259 self.stream.flush()
1259 self.stream.flush()
1260
1260
1261 def addIgnore(self, test, reason):
1261 def addIgnore(self, test, reason):
1262 self.ignored.append((test, reason))
1262 self.ignored.append((test, reason))
1263 with iolock:
1263 with iolock:
1264 if self.showAll:
1264 if self.showAll:
1265 self.stream.writeln('ignored %s' % reason)
1265 self.stream.writeln('ignored %s' % reason)
1266 else:
1266 else:
1267 if reason not in ('not retesting', "doesn't match keyword"):
1267 if reason not in ('not retesting', "doesn't match keyword"):
1268 self.stream.write('i')
1268 self.stream.write('i')
1269 else:
1269 else:
1270 self.testsRun += 1
1270 self.testsRun += 1
1271 self.stream.flush()
1271 self.stream.flush()
1272
1272
1273 def addWarn(self, test, reason):
1273 def addWarn(self, test, reason):
1274 self.warned.append((test, reason))
1274 self.warned.append((test, reason))
1275
1275
1276 if self._options.first:
1276 if self._options.first:
1277 self.stop()
1277 self.stop()
1278
1278
1279 with iolock:
1279 with iolock:
1280 if self.showAll:
1280 if self.showAll:
1281 self.stream.writeln('warned %s' % reason)
1281 self.stream.writeln('warned %s' % reason)
1282 else:
1282 else:
1283 self.stream.write('~')
1283 self.stream.write('~')
1284 self.stream.flush()
1284 self.stream.flush()
1285
1285
1286 def addOutputMismatch(self, test, ret, got, expected):
1286 def addOutputMismatch(self, test, ret, got, expected):
1287 """Record a mismatch in test output for a particular test."""
1287 """Record a mismatch in test output for a particular test."""
1288 if self.shouldStop:
1288 if self.shouldStop:
1289 # don't print, some other test case already failed and
1289 # don't print, some other test case already failed and
1290 # printed, we're just stale and probably failed due to our
1290 # printed, we're just stale and probably failed due to our
1291 # temp dir getting cleaned up.
1291 # temp dir getting cleaned up.
1292 return
1292 return
1293
1293
1294 accepted = False
1294 accepted = False
1295 failed = False
1295 failed = False
1296 lines = []
1296 lines = []
1297
1297
1298 with iolock:
1298 with iolock:
1299 if self._options.nodiff:
1299 if self._options.nodiff:
1300 pass
1300 pass
1301 elif self._options.view:
1301 elif self._options.view:
1302 v = self._options.view
1302 v = self._options.view
1303 if PYTHON3:
1303 if PYTHON3:
1304 v = _bytespath(v)
1304 v = _bytespath(v)
1305 os.system(b"%s %s %s" %
1305 os.system(b"%s %s %s" %
1306 (v, test.refpath, test.errpath))
1306 (v, test.refpath, test.errpath))
1307 else:
1307 else:
1308 servefail, lines = getdiff(expected, got,
1308 servefail, lines = getdiff(expected, got,
1309 test.refpath, test.errpath)
1309 test.refpath, test.errpath)
1310 if servefail:
1310 if servefail:
1311 self.addFailure(
1311 self.addFailure(
1312 test,
1312 test,
1313 'server failed to start (HGPORT=%s)' % test._startport)
1313 'server failed to start (HGPORT=%s)' % test._startport)
1314 else:
1314 else:
1315 self.stream.write('\n')
1315 self.stream.write('\n')
1316 for line in lines:
1316 for line in lines:
1317 if PYTHON3:
1317 if PYTHON3:
1318 self.stream.flush()
1318 self.stream.flush()
1319 self.stream.buffer.write(line)
1319 self.stream.buffer.write(line)
1320 self.stream.buffer.flush()
1320 self.stream.buffer.flush()
1321 else:
1321 else:
1322 self.stream.write(line)
1322 self.stream.write(line)
1323 self.stream.flush()
1323 self.stream.flush()
1324
1324
1325 # handle interactive prompt without releasing iolock
1325 # handle interactive prompt without releasing iolock
1326 if self._options.interactive:
1326 if self._options.interactive:
1327 self.stream.write('Accept this change? [n] ')
1327 self.stream.write('Accept this change? [n] ')
1328 answer = sys.stdin.readline().strip()
1328 answer = sys.stdin.readline().strip()
1329 if answer.lower() in ('y', 'yes'):
1329 if answer.lower() in ('y', 'yes'):
1330 if test.name.endswith('.t'):
1330 if test.name.endswith('.t'):
1331 rename(test.errpath, test.path)
1331 rename(test.errpath, test.path)
1332 else:
1332 else:
1333 rename(test.errpath, '%s.out' % test.path)
1333 rename(test.errpath, '%s.out' % test.path)
1334 accepted = True
1334 accepted = True
1335 if not accepted and not failed:
1335 if not accepted and not failed:
1336 self.faildata[test.name] = b''.join(lines)
1336 self.faildata[test.name] = b''.join(lines)
1337
1337
1338 return accepted
1338 return accepted
1339
1339
1340 def startTest(self, test):
1340 def startTest(self, test):
1341 super(TestResult, self).startTest(test)
1341 super(TestResult, self).startTest(test)
1342
1342
1343 # os.times module computes the user time and system time spent by
1343 # os.times module computes the user time and system time spent by
1344 # child's processes along with real elapsed time taken by a process.
1344 # child's processes along with real elapsed time taken by a process.
1345 # This module has one limitation. It can only work for Linux user
1345 # This module has one limitation. It can only work for Linux user
1346 # and not for Windows.
1346 # and not for Windows.
1347 test.started = os.times()
1347 test.started = os.times()
1348 if self._firststarttime is None: # thread racy but irrelevant
1348 if self._firststarttime is None: # thread racy but irrelevant
1349 self._firststarttime = test.started[4]
1349 self._firststarttime = test.started[4]
1350
1350
1351 def stopTest(self, test, interrupted=False):
1351 def stopTest(self, test, interrupted=False):
1352 super(TestResult, self).stopTest(test)
1352 super(TestResult, self).stopTest(test)
1353
1353
1354 test.stopped = os.times()
1354 test.stopped = os.times()
1355
1355
1356 starttime = test.started
1356 starttime = test.started
1357 endtime = test.stopped
1357 endtime = test.stopped
1358 origin = self._firststarttime
1358 origin = self._firststarttime
1359 self.times.append((test.name,
1359 self.times.append((test.name,
1360 endtime[2] - starttime[2], # user space CPU time
1360 endtime[2] - starttime[2], # user space CPU time
1361 endtime[3] - starttime[3], # sys space CPU time
1361 endtime[3] - starttime[3], # sys space CPU time
1362 endtime[4] - starttime[4], # real time
1362 endtime[4] - starttime[4], # real time
1363 starttime[4] - origin, # start date in run context
1363 starttime[4] - origin, # start date in run context
1364 endtime[4] - origin, # end date in run context
1364 endtime[4] - origin, # end date in run context
1365 ))
1365 ))
1366
1366
1367 if interrupted:
1367 if interrupted:
1368 with iolock:
1368 with iolock:
1369 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1369 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1370 test.name, self.times[-1][3]))
1370 test.name, self.times[-1][3]))
1371
1371
1372 class TestSuite(unittest.TestSuite):
1372 class TestSuite(unittest.TestSuite):
1373 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1373 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1374
1374
1375 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1375 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1376 retest=False, keywords=None, loop=False, runs_per_test=1,
1376 retest=False, keywords=None, loop=False, runs_per_test=1,
1377 loadtest=None,
1377 loadtest=None,
1378 *args, **kwargs):
1378 *args, **kwargs):
1379 """Create a new instance that can run tests with a configuration.
1379 """Create a new instance that can run tests with a configuration.
1380
1380
1381 testdir specifies the directory where tests are executed from. This
1381 testdir specifies the directory where tests are executed from. This
1382 is typically the ``tests`` directory from Mercurial's source
1382 is typically the ``tests`` directory from Mercurial's source
1383 repository.
1383 repository.
1384
1384
1385 jobs specifies the number of jobs to run concurrently. Each test
1385 jobs specifies the number of jobs to run concurrently. Each test
1386 executes on its own thread. Tests actually spawn new processes, so
1386 executes on its own thread. Tests actually spawn new processes, so
1387 state mutation should not be an issue.
1387 state mutation should not be an issue.
1388
1388
1389 whitelist and blacklist denote tests that have been whitelisted and
1389 whitelist and blacklist denote tests that have been whitelisted and
1390 blacklisted, respectively. These arguments don't belong in TestSuite.
1390 blacklisted, respectively. These arguments don't belong in TestSuite.
1391 Instead, whitelist and blacklist should be handled by the thing that
1391 Instead, whitelist and blacklist should be handled by the thing that
1392 populates the TestSuite with tests. They are present to preserve
1392 populates the TestSuite with tests. They are present to preserve
1393 backwards compatible behavior which reports skipped tests as part
1393 backwards compatible behavior which reports skipped tests as part
1394 of the results.
1394 of the results.
1395
1395
1396 retest denotes whether to retest failed tests. This arguably belongs
1396 retest denotes whether to retest failed tests. This arguably belongs
1397 outside of TestSuite.
1397 outside of TestSuite.
1398
1398
1399 keywords denotes key words that will be used to filter which tests
1399 keywords denotes key words that will be used to filter which tests
1400 to execute. This arguably belongs outside of TestSuite.
1400 to execute. This arguably belongs outside of TestSuite.
1401
1401
1402 loop denotes whether to loop over tests forever.
1402 loop denotes whether to loop over tests forever.
1403 """
1403 """
1404 super(TestSuite, self).__init__(*args, **kwargs)
1404 super(TestSuite, self).__init__(*args, **kwargs)
1405
1405
1406 self._jobs = jobs
1406 self._jobs = jobs
1407 self._whitelist = whitelist
1407 self._whitelist = whitelist
1408 self._blacklist = blacklist
1408 self._blacklist = blacklist
1409 self._retest = retest
1409 self._retest = retest
1410 self._keywords = keywords
1410 self._keywords = keywords
1411 self._loop = loop
1411 self._loop = loop
1412 self._runs_per_test = runs_per_test
1412 self._runs_per_test = runs_per_test
1413 self._loadtest = loadtest
1413 self._loadtest = loadtest
1414
1414
1415 def run(self, result):
1415 def run(self, result):
1416 # We have a number of filters that need to be applied. We do this
1416 # We have a number of filters that need to be applied. We do this
1417 # here instead of inside Test because it makes the running logic for
1417 # here instead of inside Test because it makes the running logic for
1418 # Test simpler.
1418 # Test simpler.
1419 tests = []
1419 tests = []
1420 num_tests = [0]
1420 num_tests = [0]
1421 for test in self._tests:
1421 for test in self._tests:
1422 def get():
1422 def get():
1423 num_tests[0] += 1
1423 num_tests[0] += 1
1424 if getattr(test, 'should_reload', False):
1424 if getattr(test, 'should_reload', False):
1425 return self._loadtest(test.bname, num_tests[0])
1425 return self._loadtest(test.bname, num_tests[0])
1426 return test
1426 return test
1427 if not os.path.exists(test.path):
1427 if not os.path.exists(test.path):
1428 result.addSkip(test, "Doesn't exist")
1428 result.addSkip(test, "Doesn't exist")
1429 continue
1429 continue
1430
1430
1431 if not (self._whitelist and test.name in self._whitelist):
1431 if not (self._whitelist and test.name in self._whitelist):
1432 if self._blacklist and test.bname in self._blacklist:
1432 if self._blacklist and test.bname in self._blacklist:
1433 result.addSkip(test, 'blacklisted')
1433 result.addSkip(test, 'blacklisted')
1434 continue
1434 continue
1435
1435
1436 if self._retest and not os.path.exists(test.errpath):
1436 if self._retest and not os.path.exists(test.errpath):
1437 result.addIgnore(test, 'not retesting')
1437 result.addIgnore(test, 'not retesting')
1438 continue
1438 continue
1439
1439
1440 if self._keywords:
1440 if self._keywords:
1441 f = open(test.path, 'rb')
1441 f = open(test.path, 'rb')
1442 t = f.read().lower() + test.bname.lower()
1442 t = f.read().lower() + test.bname.lower()
1443 f.close()
1443 f.close()
1444 ignored = False
1444 ignored = False
1445 for k in self._keywords.lower().split():
1445 for k in self._keywords.lower().split():
1446 if k not in t:
1446 if k not in t:
1447 result.addIgnore(test, "doesn't match keyword")
1447 result.addIgnore(test, "doesn't match keyword")
1448 ignored = True
1448 ignored = True
1449 break
1449 break
1450
1450
1451 if ignored:
1451 if ignored:
1452 continue
1452 continue
1453 for _ in xrange(self._runs_per_test):
1453 for _ in xrange(self._runs_per_test):
1454 tests.append(get())
1454 tests.append(get())
1455
1455
1456 runtests = list(tests)
1456 runtests = list(tests)
1457 done = queue.Queue()
1457 done = queue.Queue()
1458 running = 0
1458 running = 0
1459
1459
1460 def job(test, result):
1460 def job(test, result):
1461 try:
1461 try:
1462 test(result)
1462 test(result)
1463 done.put(None)
1463 done.put(None)
1464 except KeyboardInterrupt:
1464 except KeyboardInterrupt:
1465 pass
1465 pass
1466 except: # re-raises
1466 except: # re-raises
1467 done.put(('!', test, 'run-test raised an error, see traceback'))
1467 done.put(('!', test, 'run-test raised an error, see traceback'))
1468 raise
1468 raise
1469
1469
1470 stoppedearly = False
1470 stoppedearly = False
1471
1471
1472 try:
1472 try:
1473 while tests or running:
1473 while tests or running:
1474 if not done.empty() or running == self._jobs or not tests:
1474 if not done.empty() or running == self._jobs or not tests:
1475 try:
1475 try:
1476 done.get(True, 1)
1476 done.get(True, 1)
1477 running -= 1
1477 running -= 1
1478 if result and result.shouldStop:
1478 if result and result.shouldStop:
1479 stoppedearly = True
1479 stoppedearly = True
1480 break
1480 break
1481 except queue.Empty:
1481 except queue.Empty:
1482 continue
1482 continue
1483 if tests and not running == self._jobs:
1483 if tests and not running == self._jobs:
1484 test = tests.pop(0)
1484 test = tests.pop(0)
1485 if self._loop:
1485 if self._loop:
1486 if getattr(test, 'should_reload', False):
1486 if getattr(test, 'should_reload', False):
1487 num_tests[0] += 1
1487 num_tests[0] += 1
1488 tests.append(
1488 tests.append(
1489 self._loadtest(test.name, num_tests[0]))
1489 self._loadtest(test.name, num_tests[0]))
1490 else:
1490 else:
1491 tests.append(test)
1491 tests.append(test)
1492 t = threading.Thread(target=job, name=test.name,
1492 t = threading.Thread(target=job, name=test.name,
1493 args=(test, result))
1493 args=(test, result))
1494 t.start()
1494 t.start()
1495 running += 1
1495 running += 1
1496
1496
1497 # If we stop early we still need to wait on started tests to
1497 # If we stop early we still need to wait on started tests to
1498 # finish. Otherwise, there is a race between the test completing
1498 # finish. Otherwise, there is a race between the test completing
1499 # and the test's cleanup code running. This could result in the
1499 # and the test's cleanup code running. This could result in the
1500 # test reporting incorrect.
1500 # test reporting incorrect.
1501 if stoppedearly:
1501 if stoppedearly:
1502 while running:
1502 while running:
1503 try:
1503 try:
1504 done.get(True, 1)
1504 done.get(True, 1)
1505 running -= 1
1505 running -= 1
1506 except queue.Empty:
1506 except queue.Empty:
1507 continue
1507 continue
1508 except KeyboardInterrupt:
1508 except KeyboardInterrupt:
1509 for test in runtests:
1509 for test in runtests:
1510 test.abort()
1510 test.abort()
1511
1511
1512 return result
1512 return result
1513
1513
1514 class TextTestRunner(unittest.TextTestRunner):
1514 class TextTestRunner(unittest.TextTestRunner):
1515 """Custom unittest test runner that uses appropriate settings."""
1515 """Custom unittest test runner that uses appropriate settings."""
1516
1516
1517 def __init__(self, runner, *args, **kwargs):
1517 def __init__(self, runner, *args, **kwargs):
1518 super(TextTestRunner, self).__init__(*args, **kwargs)
1518 super(TextTestRunner, self).__init__(*args, **kwargs)
1519
1519
1520 self._runner = runner
1520 self._runner = runner
1521
1521
1522 def run(self, test):
1522 def run(self, test):
1523 result = TestResult(self._runner.options, self.stream,
1523 result = TestResult(self._runner.options, self.stream,
1524 self.descriptions, self.verbosity)
1524 self.descriptions, self.verbosity)
1525
1525
1526 test(result)
1526 test(result)
1527
1527
1528 failed = len(result.failures)
1528 failed = len(result.failures)
1529 warned = len(result.warned)
1529 warned = len(result.warned)
1530 skipped = len(result.skipped)
1530 skipped = len(result.skipped)
1531 ignored = len(result.ignored)
1531 ignored = len(result.ignored)
1532
1532
1533 with iolock:
1533 with iolock:
1534 self.stream.writeln('')
1534 self.stream.writeln('')
1535
1535
1536 if not self._runner.options.noskips:
1536 if not self._runner.options.noskips:
1537 for test, msg in result.skipped:
1537 for test, msg in result.skipped:
1538 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1538 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1539 for test, msg in result.warned:
1539 for test, msg in result.warned:
1540 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1540 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1541 for test, msg in result.failures:
1541 for test, msg in result.failures:
1542 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1542 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1543 for test, msg in result.errors:
1543 for test, msg in result.errors:
1544 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1544 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1545
1545
1546 if self._runner.options.xunit:
1546 if self._runner.options.xunit:
1547 xuf = open(self._runner.options.xunit, 'wb')
1547 xuf = open(self._runner.options.xunit, 'wb')
1548 try:
1548 try:
1549 timesd = dict((t[0], t[3]) for t in result.times)
1549 timesd = dict((t[0], t[3]) for t in result.times)
1550 doc = minidom.Document()
1550 doc = minidom.Document()
1551 s = doc.createElement('testsuite')
1551 s = doc.createElement('testsuite')
1552 s.setAttribute('name', 'run-tests')
1552 s.setAttribute('name', 'run-tests')
1553 s.setAttribute('tests', str(result.testsRun))
1553 s.setAttribute('tests', str(result.testsRun))
1554 s.setAttribute('errors', "0") # TODO
1554 s.setAttribute('errors', "0") # TODO
1555 s.setAttribute('failures', str(failed))
1555 s.setAttribute('failures', str(failed))
1556 s.setAttribute('skipped', str(skipped + ignored))
1556 s.setAttribute('skipped', str(skipped + ignored))
1557 doc.appendChild(s)
1557 doc.appendChild(s)
1558 for tc in result.successes:
1558 for tc in result.successes:
1559 t = doc.createElement('testcase')
1559 t = doc.createElement('testcase')
1560 t.setAttribute('name', tc.name)
1560 t.setAttribute('name', tc.name)
1561 t.setAttribute('time', '%.3f' % timesd[tc.name])
1561 t.setAttribute('time', '%.3f' % timesd[tc.name])
1562 s.appendChild(t)
1562 s.appendChild(t)
1563 for tc, err in sorted(result.faildata.items()):
1563 for tc, err in sorted(result.faildata.items()):
1564 t = doc.createElement('testcase')
1564 t = doc.createElement('testcase')
1565 t.setAttribute('name', tc)
1565 t.setAttribute('name', tc)
1566 t.setAttribute('time', '%.3f' % timesd[tc])
1566 t.setAttribute('time', '%.3f' % timesd[tc])
1567 # createCDATASection expects a unicode or it will
1567 # createCDATASection expects a unicode or it will
1568 # convert using default conversion rules, which will
1568 # convert using default conversion rules, which will
1569 # fail if string isn't ASCII.
1569 # fail if string isn't ASCII.
1570 err = cdatasafe(err).decode('utf-8', 'replace')
1570 err = cdatasafe(err).decode('utf-8', 'replace')
1571 cd = doc.createCDATASection(err)
1571 cd = doc.createCDATASection(err)
1572 t.appendChild(cd)
1572 t.appendChild(cd)
1573 s.appendChild(t)
1573 s.appendChild(t)
1574 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1574 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1575 finally:
1575 finally:
1576 xuf.close()
1576 xuf.close()
1577
1577
1578 if self._runner.options.json:
1578 if self._runner.options.json:
1579 if json is None:
1579 if json is None:
1580 raise ImportError("json module not installed")
1580 raise ImportError("json module not installed")
1581 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1581 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1582 fp = open(jsonpath, 'w')
1582 fp = open(jsonpath, 'w')
1583 try:
1583 try:
1584 timesd = {}
1584 timesd = {}
1585 for tdata in result.times:
1585 for tdata in result.times:
1586 test = tdata[0]
1586 test = tdata[0]
1587 timesd[test] = tdata[1:]
1587 timesd[test] = tdata[1:]
1588
1588
1589 outcome = {}
1589 outcome = {}
1590 groups = [('success', ((tc, None)
1590 groups = [('success', ((tc, None)
1591 for tc in result.successes)),
1591 for tc in result.successes)),
1592 ('failure', result.failures),
1592 ('failure', result.failures),
1593 ('skip', result.skipped)]
1593 ('skip', result.skipped)]
1594 for res, testcases in groups:
1594 for res, testcases in groups:
1595 for tc, __ in testcases:
1595 for tc, __ in testcases:
1596 tres = {'result': res,
1596 tres = {'result': res,
1597 'time': ('%0.3f' % timesd[tc.name][2]),
1597 'time': ('%0.3f' % timesd[tc.name][2]),
1598 'cuser': ('%0.3f' % timesd[tc.name][0]),
1598 'cuser': ('%0.3f' % timesd[tc.name][0]),
1599 'csys': ('%0.3f' % timesd[tc.name][1]),
1599 'csys': ('%0.3f' % timesd[tc.name][1]),
1600 'start': ('%0.3f' % timesd[tc.name][3]),
1600 'start': ('%0.3f' % timesd[tc.name][3]),
1601 'end': ('%0.3f' % timesd[tc.name][4])}
1601 'end': ('%0.3f' % timesd[tc.name][4])}
1602 outcome[tc.name] = tres
1602 outcome[tc.name] = tres
1603 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1603 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1604 fp.writelines(("testreport =", jsonout))
1604 fp.writelines(("testreport =", jsonout))
1605 finally:
1605 finally:
1606 fp.close()
1606 fp.close()
1607
1607
1608 self._runner._checkhglib('Tested')
1608 self._runner._checkhglib('Tested')
1609
1609
1610 self.stream.writeln(
1610 self.stream.writeln(
1611 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1611 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1612 % (result.testsRun,
1612 % (result.testsRun,
1613 skipped + ignored, warned, failed))
1613 skipped + ignored, warned, failed))
1614 if failed:
1614 if failed:
1615 self.stream.writeln('python hash seed: %s' %
1615 self.stream.writeln('python hash seed: %s' %
1616 os.environ['PYTHONHASHSEED'])
1616 os.environ['PYTHONHASHSEED'])
1617 if self._runner.options.time:
1617 if self._runner.options.time:
1618 self.printtimes(result.times)
1618 self.printtimes(result.times)
1619
1619
1620 return result
1620 return result
1621
1621
1622 def printtimes(self, times):
1622 def printtimes(self, times):
1623 # iolock held by run
1623 # iolock held by run
1624 self.stream.writeln('# Producing time report')
1624 self.stream.writeln('# Producing time report')
1625 times.sort(key=lambda t: (t[3]))
1625 times.sort(key=lambda t: (t[3]))
1626 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1626 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1627 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1627 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1628 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1628 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1629 for tdata in times:
1629 for tdata in times:
1630 test = tdata[0]
1630 test = tdata[0]
1631 cuser, csys, real, start, end = tdata[1:6]
1631 cuser, csys, real, start, end = tdata[1:6]
1632 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1632 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1633
1633
1634 class TestRunner(object):
1634 class TestRunner(object):
1635 """Holds context for executing tests.
1635 """Holds context for executing tests.
1636
1636
1637 Tests rely on a lot of state. This object holds it for them.
1637 Tests rely on a lot of state. This object holds it for them.
1638 """
1638 """
1639
1639
1640 # Programs required to run tests.
1640 # Programs required to run tests.
1641 REQUIREDTOOLS = [
1641 REQUIREDTOOLS = [
1642 os.path.basename(_bytespath(sys.executable)),
1642 os.path.basename(_bytespath(sys.executable)),
1643 b'diff',
1643 b'diff',
1644 b'grep',
1644 b'grep',
1645 b'unzip',
1645 b'unzip',
1646 b'gunzip',
1646 b'gunzip',
1647 b'bunzip2',
1647 b'bunzip2',
1648 b'sed',
1648 b'sed',
1649 ]
1649 ]
1650
1650
1651 # Maps file extensions to test class.
1651 # Maps file extensions to test class.
1652 TESTTYPES = [
1652 TESTTYPES = [
1653 (b'.py', PythonTest),
1653 (b'.py', PythonTest),
1654 (b'.t', TTest),
1654 (b'.t', TTest),
1655 ]
1655 ]
1656
1656
1657 def __init__(self):
1657 def __init__(self):
1658 self.options = None
1658 self.options = None
1659 self._hgroot = None
1659 self._hgroot = None
1660 self._testdir = None
1660 self._testdir = None
1661 self._hgtmp = None
1661 self._hgtmp = None
1662 self._installdir = None
1662 self._installdir = None
1663 self._bindir = None
1663 self._bindir = None
1664 self._tmpbinddir = None
1664 self._tmpbinddir = None
1665 self._pythondir = None
1665 self._pythondir = None
1666 self._coveragefile = None
1666 self._coveragefile = None
1667 self._createdfiles = []
1667 self._createdfiles = []
1668 self._hgpath = None
1668 self._hgpath = None
1669 self._portoffset = 0
1669 self._portoffset = 0
1670 self._ports = {}
1670 self._ports = {}
1671
1671
1672 def run(self, args, parser=None):
1672 def run(self, args, parser=None):
1673 """Run the test suite."""
1673 """Run the test suite."""
1674 oldmask = os.umask(0o22)
1674 oldmask = os.umask(0o22)
1675 try:
1675 try:
1676 parser = parser or getparser()
1676 parser = parser or getparser()
1677 options, args = parseargs(args, parser)
1677 options, args = parseargs(args, parser)
1678 # positional arguments are paths to test files to run, so
1678 # positional arguments are paths to test files to run, so
1679 # we make sure they're all bytestrings
1679 # we make sure they're all bytestrings
1680 args = [_bytespath(a) for a in args]
1680 args = [_bytespath(a) for a in args]
1681 self.options = options
1681 self.options = options
1682
1682
1683 self._checktools()
1683 self._checktools()
1684 tests = self.findtests(args)
1684 tests = self.findtests(args)
1685 if options.profile_runner:
1685 if options.profile_runner:
1686 import statprof
1686 import statprof
1687 statprof.start()
1687 statprof.start()
1688 result = self._run(tests)
1688 result = self._run(tests)
1689 if options.profile_runner:
1689 if options.profile_runner:
1690 statprof.stop()
1690 statprof.stop()
1691 statprof.display()
1691 statprof.display()
1692 return result
1692 return result
1693
1693
1694 finally:
1694 finally:
1695 os.umask(oldmask)
1695 os.umask(oldmask)
1696
1696
1697 def _run(self, tests):
1697 def _run(self, tests):
1698 if self.options.random:
1698 if self.options.random:
1699 random.shuffle(tests)
1699 random.shuffle(tests)
1700 else:
1700 else:
1701 # keywords for slow tests
1701 # keywords for slow tests
1702 slow = {b'svn': 10,
1702 slow = {b'svn': 10,
1703 b'gendoc': 10,
1703 b'gendoc': 10,
1704 b'check-code-hg': 100,
1704 b'check-code-hg': 100,
1705 }
1705 }
1706 def sortkey(f):
1706 def sortkey(f):
1707 # run largest tests first, as they tend to take the longest
1707 # run largest tests first, as they tend to take the longest
1708 try:
1708 try:
1709 val = -os.stat(f).st_size
1709 val = -os.stat(f).st_size
1710 except OSError as e:
1710 except OSError as e:
1711 if e.errno != errno.ENOENT:
1711 if e.errno != errno.ENOENT:
1712 raise
1712 raise
1713 return -1e9 # file does not exist, tell early
1713 return -1e9 # file does not exist, tell early
1714 for kw, mul in slow.iteritems():
1714 for kw, mul in slow.items():
1715 if kw in f:
1715 if kw in f:
1716 val *= mul
1716 val *= mul
1717 return val
1717 return val
1718 tests.sort(key=sortkey)
1718 tests.sort(key=sortkey)
1719
1719
1720 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1720 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1721 os, 'getcwdb', os.getcwd)()
1721 os, 'getcwdb', os.getcwd)()
1722
1722
1723 if 'PYTHONHASHSEED' not in os.environ:
1723 if 'PYTHONHASHSEED' not in os.environ:
1724 # use a random python hash seed all the time
1724 # use a random python hash seed all the time
1725 # we do the randomness ourself to know what seed is used
1725 # we do the randomness ourself to know what seed is used
1726 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1726 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1727
1727
1728 if self.options.tmpdir:
1728 if self.options.tmpdir:
1729 self.options.keep_tmpdir = True
1729 self.options.keep_tmpdir = True
1730 tmpdir = _bytespath(self.options.tmpdir)
1730 tmpdir = _bytespath(self.options.tmpdir)
1731 if os.path.exists(tmpdir):
1731 if os.path.exists(tmpdir):
1732 # Meaning of tmpdir has changed since 1.3: we used to create
1732 # Meaning of tmpdir has changed since 1.3: we used to create
1733 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1733 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1734 # tmpdir already exists.
1734 # tmpdir already exists.
1735 print("error: temp dir %r already exists" % tmpdir)
1735 print("error: temp dir %r already exists" % tmpdir)
1736 return 1
1736 return 1
1737
1737
1738 # Automatically removing tmpdir sounds convenient, but could
1738 # Automatically removing tmpdir sounds convenient, but could
1739 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1739 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1740 # or "--tmpdir=$HOME".
1740 # or "--tmpdir=$HOME".
1741 #vlog("# Removing temp dir", tmpdir)
1741 #vlog("# Removing temp dir", tmpdir)
1742 #shutil.rmtree(tmpdir)
1742 #shutil.rmtree(tmpdir)
1743 os.makedirs(tmpdir)
1743 os.makedirs(tmpdir)
1744 else:
1744 else:
1745 d = None
1745 d = None
1746 if os.name == 'nt':
1746 if os.name == 'nt':
1747 # without this, we get the default temp dir location, but
1747 # without this, we get the default temp dir location, but
1748 # in all lowercase, which causes troubles with paths (issue3490)
1748 # in all lowercase, which causes troubles with paths (issue3490)
1749 d = osenvironb.get(b'TMP', None)
1749 d = osenvironb.get(b'TMP', None)
1750 # FILE BUG: mkdtemp works only on unicode in Python 3
1750 # FILE BUG: mkdtemp works only on unicode in Python 3
1751 tmpdir = tempfile.mkdtemp('', 'hgtests.', d and _strpath(d))
1751 tmpdir = tempfile.mkdtemp('', 'hgtests.', d and _strpath(d))
1752 tmpdir = _bytespath(tmpdir)
1752 tmpdir = _bytespath(tmpdir)
1753
1753
1754 self._hgtmp = osenvironb[b'HGTMP'] = (
1754 self._hgtmp = osenvironb[b'HGTMP'] = (
1755 os.path.realpath(tmpdir))
1755 os.path.realpath(tmpdir))
1756
1756
1757 if self.options.with_hg:
1757 if self.options.with_hg:
1758 self._installdir = None
1758 self._installdir = None
1759 whg = self.options.with_hg
1759 whg = self.options.with_hg
1760 # If --with-hg is not specified, we have bytes already,
1760 # If --with-hg is not specified, we have bytes already,
1761 # but if it was specified in python3 we get a str, so we
1761 # but if it was specified in python3 we get a str, so we
1762 # have to encode it back into a bytes.
1762 # have to encode it back into a bytes.
1763 if PYTHON3:
1763 if PYTHON3:
1764 if not isinstance(whg, bytes):
1764 if not isinstance(whg, bytes):
1765 whg = _bytespath(whg)
1765 whg = _bytespath(whg)
1766 self._bindir = os.path.dirname(os.path.realpath(whg))
1766 self._bindir = os.path.dirname(os.path.realpath(whg))
1767 assert isinstance(self._bindir, bytes)
1767 assert isinstance(self._bindir, bytes)
1768 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1768 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1769 os.makedirs(self._tmpbindir)
1769 os.makedirs(self._tmpbindir)
1770
1770
1771 # This looks redundant with how Python initializes sys.path from
1771 # This looks redundant with how Python initializes sys.path from
1772 # the location of the script being executed. Needed because the
1772 # the location of the script being executed. Needed because the
1773 # "hg" specified by --with-hg is not the only Python script
1773 # "hg" specified by --with-hg is not the only Python script
1774 # executed in the test suite that needs to import 'mercurial'
1774 # executed in the test suite that needs to import 'mercurial'
1775 # ... which means it's not really redundant at all.
1775 # ... which means it's not really redundant at all.
1776 self._pythondir = self._bindir
1776 self._pythondir = self._bindir
1777 else:
1777 else:
1778 self._installdir = os.path.join(self._hgtmp, b"install")
1778 self._installdir = os.path.join(self._hgtmp, b"install")
1779 self._bindir = osenvironb[b"BINDIR"] = \
1779 self._bindir = osenvironb[b"BINDIR"] = \
1780 os.path.join(self._installdir, b"bin")
1780 os.path.join(self._installdir, b"bin")
1781 self._tmpbindir = self._bindir
1781 self._tmpbindir = self._bindir
1782 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1782 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1783
1783
1784 osenvironb[b"BINDIR"] = self._bindir
1784 osenvironb[b"BINDIR"] = self._bindir
1785 osenvironb[b"PYTHON"] = PYTHON
1785 osenvironb[b"PYTHON"] = PYTHON
1786
1786
1787 fileb = _bytespath(__file__)
1787 fileb = _bytespath(__file__)
1788 runtestdir = os.path.abspath(os.path.dirname(fileb))
1788 runtestdir = os.path.abspath(os.path.dirname(fileb))
1789 if PYTHON3:
1789 if PYTHON3:
1790 sepb = _bytespath(os.pathsep)
1790 sepb = _bytespath(os.pathsep)
1791 else:
1791 else:
1792 sepb = os.pathsep
1792 sepb = os.pathsep
1793 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1793 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1794 if os.path.islink(__file__):
1794 if os.path.islink(__file__):
1795 # test helper will likely be at the end of the symlink
1795 # test helper will likely be at the end of the symlink
1796 realfile = os.path.realpath(fileb)
1796 realfile = os.path.realpath(fileb)
1797 realdir = os.path.abspath(os.path.dirname(realfile))
1797 realdir = os.path.abspath(os.path.dirname(realfile))
1798 path.insert(2, realdir)
1798 path.insert(2, realdir)
1799 if self._tmpbindir != self._bindir:
1799 if self._tmpbindir != self._bindir:
1800 path = [self._tmpbindir] + path
1800 path = [self._tmpbindir] + path
1801 osenvironb[b"PATH"] = sepb.join(path)
1801 osenvironb[b"PATH"] = sepb.join(path)
1802
1802
1803 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1803 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1804 # can run .../tests/run-tests.py test-foo where test-foo
1804 # can run .../tests/run-tests.py test-foo where test-foo
1805 # adds an extension to HGRC. Also include run-test.py directory to
1805 # adds an extension to HGRC. Also include run-test.py directory to
1806 # import modules like heredoctest.
1806 # import modules like heredoctest.
1807 pypath = [self._pythondir, self._testdir, runtestdir]
1807 pypath = [self._pythondir, self._testdir, runtestdir]
1808 # We have to augment PYTHONPATH, rather than simply replacing
1808 # We have to augment PYTHONPATH, rather than simply replacing
1809 # it, in case external libraries are only available via current
1809 # it, in case external libraries are only available via current
1810 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1810 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1811 # are in /opt/subversion.)
1811 # are in /opt/subversion.)
1812 oldpypath = osenvironb.get(IMPL_PATH)
1812 oldpypath = osenvironb.get(IMPL_PATH)
1813 if oldpypath:
1813 if oldpypath:
1814 pypath.append(oldpypath)
1814 pypath.append(oldpypath)
1815 osenvironb[IMPL_PATH] = sepb.join(pypath)
1815 osenvironb[IMPL_PATH] = sepb.join(pypath)
1816
1816
1817 if self.options.pure:
1817 if self.options.pure:
1818 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1818 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1819
1819
1820 self._coveragefile = os.path.join(self._testdir, b'.coverage')
1820 self._coveragefile = os.path.join(self._testdir, b'.coverage')
1821
1821
1822 vlog("# Using TESTDIR", self._testdir)
1822 vlog("# Using TESTDIR", self._testdir)
1823 vlog("# Using HGTMP", self._hgtmp)
1823 vlog("# Using HGTMP", self._hgtmp)
1824 vlog("# Using PATH", os.environ["PATH"])
1824 vlog("# Using PATH", os.environ["PATH"])
1825 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
1825 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
1826
1826
1827 try:
1827 try:
1828 return self._runtests(tests) or 0
1828 return self._runtests(tests) or 0
1829 finally:
1829 finally:
1830 time.sleep(.1)
1830 time.sleep(.1)
1831 self._cleanup()
1831 self._cleanup()
1832
1832
1833 def findtests(self, args):
1833 def findtests(self, args):
1834 """Finds possible test files from arguments.
1834 """Finds possible test files from arguments.
1835
1835
1836 If you wish to inject custom tests into the test harness, this would
1836 If you wish to inject custom tests into the test harness, this would
1837 be a good function to monkeypatch or override in a derived class.
1837 be a good function to monkeypatch or override in a derived class.
1838 """
1838 """
1839 if not args:
1839 if not args:
1840 if self.options.changed:
1840 if self.options.changed:
1841 proc = Popen4('hg st --rev "%s" -man0 .' %
1841 proc = Popen4('hg st --rev "%s" -man0 .' %
1842 self.options.changed, None, 0)
1842 self.options.changed, None, 0)
1843 stdout, stderr = proc.communicate()
1843 stdout, stderr = proc.communicate()
1844 args = stdout.strip(b'\0').split(b'\0')
1844 args = stdout.strip(b'\0').split(b'\0')
1845 else:
1845 else:
1846 args = os.listdir(b'.')
1846 args = os.listdir(b'.')
1847
1847
1848 return [t for t in args
1848 return [t for t in args
1849 if os.path.basename(t).startswith(b'test-')
1849 if os.path.basename(t).startswith(b'test-')
1850 and (t.endswith(b'.py') or t.endswith(b'.t'))]
1850 and (t.endswith(b'.py') or t.endswith(b'.t'))]
1851
1851
1852 def _runtests(self, tests):
1852 def _runtests(self, tests):
1853 try:
1853 try:
1854 if self._installdir:
1854 if self._installdir:
1855 self._installhg()
1855 self._installhg()
1856 self._checkhglib("Testing")
1856 self._checkhglib("Testing")
1857 else:
1857 else:
1858 self._usecorrectpython()
1858 self._usecorrectpython()
1859
1859
1860 if self.options.restart:
1860 if self.options.restart:
1861 orig = list(tests)
1861 orig = list(tests)
1862 while tests:
1862 while tests:
1863 if os.path.exists(tests[0] + ".err"):
1863 if os.path.exists(tests[0] + ".err"):
1864 break
1864 break
1865 tests.pop(0)
1865 tests.pop(0)
1866 if not tests:
1866 if not tests:
1867 print("running all tests")
1867 print("running all tests")
1868 tests = orig
1868 tests = orig
1869
1869
1870 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1870 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1871
1871
1872 failed = False
1872 failed = False
1873 warned = False
1873 warned = False
1874 kws = self.options.keywords
1874 kws = self.options.keywords
1875 if kws is not None and PYTHON3:
1875 if kws is not None and PYTHON3:
1876 kws = kws.encode('utf-8')
1876 kws = kws.encode('utf-8')
1877
1877
1878 suite = TestSuite(self._testdir,
1878 suite = TestSuite(self._testdir,
1879 jobs=self.options.jobs,
1879 jobs=self.options.jobs,
1880 whitelist=self.options.whitelisted,
1880 whitelist=self.options.whitelisted,
1881 blacklist=self.options.blacklist,
1881 blacklist=self.options.blacklist,
1882 retest=self.options.retest,
1882 retest=self.options.retest,
1883 keywords=kws,
1883 keywords=kws,
1884 loop=self.options.loop,
1884 loop=self.options.loop,
1885 runs_per_test=self.options.runs_per_test,
1885 runs_per_test=self.options.runs_per_test,
1886 tests=tests, loadtest=self._gettest)
1886 tests=tests, loadtest=self._gettest)
1887 verbosity = 1
1887 verbosity = 1
1888 if self.options.verbose:
1888 if self.options.verbose:
1889 verbosity = 2
1889 verbosity = 2
1890 runner = TextTestRunner(self, verbosity=verbosity)
1890 runner = TextTestRunner(self, verbosity=verbosity)
1891 result = runner.run(suite)
1891 result = runner.run(suite)
1892
1892
1893 if result.failures:
1893 if result.failures:
1894 failed = True
1894 failed = True
1895 if result.warned:
1895 if result.warned:
1896 warned = True
1896 warned = True
1897
1897
1898 if self.options.anycoverage:
1898 if self.options.anycoverage:
1899 self._outputcoverage()
1899 self._outputcoverage()
1900 except KeyboardInterrupt:
1900 except KeyboardInterrupt:
1901 failed = True
1901 failed = True
1902 print("\ninterrupted!")
1902 print("\ninterrupted!")
1903
1903
1904 if failed:
1904 if failed:
1905 return 1
1905 return 1
1906 if warned:
1906 if warned:
1907 return 80
1907 return 80
1908
1908
1909 def _getport(self, count):
1909 def _getport(self, count):
1910 port = self._ports.get(count) # do we have a cached entry?
1910 port = self._ports.get(count) # do we have a cached entry?
1911 if port is None:
1911 if port is None:
1912 port = self.options.port + self._portoffset
1912 port = self.options.port + self._portoffset
1913 portneeded = 3
1913 portneeded = 3
1914 # above 100 tries we just give up and let test reports failure
1914 # above 100 tries we just give up and let test reports failure
1915 for tries in xrange(100):
1915 for tries in xrange(100):
1916 allfree = True
1916 allfree = True
1917 for idx in xrange(portneeded):
1917 for idx in xrange(portneeded):
1918 if not checkportisavailable(port + idx):
1918 if not checkportisavailable(port + idx):
1919 allfree = False
1919 allfree = False
1920 break
1920 break
1921 self._portoffset += portneeded
1921 self._portoffset += portneeded
1922 if allfree:
1922 if allfree:
1923 break
1923 break
1924 self._ports[count] = port
1924 self._ports[count] = port
1925 return port
1925 return port
1926
1926
1927 def _gettest(self, test, count):
1927 def _gettest(self, test, count):
1928 """Obtain a Test by looking at its filename.
1928 """Obtain a Test by looking at its filename.
1929
1929
1930 Returns a Test instance. The Test may not be runnable if it doesn't
1930 Returns a Test instance. The Test may not be runnable if it doesn't
1931 map to a known type.
1931 map to a known type.
1932 """
1932 """
1933 lctest = test.lower()
1933 lctest = test.lower()
1934 testcls = Test
1934 testcls = Test
1935
1935
1936 for ext, cls in self.TESTTYPES:
1936 for ext, cls in self.TESTTYPES:
1937 if lctest.endswith(ext):
1937 if lctest.endswith(ext):
1938 testcls = cls
1938 testcls = cls
1939 break
1939 break
1940
1940
1941 refpath = os.path.join(self._testdir, test)
1941 refpath = os.path.join(self._testdir, test)
1942 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
1942 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
1943
1943
1944 t = testcls(refpath, tmpdir,
1944 t = testcls(refpath, tmpdir,
1945 keeptmpdir=self.options.keep_tmpdir,
1945 keeptmpdir=self.options.keep_tmpdir,
1946 debug=self.options.debug,
1946 debug=self.options.debug,
1947 timeout=self.options.timeout,
1947 timeout=self.options.timeout,
1948 startport=self._getport(count),
1948 startport=self._getport(count),
1949 extraconfigopts=self.options.extra_config_opt,
1949 extraconfigopts=self.options.extra_config_opt,
1950 py3kwarnings=self.options.py3k_warnings,
1950 py3kwarnings=self.options.py3k_warnings,
1951 shell=self.options.shell)
1951 shell=self.options.shell)
1952 t.should_reload = True
1952 t.should_reload = True
1953 return t
1953 return t
1954
1954
1955 def _cleanup(self):
1955 def _cleanup(self):
1956 """Clean up state from this test invocation."""
1956 """Clean up state from this test invocation."""
1957
1957
1958 if self.options.keep_tmpdir:
1958 if self.options.keep_tmpdir:
1959 return
1959 return
1960
1960
1961 vlog("# Cleaning up HGTMP", self._hgtmp)
1961 vlog("# Cleaning up HGTMP", self._hgtmp)
1962 shutil.rmtree(self._hgtmp, True)
1962 shutil.rmtree(self._hgtmp, True)
1963 for f in self._createdfiles:
1963 for f in self._createdfiles:
1964 try:
1964 try:
1965 os.remove(f)
1965 os.remove(f)
1966 except OSError:
1966 except OSError:
1967 pass
1967 pass
1968
1968
1969 def _usecorrectpython(self):
1969 def _usecorrectpython(self):
1970 """Configure the environment to use the appropriate Python in tests."""
1970 """Configure the environment to use the appropriate Python in tests."""
1971 # Tests must use the same interpreter as us or bad things will happen.
1971 # Tests must use the same interpreter as us or bad things will happen.
1972 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
1972 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
1973 if getattr(os, 'symlink', None):
1973 if getattr(os, 'symlink', None):
1974 vlog("# Making python executable in test path a symlink to '%s'" %
1974 vlog("# Making python executable in test path a symlink to '%s'" %
1975 sys.executable)
1975 sys.executable)
1976 mypython = os.path.join(self._tmpbindir, pyexename)
1976 mypython = os.path.join(self._tmpbindir, pyexename)
1977 try:
1977 try:
1978 if os.readlink(mypython) == sys.executable:
1978 if os.readlink(mypython) == sys.executable:
1979 return
1979 return
1980 os.unlink(mypython)
1980 os.unlink(mypython)
1981 except OSError as err:
1981 except OSError as err:
1982 if err.errno != errno.ENOENT:
1982 if err.errno != errno.ENOENT:
1983 raise
1983 raise
1984 if self._findprogram(pyexename) != sys.executable:
1984 if self._findprogram(pyexename) != sys.executable:
1985 try:
1985 try:
1986 os.symlink(sys.executable, mypython)
1986 os.symlink(sys.executable, mypython)
1987 self._createdfiles.append(mypython)
1987 self._createdfiles.append(mypython)
1988 except OSError as err:
1988 except OSError as err:
1989 # child processes may race, which is harmless
1989 # child processes may race, which is harmless
1990 if err.errno != errno.EEXIST:
1990 if err.errno != errno.EEXIST:
1991 raise
1991 raise
1992 else:
1992 else:
1993 exedir, exename = os.path.split(sys.executable)
1993 exedir, exename = os.path.split(sys.executable)
1994 vlog("# Modifying search path to find %s as %s in '%s'" %
1994 vlog("# Modifying search path to find %s as %s in '%s'" %
1995 (exename, pyexename, exedir))
1995 (exename, pyexename, exedir))
1996 path = os.environ['PATH'].split(os.pathsep)
1996 path = os.environ['PATH'].split(os.pathsep)
1997 while exedir in path:
1997 while exedir in path:
1998 path.remove(exedir)
1998 path.remove(exedir)
1999 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1999 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2000 if not self._findprogram(pyexename):
2000 if not self._findprogram(pyexename):
2001 print("WARNING: Cannot find %s in search path" % pyexename)
2001 print("WARNING: Cannot find %s in search path" % pyexename)
2002
2002
2003 def _installhg(self):
2003 def _installhg(self):
2004 """Install hg into the test environment.
2004 """Install hg into the test environment.
2005
2005
2006 This will also configure hg with the appropriate testing settings.
2006 This will also configure hg with the appropriate testing settings.
2007 """
2007 """
2008 vlog("# Performing temporary installation of HG")
2008 vlog("# Performing temporary installation of HG")
2009 installerrs = os.path.join(b"tests", b"install.err")
2009 installerrs = os.path.join(b"tests", b"install.err")
2010 compiler = ''
2010 compiler = ''
2011 if self.options.compiler:
2011 if self.options.compiler:
2012 compiler = '--compiler ' + self.options.compiler
2012 compiler = '--compiler ' + self.options.compiler
2013 if self.options.pure:
2013 if self.options.pure:
2014 pure = b"--pure"
2014 pure = b"--pure"
2015 else:
2015 else:
2016 pure = b""
2016 pure = b""
2017 py3 = ''
2017 py3 = ''
2018
2018
2019 # Run installer in hg root
2019 # Run installer in hg root
2020 script = os.path.realpath(sys.argv[0])
2020 script = os.path.realpath(sys.argv[0])
2021 exe = sys.executable
2021 exe = sys.executable
2022 if PYTHON3:
2022 if PYTHON3:
2023 py3 = b'--c2to3'
2023 py3 = b'--c2to3'
2024 compiler = _bytespath(compiler)
2024 compiler = _bytespath(compiler)
2025 script = _bytespath(script)
2025 script = _bytespath(script)
2026 exe = _bytespath(exe)
2026 exe = _bytespath(exe)
2027 hgroot = os.path.dirname(os.path.dirname(script))
2027 hgroot = os.path.dirname(os.path.dirname(script))
2028 self._hgroot = hgroot
2028 self._hgroot = hgroot
2029 os.chdir(hgroot)
2029 os.chdir(hgroot)
2030 nohome = b'--home=""'
2030 nohome = b'--home=""'
2031 if os.name == 'nt':
2031 if os.name == 'nt':
2032 # The --home="" trick works only on OS where os.sep == '/'
2032 # The --home="" trick works only on OS where os.sep == '/'
2033 # because of a distutils convert_path() fast-path. Avoid it at
2033 # because of a distutils convert_path() fast-path. Avoid it at
2034 # least on Windows for now, deal with .pydistutils.cfg bugs
2034 # least on Windows for now, deal with .pydistutils.cfg bugs
2035 # when they happen.
2035 # when they happen.
2036 nohome = b''
2036 nohome = b''
2037 cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all'
2037 cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all'
2038 b' build %(compiler)s --build-base="%(base)s"'
2038 b' build %(compiler)s --build-base="%(base)s"'
2039 b' install --force --prefix="%(prefix)s"'
2039 b' install --force --prefix="%(prefix)s"'
2040 b' --install-lib="%(libdir)s"'
2040 b' --install-lib="%(libdir)s"'
2041 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2041 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2042 % {b'exe': exe, b'py3': py3, b'pure': pure,
2042 % {b'exe': exe, b'py3': py3, b'pure': pure,
2043 b'compiler': compiler,
2043 b'compiler': compiler,
2044 b'base': os.path.join(self._hgtmp, b"build"),
2044 b'base': os.path.join(self._hgtmp, b"build"),
2045 b'prefix': self._installdir, b'libdir': self._pythondir,
2045 b'prefix': self._installdir, b'libdir': self._pythondir,
2046 b'bindir': self._bindir,
2046 b'bindir': self._bindir,
2047 b'nohome': nohome, b'logfile': installerrs})
2047 b'nohome': nohome, b'logfile': installerrs})
2048
2048
2049 # setuptools requires install directories to exist.
2049 # setuptools requires install directories to exist.
2050 def makedirs(p):
2050 def makedirs(p):
2051 try:
2051 try:
2052 os.makedirs(p)
2052 os.makedirs(p)
2053 except OSError as e:
2053 except OSError as e:
2054 if e.errno != errno.EEXIST:
2054 if e.errno != errno.EEXIST:
2055 raise
2055 raise
2056 makedirs(self._pythondir)
2056 makedirs(self._pythondir)
2057 makedirs(self._bindir)
2057 makedirs(self._bindir)
2058
2058
2059 vlog("# Running", cmd)
2059 vlog("# Running", cmd)
2060 if os.system(cmd) == 0:
2060 if os.system(cmd) == 0:
2061 if not self.options.verbose:
2061 if not self.options.verbose:
2062 os.remove(installerrs)
2062 os.remove(installerrs)
2063 else:
2063 else:
2064 f = open(installerrs, 'rb')
2064 f = open(installerrs, 'rb')
2065 for line in f:
2065 for line in f:
2066 if PYTHON3:
2066 if PYTHON3:
2067 sys.stdout.buffer.write(line)
2067 sys.stdout.buffer.write(line)
2068 else:
2068 else:
2069 sys.stdout.write(line)
2069 sys.stdout.write(line)
2070 f.close()
2070 f.close()
2071 sys.exit(1)
2071 sys.exit(1)
2072 os.chdir(self._testdir)
2072 os.chdir(self._testdir)
2073
2073
2074 self._usecorrectpython()
2074 self._usecorrectpython()
2075
2075
2076 if self.options.py3k_warnings and not self.options.anycoverage:
2076 if self.options.py3k_warnings and not self.options.anycoverage:
2077 vlog("# Updating hg command to enable Py3k Warnings switch")
2077 vlog("# Updating hg command to enable Py3k Warnings switch")
2078 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2078 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2079 lines = [line.rstrip() for line in f]
2079 lines = [line.rstrip() for line in f]
2080 lines[0] += ' -3'
2080 lines[0] += ' -3'
2081 f.close()
2081 f.close()
2082 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2082 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2083 for line in lines:
2083 for line in lines:
2084 f.write(line + '\n')
2084 f.write(line + '\n')
2085 f.close()
2085 f.close()
2086
2086
2087 hgbat = os.path.join(self._bindir, b'hg.bat')
2087 hgbat = os.path.join(self._bindir, b'hg.bat')
2088 if os.path.isfile(hgbat):
2088 if os.path.isfile(hgbat):
2089 # hg.bat expects to be put in bin/scripts while run-tests.py
2089 # hg.bat expects to be put in bin/scripts while run-tests.py
2090 # installation layout put it in bin/ directly. Fix it
2090 # installation layout put it in bin/ directly. Fix it
2091 f = open(hgbat, 'rb')
2091 f = open(hgbat, 'rb')
2092 data = f.read()
2092 data = f.read()
2093 f.close()
2093 f.close()
2094 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2094 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2095 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2095 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2096 b'"%~dp0python" "%~dp0hg" %*')
2096 b'"%~dp0python" "%~dp0hg" %*')
2097 f = open(hgbat, 'wb')
2097 f = open(hgbat, 'wb')
2098 f.write(data)
2098 f.write(data)
2099 f.close()
2099 f.close()
2100 else:
2100 else:
2101 print('WARNING: cannot fix hg.bat reference to python.exe')
2101 print('WARNING: cannot fix hg.bat reference to python.exe')
2102
2102
2103 if self.options.anycoverage:
2103 if self.options.anycoverage:
2104 custom = os.path.join(self._testdir, 'sitecustomize.py')
2104 custom = os.path.join(self._testdir, 'sitecustomize.py')
2105 target = os.path.join(self._pythondir, 'sitecustomize.py')
2105 target = os.path.join(self._pythondir, 'sitecustomize.py')
2106 vlog('# Installing coverage trigger to %s' % target)
2106 vlog('# Installing coverage trigger to %s' % target)
2107 shutil.copyfile(custom, target)
2107 shutil.copyfile(custom, target)
2108 rc = os.path.join(self._testdir, '.coveragerc')
2108 rc = os.path.join(self._testdir, '.coveragerc')
2109 vlog('# Installing coverage rc to %s' % rc)
2109 vlog('# Installing coverage rc to %s' % rc)
2110 os.environ['COVERAGE_PROCESS_START'] = rc
2110 os.environ['COVERAGE_PROCESS_START'] = rc
2111 covdir = os.path.join(self._installdir, '..', 'coverage')
2111 covdir = os.path.join(self._installdir, '..', 'coverage')
2112 try:
2112 try:
2113 os.mkdir(covdir)
2113 os.mkdir(covdir)
2114 except OSError as e:
2114 except OSError as e:
2115 if e.errno != errno.EEXIST:
2115 if e.errno != errno.EEXIST:
2116 raise
2116 raise
2117
2117
2118 os.environ['COVERAGE_DIR'] = covdir
2118 os.environ['COVERAGE_DIR'] = covdir
2119
2119
2120 def _checkhglib(self, verb):
2120 def _checkhglib(self, verb):
2121 """Ensure that the 'mercurial' package imported by python is
2121 """Ensure that the 'mercurial' package imported by python is
2122 the one we expect it to be. If not, print a warning to stderr."""
2122 the one we expect it to be. If not, print a warning to stderr."""
2123 if ((self._bindir == self._pythondir) and
2123 if ((self._bindir == self._pythondir) and
2124 (self._bindir != self._tmpbindir)):
2124 (self._bindir != self._tmpbindir)):
2125 # The pythondir has been inferred from --with-hg flag.
2125 # The pythondir has been inferred from --with-hg flag.
2126 # We cannot expect anything sensible here.
2126 # We cannot expect anything sensible here.
2127 return
2127 return
2128 expecthg = os.path.join(self._pythondir, b'mercurial')
2128 expecthg = os.path.join(self._pythondir, b'mercurial')
2129 actualhg = self._gethgpath()
2129 actualhg = self._gethgpath()
2130 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2130 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2131 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2131 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2132 ' (expected %s)\n'
2132 ' (expected %s)\n'
2133 % (verb, actualhg, expecthg))
2133 % (verb, actualhg, expecthg))
2134 def _gethgpath(self):
2134 def _gethgpath(self):
2135 """Return the path to the mercurial package that is actually found by
2135 """Return the path to the mercurial package that is actually found by
2136 the current Python interpreter."""
2136 the current Python interpreter."""
2137 if self._hgpath is not None:
2137 if self._hgpath is not None:
2138 return self._hgpath
2138 return self._hgpath
2139
2139
2140 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2140 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2141 cmd = cmd % PYTHON
2141 cmd = cmd % PYTHON
2142 if PYTHON3:
2142 if PYTHON3:
2143 cmd = _strpath(cmd)
2143 cmd = _strpath(cmd)
2144 pipe = os.popen(cmd)
2144 pipe = os.popen(cmd)
2145 try:
2145 try:
2146 self._hgpath = _bytespath(pipe.read().strip())
2146 self._hgpath = _bytespath(pipe.read().strip())
2147 finally:
2147 finally:
2148 pipe.close()
2148 pipe.close()
2149
2149
2150 return self._hgpath
2150 return self._hgpath
2151
2151
2152 def _outputcoverage(self):
2152 def _outputcoverage(self):
2153 """Produce code coverage output."""
2153 """Produce code coverage output."""
2154 from coverage import coverage
2154 from coverage import coverage
2155
2155
2156 vlog('# Producing coverage report')
2156 vlog('# Producing coverage report')
2157 # chdir is the easiest way to get short, relative paths in the
2157 # chdir is the easiest way to get short, relative paths in the
2158 # output.
2158 # output.
2159 os.chdir(self._hgroot)
2159 os.chdir(self._hgroot)
2160 covdir = os.path.join(self._installdir, '..', 'coverage')
2160 covdir = os.path.join(self._installdir, '..', 'coverage')
2161 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2161 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2162
2162
2163 # Map install directory paths back to source directory.
2163 # Map install directory paths back to source directory.
2164 cov.config.paths['srcdir'] = ['.', self._pythondir]
2164 cov.config.paths['srcdir'] = ['.', self._pythondir]
2165
2165
2166 cov.combine()
2166 cov.combine()
2167
2167
2168 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2168 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2169 cov.report(ignore_errors=True, omit=omit)
2169 cov.report(ignore_errors=True, omit=omit)
2170
2170
2171 if self.options.htmlcov:
2171 if self.options.htmlcov:
2172 htmldir = os.path.join(self._testdir, 'htmlcov')
2172 htmldir = os.path.join(self._testdir, 'htmlcov')
2173 cov.html_report(directory=htmldir, omit=omit)
2173 cov.html_report(directory=htmldir, omit=omit)
2174 if self.options.annotate:
2174 if self.options.annotate:
2175 adir = os.path.join(self._testdir, 'annotated')
2175 adir = os.path.join(self._testdir, 'annotated')
2176 if not os.path.isdir(adir):
2176 if not os.path.isdir(adir):
2177 os.mkdir(adir)
2177 os.mkdir(adir)
2178 cov.annotate(directory=adir, omit=omit)
2178 cov.annotate(directory=adir, omit=omit)
2179
2179
2180 def _findprogram(self, program):
2180 def _findprogram(self, program):
2181 """Search PATH for a executable program"""
2181 """Search PATH for a executable program"""
2182 dpb = _bytespath(os.defpath)
2182 dpb = _bytespath(os.defpath)
2183 sepb = _bytespath(os.pathsep)
2183 sepb = _bytespath(os.pathsep)
2184 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2184 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2185 name = os.path.join(p, program)
2185 name = os.path.join(p, program)
2186 if os.name == 'nt' or os.access(name, os.X_OK):
2186 if os.name == 'nt' or os.access(name, os.X_OK):
2187 return name
2187 return name
2188 return None
2188 return None
2189
2189
2190 def _checktools(self):
2190 def _checktools(self):
2191 """Ensure tools required to run tests are present."""
2191 """Ensure tools required to run tests are present."""
2192 for p in self.REQUIREDTOOLS:
2192 for p in self.REQUIREDTOOLS:
2193 if os.name == 'nt' and not p.endswith('.exe'):
2193 if os.name == 'nt' and not p.endswith('.exe'):
2194 p += '.exe'
2194 p += '.exe'
2195 found = self._findprogram(p)
2195 found = self._findprogram(p)
2196 if found:
2196 if found:
2197 vlog("# Found prerequisite", p, "at", found)
2197 vlog("# Found prerequisite", p, "at", found)
2198 else:
2198 else:
2199 print("WARNING: Did not find prerequisite tool: %s " % p)
2199 print("WARNING: Did not find prerequisite tool: %s " % p)
2200
2200
2201 if __name__ == '__main__':
2201 if __name__ == '__main__':
2202 runner = TestRunner()
2202 runner = TestRunner()
2203
2203
2204 try:
2204 try:
2205 import msvcrt
2205 import msvcrt
2206 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2206 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2207 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2207 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2208 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2208 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2209 except ImportError:
2209 except ImportError:
2210 pass
2210 pass
2211
2211
2212 sys.exit(runner.run(sys.argv[1:]))
2212 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now