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