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