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