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