##// END OF EJS Templates
tests: Add `--rhg` and `--with-rhg=<path>` options for `run-tests.py`...
Simon Sapin -
r47426:fb236859 default
parent child Browse files
Show More
@@ -1,3823 +1,3878 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
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 argparse
48 import argparse
49 import collections
49 import collections
50 import contextlib
50 import contextlib
51 import difflib
51 import difflib
52 import distutils.version as version
52 import distutils.version as version
53 import errno
53 import errno
54 import json
54 import json
55 import multiprocessing
55 import multiprocessing
56 import os
56 import os
57 import platform
57 import platform
58 import random
58 import random
59 import re
59 import re
60 import shutil
60 import shutil
61 import signal
61 import signal
62 import socket
62 import socket
63 import subprocess
63 import subprocess
64 import sys
64 import sys
65 import sysconfig
65 import sysconfig
66 import tempfile
66 import tempfile
67 import threading
67 import threading
68 import time
68 import time
69 import unittest
69 import unittest
70 import uuid
70 import uuid
71 import xml.dom.minidom as minidom
71 import xml.dom.minidom as minidom
72
72
73 try:
73 try:
74 import Queue as queue
74 import Queue as queue
75 except ImportError:
75 except ImportError:
76 import queue
76 import queue
77
77
78 try:
78 try:
79 import shlex
79 import shlex
80
80
81 shellquote = shlex.quote
81 shellquote = shlex.quote
82 except (ImportError, AttributeError):
82 except (ImportError, AttributeError):
83 import pipes
83 import pipes
84
84
85 shellquote = pipes.quote
85 shellquote = pipes.quote
86
86
87 processlock = threading.Lock()
87 processlock = threading.Lock()
88
88
89 pygmentspresent = False
89 pygmentspresent = False
90 # ANSI color is unsupported prior to Windows 10
90 # ANSI color is unsupported prior to Windows 10
91 if os.name != 'nt':
91 if os.name != 'nt':
92 try: # is pygments installed
92 try: # is pygments installed
93 import pygments
93 import pygments
94 import pygments.lexers as lexers
94 import pygments.lexers as lexers
95 import pygments.lexer as lexer
95 import pygments.lexer as lexer
96 import pygments.formatters as formatters
96 import pygments.formatters as formatters
97 import pygments.token as token
97 import pygments.token as token
98 import pygments.style as style
98 import pygments.style as style
99
99
100 pygmentspresent = True
100 pygmentspresent = True
101 difflexer = lexers.DiffLexer()
101 difflexer = lexers.DiffLexer()
102 terminal256formatter = formatters.Terminal256Formatter()
102 terminal256formatter = formatters.Terminal256Formatter()
103 except ImportError:
103 except ImportError:
104 pass
104 pass
105
105
106 if pygmentspresent:
106 if pygmentspresent:
107
107
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 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
122 testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
123 tokens = {
123 tokens = {
124 'root': [
124 'root': [
125 (r'^Skipped', token.Generic.Skipped, 'skipped'),
125 (r'^Skipped', token.Generic.Skipped, 'skipped'),
126 (r'^Failed ', token.Generic.Failed, 'failed'),
126 (r'^Failed ', token.Generic.Failed, 'failed'),
127 (r'^ERROR: ', token.Generic.Failed, 'failed'),
127 (r'^ERROR: ', token.Generic.Failed, 'failed'),
128 ],
128 ],
129 'skipped': [
129 'skipped': [
130 (testpattern, token.Generic.SName),
130 (testpattern, token.Generic.SName),
131 (r':.*', token.Generic.Skipped),
131 (r':.*', token.Generic.Skipped),
132 ],
132 ],
133 'failed': [
133 'failed': [
134 (testpattern, token.Generic.FName),
134 (testpattern, token.Generic.FName),
135 (r'(:| ).*', token.Generic.Failed),
135 (r'(:| ).*', token.Generic.Failed),
136 ],
136 ],
137 }
137 }
138
138
139 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
139 runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
140 runnerlexer = TestRunnerLexer()
140 runnerlexer = TestRunnerLexer()
141
141
142 origenviron = os.environ.copy()
142 origenviron = os.environ.copy()
143
143
144 if sys.version_info > (3, 5, 0):
144 if sys.version_info > (3, 5, 0):
145 PYTHON3 = True
145 PYTHON3 = True
146 xrange = range # we use xrange in one place, and we'd rather not use range
146 xrange = range # we use xrange in one place, and we'd rather not use range
147
147
148 def _sys2bytes(p):
148 def _sys2bytes(p):
149 if p is None:
149 if p is None:
150 return p
150 return p
151 return p.encode('utf-8')
151 return p.encode('utf-8')
152
152
153 def _bytes2sys(p):
153 def _bytes2sys(p):
154 if p is None:
154 if p is None:
155 return p
155 return p
156 return p.decode('utf-8')
156 return p.decode('utf-8')
157
157
158 osenvironb = getattr(os, 'environb', None)
158 osenvironb = getattr(os, 'environb', None)
159 if osenvironb is None:
159 if osenvironb is None:
160 # Windows lacks os.environb, for instance. A proxy over the real thing
160 # Windows lacks os.environb, for instance. A proxy over the real thing
161 # instead of a copy allows the environment to be updated via bytes on
161 # instead of a copy allows the environment to be updated via bytes on
162 # all platforms.
162 # all platforms.
163 class environbytes(object):
163 class environbytes(object):
164 def __init__(self, strenv):
164 def __init__(self, strenv):
165 self.__len__ = strenv.__len__
165 self.__len__ = strenv.__len__
166 self.clear = strenv.clear
166 self.clear = strenv.clear
167 self._strenv = strenv
167 self._strenv = strenv
168
168
169 def __getitem__(self, k):
169 def __getitem__(self, k):
170 v = self._strenv.__getitem__(_bytes2sys(k))
170 v = self._strenv.__getitem__(_bytes2sys(k))
171 return _sys2bytes(v)
171 return _sys2bytes(v)
172
172
173 def __setitem__(self, k, v):
173 def __setitem__(self, k, v):
174 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
174 self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
175
175
176 def __delitem__(self, k):
176 def __delitem__(self, k):
177 self._strenv.__delitem__(_bytes2sys(k))
177 self._strenv.__delitem__(_bytes2sys(k))
178
178
179 def __contains__(self, k):
179 def __contains__(self, k):
180 return self._strenv.__contains__(_bytes2sys(k))
180 return self._strenv.__contains__(_bytes2sys(k))
181
181
182 def __iter__(self):
182 def __iter__(self):
183 return iter([_sys2bytes(k) for k in iter(self._strenv)])
183 return iter([_sys2bytes(k) for k in iter(self._strenv)])
184
184
185 def get(self, k, default=None):
185 def get(self, k, default=None):
186 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
186 v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
187 return _sys2bytes(v)
187 return _sys2bytes(v)
188
188
189 def pop(self, k, default=None):
189 def pop(self, k, default=None):
190 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
190 v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
191 return _sys2bytes(v)
191 return _sys2bytes(v)
192
192
193 osenvironb = environbytes(os.environ)
193 osenvironb = environbytes(os.environ)
194
194
195 getcwdb = getattr(os, 'getcwdb')
195 getcwdb = getattr(os, 'getcwdb')
196 if not getcwdb or os.name == 'nt':
196 if not getcwdb or os.name == 'nt':
197 getcwdb = lambda: _sys2bytes(os.getcwd())
197 getcwdb = lambda: _sys2bytes(os.getcwd())
198
198
199 elif sys.version_info >= (3, 0, 0):
199 elif sys.version_info >= (3, 0, 0):
200 print(
200 print(
201 '%s is only supported on Python 3.5+ and 2.7, not %s'
201 '%s is only supported on Python 3.5+ and 2.7, not %s'
202 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
202 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
203 )
203 )
204 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
204 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
205 else:
205 else:
206 PYTHON3 = False
206 PYTHON3 = False
207
207
208 # In python 2.x, path operations are generally done using
208 # In python 2.x, path operations are generally done using
209 # bytestrings by default, so we don't have to do any extra
209 # bytestrings by default, so we don't have to do any extra
210 # fiddling there. We define the wrapper functions anyway just to
210 # fiddling there. We define the wrapper functions anyway just to
211 # help keep code consistent between platforms.
211 # help keep code consistent between platforms.
212 def _sys2bytes(p):
212 def _sys2bytes(p):
213 return p
213 return p
214
214
215 _bytes2sys = _sys2bytes
215 _bytes2sys = _sys2bytes
216 osenvironb = os.environ
216 osenvironb = os.environ
217 getcwdb = os.getcwd
217 getcwdb = os.getcwd
218
218
219 # For Windows support
219 # For Windows support
220 wifexited = getattr(os, "WIFEXITED", lambda x: False)
220 wifexited = getattr(os, "WIFEXITED", lambda x: False)
221
221
222 # Whether to use IPv6
222 # Whether to use IPv6
223 def checksocketfamily(name, port=20058):
223 def checksocketfamily(name, port=20058):
224 """return true if we can listen on localhost using family=name
224 """return true if we can listen on localhost using family=name
225
225
226 name should be either 'AF_INET', or 'AF_INET6'.
226 name should be either 'AF_INET', or 'AF_INET6'.
227 port being used is okay - EADDRINUSE is considered as successful.
227 port being used is okay - EADDRINUSE is considered as successful.
228 """
228 """
229 family = getattr(socket, name, None)
229 family = getattr(socket, name, None)
230 if family is None:
230 if family is None:
231 return False
231 return False
232 try:
232 try:
233 s = socket.socket(family, socket.SOCK_STREAM)
233 s = socket.socket(family, socket.SOCK_STREAM)
234 s.bind(('localhost', port))
234 s.bind(('localhost', port))
235 s.close()
235 s.close()
236 return True
236 return True
237 except socket.error as exc:
237 except socket.error as exc:
238 if exc.errno == errno.EADDRINUSE:
238 if exc.errno == errno.EADDRINUSE:
239 return True
239 return True
240 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
240 elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
241 return False
241 return False
242 else:
242 else:
243 raise
243 raise
244 else:
244 else:
245 return False
245 return False
246
246
247
247
248 # useipv6 will be set by parseargs
248 # useipv6 will be set by parseargs
249 useipv6 = None
249 useipv6 = None
250
250
251
251
252 def checkportisavailable(port):
252 def checkportisavailable(port):
253 """return true if a port seems free to bind on localhost"""
253 """return true if a port seems free to bind on localhost"""
254 if useipv6:
254 if useipv6:
255 family = socket.AF_INET6
255 family = socket.AF_INET6
256 else:
256 else:
257 family = socket.AF_INET
257 family = socket.AF_INET
258 try:
258 try:
259 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
259 with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as s:
260 s.bind(('localhost', port))
260 s.bind(('localhost', port))
261 return True
261 return True
262 except socket.error as exc:
262 except socket.error as exc:
263 if os.name == 'nt' and exc.errno == errno.WSAEACCES:
263 if os.name == 'nt' and exc.errno == errno.WSAEACCES:
264 return False
264 return False
265 elif exc.errno not in (
265 elif exc.errno not in (
266 errno.EADDRINUSE,
266 errno.EADDRINUSE,
267 errno.EADDRNOTAVAIL,
267 errno.EADDRNOTAVAIL,
268 errno.EPROTONOSUPPORT,
268 errno.EPROTONOSUPPORT,
269 ):
269 ):
270 raise
270 raise
271 return False
271 return False
272
272
273
273
274 closefds = os.name == 'posix'
274 closefds = os.name == 'posix'
275
275
276
276
277 def Popen4(cmd, wd, timeout, env=None):
277 def Popen4(cmd, wd, timeout, env=None):
278 processlock.acquire()
278 processlock.acquire()
279 p = subprocess.Popen(
279 p = subprocess.Popen(
280 _bytes2sys(cmd),
280 _bytes2sys(cmd),
281 shell=True,
281 shell=True,
282 bufsize=-1,
282 bufsize=-1,
283 cwd=_bytes2sys(wd),
283 cwd=_bytes2sys(wd),
284 env=env,
284 env=env,
285 close_fds=closefds,
285 close_fds=closefds,
286 stdin=subprocess.PIPE,
286 stdin=subprocess.PIPE,
287 stdout=subprocess.PIPE,
287 stdout=subprocess.PIPE,
288 stderr=subprocess.STDOUT,
288 stderr=subprocess.STDOUT,
289 )
289 )
290 processlock.release()
290 processlock.release()
291
291
292 p.fromchild = p.stdout
292 p.fromchild = p.stdout
293 p.tochild = p.stdin
293 p.tochild = p.stdin
294 p.childerr = p.stderr
294 p.childerr = p.stderr
295
295
296 p.timeout = False
296 p.timeout = False
297 if timeout:
297 if timeout:
298
298
299 def t():
299 def t():
300 start = time.time()
300 start = time.time()
301 while time.time() - start < timeout and p.returncode is None:
301 while time.time() - start < timeout and p.returncode is None:
302 time.sleep(0.1)
302 time.sleep(0.1)
303 p.timeout = True
303 p.timeout = True
304 if p.returncode is None:
304 if p.returncode is None:
305 terminate(p)
305 terminate(p)
306
306
307 threading.Thread(target=t).start()
307 threading.Thread(target=t).start()
308
308
309 return p
309 return p
310
310
311
311
312 if sys.executable:
312 if sys.executable:
313 sysexecutable = sys.executable
313 sysexecutable = sys.executable
314 elif os.environ.get('PYTHONEXECUTABLE'):
314 elif os.environ.get('PYTHONEXECUTABLE'):
315 sysexecutable = os.environ['PYTHONEXECUTABLE']
315 sysexecutable = os.environ['PYTHONEXECUTABLE']
316 elif os.environ.get('PYTHON'):
316 elif os.environ.get('PYTHON'):
317 sysexecutable = os.environ['PYTHON']
317 sysexecutable = os.environ['PYTHON']
318 else:
318 else:
319 raise AssertionError('Could not find Python interpreter')
319 raise AssertionError('Could not find Python interpreter')
320
320
321 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
321 PYTHON = _sys2bytes(sysexecutable.replace('\\', '/'))
322 IMPL_PATH = b'PYTHONPATH'
322 IMPL_PATH = b'PYTHONPATH'
323 if 'java' in sys.platform:
323 if 'java' in sys.platform:
324 IMPL_PATH = b'JYTHONPATH'
324 IMPL_PATH = b'JYTHONPATH'
325
325
326 default_defaults = {
326 default_defaults = {
327 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
327 'jobs': ('HGTEST_JOBS', multiprocessing.cpu_count()),
328 'timeout': ('HGTEST_TIMEOUT', 180),
328 'timeout': ('HGTEST_TIMEOUT', 180),
329 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
329 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 1500),
330 'port': ('HGTEST_PORT', 20059),
330 'port': ('HGTEST_PORT', 20059),
331 'shell': ('HGTEST_SHELL', 'sh'),
331 'shell': ('HGTEST_SHELL', 'sh'),
332 }
332 }
333
333
334 defaults = default_defaults.copy()
334 defaults = default_defaults.copy()
335
335
336
336
337 def canonpath(path):
337 def canonpath(path):
338 return os.path.realpath(os.path.expanduser(path))
338 return os.path.realpath(os.path.expanduser(path))
339
339
340
340
341 def parselistfiles(files, listtype, warn=True):
341 def parselistfiles(files, listtype, warn=True):
342 entries = dict()
342 entries = dict()
343 for filename in files:
343 for filename in files:
344 try:
344 try:
345 path = os.path.expanduser(os.path.expandvars(filename))
345 path = os.path.expanduser(os.path.expandvars(filename))
346 f = open(path, "rb")
346 f = open(path, "rb")
347 except IOError as err:
347 except IOError as err:
348 if err.errno != errno.ENOENT:
348 if err.errno != errno.ENOENT:
349 raise
349 raise
350 if warn:
350 if warn:
351 print("warning: no such %s file: %s" % (listtype, filename))
351 print("warning: no such %s file: %s" % (listtype, filename))
352 continue
352 continue
353
353
354 for line in f.readlines():
354 for line in f.readlines():
355 line = line.split(b'#', 1)[0].strip()
355 line = line.split(b'#', 1)[0].strip()
356 if line:
356 if line:
357 entries[line] = filename
357 entries[line] = filename
358
358
359 f.close()
359 f.close()
360 return entries
360 return entries
361
361
362
362
363 def parsettestcases(path):
363 def parsettestcases(path):
364 """read a .t test file, return a set of test case names
364 """read a .t test file, return a set of test case names
365
365
366 If path does not exist, return an empty set.
366 If path does not exist, return an empty set.
367 """
367 """
368 cases = []
368 cases = []
369 try:
369 try:
370 with open(path, 'rb') as f:
370 with open(path, 'rb') as f:
371 for l in f:
371 for l in f:
372 if l.startswith(b'#testcases '):
372 if l.startswith(b'#testcases '):
373 cases.append(sorted(l[11:].split()))
373 cases.append(sorted(l[11:].split()))
374 except IOError as ex:
374 except IOError as ex:
375 if ex.errno != errno.ENOENT:
375 if ex.errno != errno.ENOENT:
376 raise
376 raise
377 return cases
377 return cases
378
378
379
379
380 def getparser():
380 def getparser():
381 """Obtain the OptionParser used by the CLI."""
381 """Obtain the OptionParser used by the CLI."""
382 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
382 parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
383
383
384 selection = parser.add_argument_group('Test Selection')
384 selection = parser.add_argument_group('Test Selection')
385 selection.add_argument(
385 selection.add_argument(
386 '--allow-slow-tests',
386 '--allow-slow-tests',
387 action='store_true',
387 action='store_true',
388 help='allow extremely slow tests',
388 help='allow extremely slow tests',
389 )
389 )
390 selection.add_argument(
390 selection.add_argument(
391 "--blacklist",
391 "--blacklist",
392 action="append",
392 action="append",
393 help="skip tests listed in the specified blacklist file",
393 help="skip tests listed in the specified blacklist file",
394 )
394 )
395 selection.add_argument(
395 selection.add_argument(
396 "--changed",
396 "--changed",
397 help="run tests that are changed in parent rev or working directory",
397 help="run tests that are changed in parent rev or working directory",
398 )
398 )
399 selection.add_argument(
399 selection.add_argument(
400 "-k", "--keywords", help="run tests matching keywords"
400 "-k", "--keywords", help="run tests matching keywords"
401 )
401 )
402 selection.add_argument(
402 selection.add_argument(
403 "-r", "--retest", action="store_true", help="retest failed tests"
403 "-r", "--retest", action="store_true", help="retest failed tests"
404 )
404 )
405 selection.add_argument(
405 selection.add_argument(
406 "--test-list",
406 "--test-list",
407 action="append",
407 action="append",
408 help="read tests to run from the specified file",
408 help="read tests to run from the specified file",
409 )
409 )
410 selection.add_argument(
410 selection.add_argument(
411 "--whitelist",
411 "--whitelist",
412 action="append",
412 action="append",
413 help="always run tests listed in the specified whitelist file",
413 help="always run tests listed in the specified whitelist file",
414 )
414 )
415 selection.add_argument(
415 selection.add_argument(
416 'tests', metavar='TESTS', nargs='*', help='Tests to run'
416 'tests', metavar='TESTS', nargs='*', help='Tests to run'
417 )
417 )
418
418
419 harness = parser.add_argument_group('Test Harness Behavior')
419 harness = parser.add_argument_group('Test Harness Behavior')
420 harness.add_argument(
420 harness.add_argument(
421 '--bisect-repo',
421 '--bisect-repo',
422 metavar='bisect_repo',
422 metavar='bisect_repo',
423 help=(
423 help=(
424 "Path of a repo to bisect. Use together with " "--known-good-rev"
424 "Path of a repo to bisect. Use together with " "--known-good-rev"
425 ),
425 ),
426 )
426 )
427 harness.add_argument(
427 harness.add_argument(
428 "-d",
428 "-d",
429 "--debug",
429 "--debug",
430 action="store_true",
430 action="store_true",
431 help="debug mode: write output of test scripts to console"
431 help="debug mode: write output of test scripts to console"
432 " rather than capturing and diffing it (disables timeout)",
432 " rather than capturing and diffing it (disables timeout)",
433 )
433 )
434 harness.add_argument(
434 harness.add_argument(
435 "-f",
435 "-f",
436 "--first",
436 "--first",
437 action="store_true",
437 action="store_true",
438 help="exit on the first test failure",
438 help="exit on the first test failure",
439 )
439 )
440 harness.add_argument(
440 harness.add_argument(
441 "-i",
441 "-i",
442 "--interactive",
442 "--interactive",
443 action="store_true",
443 action="store_true",
444 help="prompt to accept changed output",
444 help="prompt to accept changed output",
445 )
445 )
446 harness.add_argument(
446 harness.add_argument(
447 "-j",
447 "-j",
448 "--jobs",
448 "--jobs",
449 type=int,
449 type=int,
450 help="number of jobs to run in parallel"
450 help="number of jobs to run in parallel"
451 " (default: $%s or %d)" % defaults['jobs'],
451 " (default: $%s or %d)" % defaults['jobs'],
452 )
452 )
453 harness.add_argument(
453 harness.add_argument(
454 "--keep-tmpdir",
454 "--keep-tmpdir",
455 action="store_true",
455 action="store_true",
456 help="keep temporary directory after running tests",
456 help="keep temporary directory after running tests",
457 )
457 )
458 harness.add_argument(
458 harness.add_argument(
459 '--known-good-rev',
459 '--known-good-rev',
460 metavar="known_good_rev",
460 metavar="known_good_rev",
461 help=(
461 help=(
462 "Automatically bisect any failures using this "
462 "Automatically bisect any failures using this "
463 "revision as a known-good revision."
463 "revision as a known-good revision."
464 ),
464 ),
465 )
465 )
466 harness.add_argument(
466 harness.add_argument(
467 "--list-tests",
467 "--list-tests",
468 action="store_true",
468 action="store_true",
469 help="list tests instead of running them",
469 help="list tests instead of running them",
470 )
470 )
471 harness.add_argument(
471 harness.add_argument(
472 "--loop", action="store_true", help="loop tests repeatedly"
472 "--loop", action="store_true", help="loop tests repeatedly"
473 )
473 )
474 harness.add_argument(
474 harness.add_argument(
475 '--random', action="store_true", help='run tests in random order'
475 '--random', action="store_true", help='run tests in random order'
476 )
476 )
477 harness.add_argument(
477 harness.add_argument(
478 '--order-by-runtime',
478 '--order-by-runtime',
479 action="store_true",
479 action="store_true",
480 help='run slowest tests first, according to .testtimes',
480 help='run slowest tests first, according to .testtimes',
481 )
481 )
482 harness.add_argument(
482 harness.add_argument(
483 "-p",
483 "-p",
484 "--port",
484 "--port",
485 type=int,
485 type=int,
486 help="port on which servers should listen"
486 help="port on which servers should listen"
487 " (default: $%s or %d)" % defaults['port'],
487 " (default: $%s or %d)" % defaults['port'],
488 )
488 )
489 harness.add_argument(
489 harness.add_argument(
490 '--profile-runner',
490 '--profile-runner',
491 action='store_true',
491 action='store_true',
492 help='run statprof on run-tests',
492 help='run statprof on run-tests',
493 )
493 )
494 harness.add_argument(
494 harness.add_argument(
495 "-R", "--restart", action="store_true", help="restart at last error"
495 "-R", "--restart", action="store_true", help="restart at last error"
496 )
496 )
497 harness.add_argument(
497 harness.add_argument(
498 "--runs-per-test",
498 "--runs-per-test",
499 type=int,
499 type=int,
500 dest="runs_per_test",
500 dest="runs_per_test",
501 help="run each test N times (default=1)",
501 help="run each test N times (default=1)",
502 default=1,
502 default=1,
503 )
503 )
504 harness.add_argument(
504 harness.add_argument(
505 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
505 "--shell", help="shell to use (default: $%s or %s)" % defaults['shell']
506 )
506 )
507 harness.add_argument(
507 harness.add_argument(
508 '--showchannels', action='store_true', help='show scheduling channels'
508 '--showchannels', action='store_true', help='show scheduling channels'
509 )
509 )
510 harness.add_argument(
510 harness.add_argument(
511 "--slowtimeout",
511 "--slowtimeout",
512 type=int,
512 type=int,
513 help="kill errant slow tests after SLOWTIMEOUT seconds"
513 help="kill errant slow tests after SLOWTIMEOUT seconds"
514 " (default: $%s or %d)" % defaults['slowtimeout'],
514 " (default: $%s or %d)" % defaults['slowtimeout'],
515 )
515 )
516 harness.add_argument(
516 harness.add_argument(
517 "-t",
517 "-t",
518 "--timeout",
518 "--timeout",
519 type=int,
519 type=int,
520 help="kill errant tests after TIMEOUT seconds"
520 help="kill errant tests after TIMEOUT seconds"
521 " (default: $%s or %d)" % defaults['timeout'],
521 " (default: $%s or %d)" % defaults['timeout'],
522 )
522 )
523 harness.add_argument(
523 harness.add_argument(
524 "--tmpdir",
524 "--tmpdir",
525 help="run tests in the given temporary directory"
525 help="run tests in the given temporary directory"
526 " (implies --keep-tmpdir)",
526 " (implies --keep-tmpdir)",
527 )
527 )
528 harness.add_argument(
528 harness.add_argument(
529 "-v", "--verbose", action="store_true", help="output verbose messages"
529 "-v", "--verbose", action="store_true", help="output verbose messages"
530 )
530 )
531
531
532 hgconf = parser.add_argument_group('Mercurial Configuration')
532 hgconf = parser.add_argument_group('Mercurial Configuration')
533 hgconf.add_argument(
533 hgconf.add_argument(
534 "--chg",
534 "--chg",
535 action="store_true",
535 action="store_true",
536 help="install and use chg wrapper in place of hg",
536 help="install and use chg wrapper in place of hg",
537 )
537 )
538 hgconf.add_argument(
538 hgconf.add_argument(
539 "--chg-debug",
539 "--chg-debug",
540 action="store_true",
540 action="store_true",
541 help="show chg debug logs",
541 help="show chg debug logs",
542 )
542 )
543 hgconf.add_argument(
544 "--rhg",
545 action="store_true",
546 help="install and use rhg Rust implementation in place of hg",
547 )
543 hgconf.add_argument("--compiler", help="compiler to build with")
548 hgconf.add_argument("--compiler", help="compiler to build with")
544 hgconf.add_argument(
549 hgconf.add_argument(
545 '--extra-config-opt',
550 '--extra-config-opt',
546 action="append",
551 action="append",
547 default=[],
552 default=[],
548 help='set the given config opt in the test hgrc',
553 help='set the given config opt in the test hgrc',
549 )
554 )
550 hgconf.add_argument(
555 hgconf.add_argument(
551 "-l",
556 "-l",
552 "--local",
557 "--local",
553 action="store_true",
558 action="store_true",
554 help="shortcut for --with-hg=<testdir>/../hg, "
559 help="shortcut for --with-hg=<testdir>/../hg, "
560 "--with-rhg=<testdir>/../rust/target/release/rhg if --rhg is set, "
555 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
561 "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set",
556 )
562 )
557 hgconf.add_argument(
563 hgconf.add_argument(
558 "--ipv6",
564 "--ipv6",
559 action="store_true",
565 action="store_true",
560 help="prefer IPv6 to IPv4 for network related tests",
566 help="prefer IPv6 to IPv4 for network related tests",
561 )
567 )
562 hgconf.add_argument(
568 hgconf.add_argument(
563 "--pure",
569 "--pure",
564 action="store_true",
570 action="store_true",
565 help="use pure Python code instead of C extensions",
571 help="use pure Python code instead of C extensions",
566 )
572 )
567 hgconf.add_argument(
573 hgconf.add_argument(
568 "--rust",
574 "--rust",
569 action="store_true",
575 action="store_true",
570 help="use Rust code alongside C extensions",
576 help="use Rust code alongside C extensions",
571 )
577 )
572 hgconf.add_argument(
578 hgconf.add_argument(
573 "--no-rust",
579 "--no-rust",
574 action="store_true",
580 action="store_true",
575 help="do not use Rust code even if compiled",
581 help="do not use Rust code even if compiled",
576 )
582 )
577 hgconf.add_argument(
583 hgconf.add_argument(
578 "--with-chg",
584 "--with-chg",
579 metavar="CHG",
585 metavar="CHG",
580 help="use specified chg wrapper in place of hg",
586 help="use specified chg wrapper in place of hg",
581 )
587 )
582 hgconf.add_argument(
588 hgconf.add_argument(
589 "--with-rhg",
590 metavar="RHG",
591 help="use specified rhg Rust implementation in place of hg",
592 )
593 hgconf.add_argument(
583 "--with-hg",
594 "--with-hg",
584 metavar="HG",
595 metavar="HG",
585 help="test using specified hg script rather than a "
596 help="test using specified hg script rather than a "
586 "temporary installation",
597 "temporary installation",
587 )
598 )
588
599
589 reporting = parser.add_argument_group('Results Reporting')
600 reporting = parser.add_argument_group('Results Reporting')
590 reporting.add_argument(
601 reporting.add_argument(
591 "-C",
602 "-C",
592 "--annotate",
603 "--annotate",
593 action="store_true",
604 action="store_true",
594 help="output files annotated with coverage",
605 help="output files annotated with coverage",
595 )
606 )
596 reporting.add_argument(
607 reporting.add_argument(
597 "--color",
608 "--color",
598 choices=["always", "auto", "never"],
609 choices=["always", "auto", "never"],
599 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
610 default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
600 help="colorisation: always|auto|never (default: auto)",
611 help="colorisation: always|auto|never (default: auto)",
601 )
612 )
602 reporting.add_argument(
613 reporting.add_argument(
603 "-c",
614 "-c",
604 "--cover",
615 "--cover",
605 action="store_true",
616 action="store_true",
606 help="print a test coverage report",
617 help="print a test coverage report",
607 )
618 )
608 reporting.add_argument(
619 reporting.add_argument(
609 '--exceptions',
620 '--exceptions',
610 action='store_true',
621 action='store_true',
611 help='log all exceptions and generate an exception report',
622 help='log all exceptions and generate an exception report',
612 )
623 )
613 reporting.add_argument(
624 reporting.add_argument(
614 "-H",
625 "-H",
615 "--htmlcov",
626 "--htmlcov",
616 action="store_true",
627 action="store_true",
617 help="create an HTML report of the coverage of the files",
628 help="create an HTML report of the coverage of the files",
618 )
629 )
619 reporting.add_argument(
630 reporting.add_argument(
620 "--json",
631 "--json",
621 action="store_true",
632 action="store_true",
622 help="store test result data in 'report.json' file",
633 help="store test result data in 'report.json' file",
623 )
634 )
624 reporting.add_argument(
635 reporting.add_argument(
625 "--outputdir",
636 "--outputdir",
626 help="directory to write error logs to (default=test directory)",
637 help="directory to write error logs to (default=test directory)",
627 )
638 )
628 reporting.add_argument(
639 reporting.add_argument(
629 "-n", "--nodiff", action="store_true", help="skip showing test changes"
640 "-n", "--nodiff", action="store_true", help="skip showing test changes"
630 )
641 )
631 reporting.add_argument(
642 reporting.add_argument(
632 "-S",
643 "-S",
633 "--noskips",
644 "--noskips",
634 action="store_true",
645 action="store_true",
635 help="don't report skip tests verbosely",
646 help="don't report skip tests verbosely",
636 )
647 )
637 reporting.add_argument(
648 reporting.add_argument(
638 "--time", action="store_true", help="time how long each test takes"
649 "--time", action="store_true", help="time how long each test takes"
639 )
650 )
640 reporting.add_argument("--view", help="external diff viewer")
651 reporting.add_argument("--view", help="external diff viewer")
641 reporting.add_argument(
652 reporting.add_argument(
642 "--xunit", help="record xunit results at specified path"
653 "--xunit", help="record xunit results at specified path"
643 )
654 )
644
655
645 for option, (envvar, default) in defaults.items():
656 for option, (envvar, default) in defaults.items():
646 defaults[option] = type(default)(os.environ.get(envvar, default))
657 defaults[option] = type(default)(os.environ.get(envvar, default))
647 parser.set_defaults(**defaults)
658 parser.set_defaults(**defaults)
648
659
649 return parser
660 return parser
650
661
651
662
652 def parseargs(args, parser):
663 def parseargs(args, parser):
653 """Parse arguments with our OptionParser and validate results."""
664 """Parse arguments with our OptionParser and validate results."""
654 options = parser.parse_args(args)
665 options = parser.parse_args(args)
655
666
656 # jython is always pure
667 # jython is always pure
657 if 'java' in sys.platform or '__pypy__' in sys.modules:
668 if 'java' in sys.platform or '__pypy__' in sys.modules:
658 options.pure = True
669 options.pure = True
659
670
660 if platform.python_implementation() != 'CPython' and options.rust:
671 if platform.python_implementation() != 'CPython' and options.rust:
661 parser.error('Rust extensions are only available with CPython')
672 parser.error('Rust extensions are only available with CPython')
662
673
663 if options.pure and options.rust:
674 if options.pure and options.rust:
664 parser.error('--rust cannot be used with --pure')
675 parser.error('--rust cannot be used with --pure')
665
676
666 if options.rust and options.no_rust:
677 if options.rust and options.no_rust:
667 parser.error('--rust cannot be used with --no-rust')
678 parser.error('--rust cannot be used with --no-rust')
668
679
669 if options.local:
680 if options.local:
670 if options.with_hg or options.with_chg:
681 if options.with_hg or options.with_rhg or options.with_chg:
671 parser.error('--local cannot be used with --with-hg or --with-chg')
682 parser.error(
683 '--local cannot be used with --with-hg or --with-rhg or --with-chg'
684 )
672 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
685 testdir = os.path.dirname(_sys2bytes(canonpath(sys.argv[0])))
673 reporootdir = os.path.dirname(testdir)
686 reporootdir = os.path.dirname(testdir)
674 pathandattrs = [(b'hg', 'with_hg')]
687 pathandattrs = [(b'hg', 'with_hg')]
675 if options.chg:
688 if options.chg:
676 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
689 pathandattrs.append((b'contrib/chg/chg', 'with_chg'))
690 if options.rhg:
691 pathandattrs.append((b'rust/target/release/rhg', 'with_rhg'))
677 for relpath, attr in pathandattrs:
692 for relpath, attr in pathandattrs:
678 binpath = os.path.join(reporootdir, relpath)
693 binpath = os.path.join(reporootdir, relpath)
679 if os.name != 'nt' and not os.access(binpath, os.X_OK):
694 if os.name != 'nt' and not os.access(binpath, os.X_OK):
680 parser.error(
695 parser.error(
681 '--local specified, but %r not found or '
696 '--local specified, but %r not found or '
682 'not executable' % binpath
697 'not executable' % binpath
683 )
698 )
684 setattr(options, attr, _bytes2sys(binpath))
699 setattr(options, attr, _bytes2sys(binpath))
685
700
686 if options.with_hg:
701 if options.with_hg:
687 options.with_hg = canonpath(_sys2bytes(options.with_hg))
702 options.with_hg = canonpath(_sys2bytes(options.with_hg))
688 if not (
703 if not (
689 os.path.isfile(options.with_hg)
704 os.path.isfile(options.with_hg)
690 and os.access(options.with_hg, os.X_OK)
705 and os.access(options.with_hg, os.X_OK)
691 ):
706 ):
692 parser.error('--with-hg must specify an executable hg script')
707 parser.error('--with-hg must specify an executable hg script')
693 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
708 if os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']:
694 sys.stderr.write('warning: --with-hg should specify an hg script\n')
709 sys.stderr.write('warning: --with-hg should specify an hg script\n')
695 sys.stderr.flush()
710 sys.stderr.flush()
696
711
697 if (options.chg or options.with_chg) and os.name == 'nt':
712 if (options.chg or options.with_chg) and os.name == 'nt':
698 parser.error('chg does not work on %s' % os.name)
713 parser.error('chg does not work on %s' % os.name)
714 if (options.rhg or options.with_rhg) and os.name == 'nt':
715 parser.error('rhg does not work on %s' % os.name)
699 if options.with_chg:
716 if options.with_chg:
700 options.chg = False # no installation to temporary location
717 options.chg = False # no installation to temporary location
701 options.with_chg = canonpath(_sys2bytes(options.with_chg))
718 options.with_chg = canonpath(_sys2bytes(options.with_chg))
702 if not (
719 if not (
703 os.path.isfile(options.with_chg)
720 os.path.isfile(options.with_chg)
704 and os.access(options.with_chg, os.X_OK)
721 and os.access(options.with_chg, os.X_OK)
705 ):
722 ):
706 parser.error('--with-chg must specify a chg executable')
723 parser.error('--with-chg must specify a chg executable')
724 if options.with_rhg:
725 options.rhg = False # no installation to temporary location
726 options.with_rhg = canonpath(_sys2bytes(options.with_rhg))
727 if not (
728 os.path.isfile(options.with_rhg)
729 and os.access(options.with_rhg, os.X_OK)
730 ):
731 parser.error('--with-rhg must specify a rhg executable')
707 if options.chg and options.with_hg:
732 if options.chg and options.with_hg:
708 # chg shares installation location with hg
733 # chg shares installation location with hg
709 parser.error(
734 parser.error(
710 '--chg does not work when --with-hg is specified '
735 '--chg does not work when --with-hg is specified '
711 '(use --with-chg instead)'
736 '(use --with-chg instead)'
712 )
737 )
738 if options.rhg and options.with_hg:
739 # rhg shares installation location with hg
740 parser.error(
741 '--rhg does not work when --with-hg is specified '
742 '(use --with-rhg instead)'
743 )
744 if options.rhg and options.chg:
745 parser.error('--rhg and --chg do not work together')
713
746
714 if options.color == 'always' and not pygmentspresent:
747 if options.color == 'always' and not pygmentspresent:
715 sys.stderr.write(
748 sys.stderr.write(
716 'warning: --color=always ignored because '
749 'warning: --color=always ignored because '
717 'pygments is not installed\n'
750 'pygments is not installed\n'
718 )
751 )
719
752
720 if options.bisect_repo and not options.known_good_rev:
753 if options.bisect_repo and not options.known_good_rev:
721 parser.error("--bisect-repo cannot be used without --known-good-rev")
754 parser.error("--bisect-repo cannot be used without --known-good-rev")
722
755
723 global useipv6
756 global useipv6
724 if options.ipv6:
757 if options.ipv6:
725 useipv6 = checksocketfamily('AF_INET6')
758 useipv6 = checksocketfamily('AF_INET6')
726 else:
759 else:
727 # only use IPv6 if IPv4 is unavailable and IPv6 is available
760 # only use IPv6 if IPv4 is unavailable and IPv6 is available
728 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
761 useipv6 = (not checksocketfamily('AF_INET')) and checksocketfamily(
729 'AF_INET6'
762 'AF_INET6'
730 )
763 )
731
764
732 options.anycoverage = options.cover or options.annotate or options.htmlcov
765 options.anycoverage = options.cover or options.annotate or options.htmlcov
733 if options.anycoverage:
766 if options.anycoverage:
734 try:
767 try:
735 import coverage
768 import coverage
736
769
737 covver = version.StrictVersion(coverage.__version__).version
770 covver = version.StrictVersion(coverage.__version__).version
738 if covver < (3, 3):
771 if covver < (3, 3):
739 parser.error('coverage options require coverage 3.3 or later')
772 parser.error('coverage options require coverage 3.3 or later')
740 except ImportError:
773 except ImportError:
741 parser.error('coverage options now require the coverage package')
774 parser.error('coverage options now require the coverage package')
742
775
743 if options.anycoverage and options.local:
776 if options.anycoverage and options.local:
744 # this needs some path mangling somewhere, I guess
777 # this needs some path mangling somewhere, I guess
745 parser.error(
778 parser.error(
746 "sorry, coverage options do not work when --local " "is specified"
779 "sorry, coverage options do not work when --local " "is specified"
747 )
780 )
748
781
749 if options.anycoverage and options.with_hg:
782 if options.anycoverage and options.with_hg:
750 parser.error(
783 parser.error(
751 "sorry, coverage options do not work when --with-hg " "is specified"
784 "sorry, coverage options do not work when --with-hg " "is specified"
752 )
785 )
753
786
754 global verbose
787 global verbose
755 if options.verbose:
788 if options.verbose:
756 verbose = ''
789 verbose = ''
757
790
758 if options.tmpdir:
791 if options.tmpdir:
759 options.tmpdir = canonpath(options.tmpdir)
792 options.tmpdir = canonpath(options.tmpdir)
760
793
761 if options.jobs < 1:
794 if options.jobs < 1:
762 parser.error('--jobs must be positive')
795 parser.error('--jobs must be positive')
763 if options.interactive and options.debug:
796 if options.interactive and options.debug:
764 parser.error("-i/--interactive and -d/--debug are incompatible")
797 parser.error("-i/--interactive and -d/--debug are incompatible")
765 if options.debug:
798 if options.debug:
766 if options.timeout != defaults['timeout']:
799 if options.timeout != defaults['timeout']:
767 sys.stderr.write('warning: --timeout option ignored with --debug\n')
800 sys.stderr.write('warning: --timeout option ignored with --debug\n')
768 if options.slowtimeout != defaults['slowtimeout']:
801 if options.slowtimeout != defaults['slowtimeout']:
769 sys.stderr.write(
802 sys.stderr.write(
770 'warning: --slowtimeout option ignored with --debug\n'
803 'warning: --slowtimeout option ignored with --debug\n'
771 )
804 )
772 options.timeout = 0
805 options.timeout = 0
773 options.slowtimeout = 0
806 options.slowtimeout = 0
774
807
775 if options.blacklist:
808 if options.blacklist:
776 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
809 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
777 if options.whitelist:
810 if options.whitelist:
778 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
811 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
779 else:
812 else:
780 options.whitelisted = {}
813 options.whitelisted = {}
781
814
782 if options.showchannels:
815 if options.showchannels:
783 options.nodiff = True
816 options.nodiff = True
784
817
785 return options
818 return options
786
819
787
820
788 def rename(src, dst):
821 def rename(src, dst):
789 """Like os.rename(), trade atomicity and opened files friendliness
822 """Like os.rename(), trade atomicity and opened files friendliness
790 for existing destination support.
823 for existing destination support.
791 """
824 """
792 shutil.copy(src, dst)
825 shutil.copy(src, dst)
793 os.remove(src)
826 os.remove(src)
794
827
795
828
796 def makecleanable(path):
829 def makecleanable(path):
797 """Try to fix directory permission recursively so that the entire tree
830 """Try to fix directory permission recursively so that the entire tree
798 can be deleted"""
831 can be deleted"""
799 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
832 for dirpath, dirnames, _filenames in os.walk(path, topdown=True):
800 for d in dirnames:
833 for d in dirnames:
801 p = os.path.join(dirpath, d)
834 p = os.path.join(dirpath, d)
802 try:
835 try:
803 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
836 os.chmod(p, os.stat(p).st_mode & 0o777 | 0o700) # chmod u+rwx
804 except OSError:
837 except OSError:
805 pass
838 pass
806
839
807
840
808 _unified_diff = difflib.unified_diff
841 _unified_diff = difflib.unified_diff
809 if PYTHON3:
842 if PYTHON3:
810 import functools
843 import functools
811
844
812 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
845 _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
813
846
814
847
815 def getdiff(expected, output, ref, err):
848 def getdiff(expected, output, ref, err):
816 servefail = False
849 servefail = False
817 lines = []
850 lines = []
818 for line in _unified_diff(expected, output, ref, err):
851 for line in _unified_diff(expected, output, ref, err):
819 if line.startswith(b'+++') or line.startswith(b'---'):
852 if line.startswith(b'+++') or line.startswith(b'---'):
820 line = line.replace(b'\\', b'/')
853 line = line.replace(b'\\', b'/')
821 if line.endswith(b' \n'):
854 if line.endswith(b' \n'):
822 line = line[:-2] + b'\n'
855 line = line[:-2] + b'\n'
823 lines.append(line)
856 lines.append(line)
824 if not servefail and line.startswith(
857 if not servefail and line.startswith(
825 b'+ abort: child process failed to start'
858 b'+ abort: child process failed to start'
826 ):
859 ):
827 servefail = True
860 servefail = True
828
861
829 return servefail, lines
862 return servefail, lines
830
863
831
864
832 verbose = False
865 verbose = False
833
866
834
867
835 def vlog(*msg):
868 def vlog(*msg):
836 """Log only when in verbose mode."""
869 """Log only when in verbose mode."""
837 if verbose is False:
870 if verbose is False:
838 return
871 return
839
872
840 return log(*msg)
873 return log(*msg)
841
874
842
875
843 # Bytes that break XML even in a CDATA block: control characters 0-31
876 # Bytes that break XML even in a CDATA block: control characters 0-31
844 # sans \t, \n and \r
877 # sans \t, \n and \r
845 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
878 CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
846
879
847 # Match feature conditionalized output lines in the form, capturing the feature
880 # Match feature conditionalized output lines in the form, capturing the feature
848 # list in group 2, and the preceeding line output in group 1:
881 # list in group 2, and the preceeding line output in group 1:
849 #
882 #
850 # output..output (feature !)\n
883 # output..output (feature !)\n
851 optline = re.compile(br'(.*) \((.+?) !\)\n$')
884 optline = re.compile(br'(.*) \((.+?) !\)\n$')
852
885
853
886
854 def cdatasafe(data):
887 def cdatasafe(data):
855 """Make a string safe to include in a CDATA block.
888 """Make a string safe to include in a CDATA block.
856
889
857 Certain control characters are illegal in a CDATA block, and
890 Certain control characters are illegal in a CDATA block, and
858 there's no way to include a ]]> in a CDATA either. This function
891 there's no way to include a ]]> in a CDATA either. This function
859 replaces illegal bytes with ? and adds a space between the ]] so
892 replaces illegal bytes with ? and adds a space between the ]] so
860 that it won't break the CDATA block.
893 that it won't break the CDATA block.
861 """
894 """
862 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
895 return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
863
896
864
897
865 def log(*msg):
898 def log(*msg):
866 """Log something to stdout.
899 """Log something to stdout.
867
900
868 Arguments are strings to print.
901 Arguments are strings to print.
869 """
902 """
870 with iolock:
903 with iolock:
871 if verbose:
904 if verbose:
872 print(verbose, end=' ')
905 print(verbose, end=' ')
873 for m in msg:
906 for m in msg:
874 print(m, end=' ')
907 print(m, end=' ')
875 print()
908 print()
876 sys.stdout.flush()
909 sys.stdout.flush()
877
910
878
911
879 def highlightdiff(line, color):
912 def highlightdiff(line, color):
880 if not color:
913 if not color:
881 return line
914 return line
882 assert pygmentspresent
915 assert pygmentspresent
883 return pygments.highlight(
916 return pygments.highlight(
884 line.decode('latin1'), difflexer, terminal256formatter
917 line.decode('latin1'), difflexer, terminal256formatter
885 ).encode('latin1')
918 ).encode('latin1')
886
919
887
920
888 def highlightmsg(msg, color):
921 def highlightmsg(msg, color):
889 if not color:
922 if not color:
890 return msg
923 return msg
891 assert pygmentspresent
924 assert pygmentspresent
892 return pygments.highlight(msg, runnerlexer, runnerformatter)
925 return pygments.highlight(msg, runnerlexer, runnerformatter)
893
926
894
927
895 def terminate(proc):
928 def terminate(proc):
896 """Terminate subprocess"""
929 """Terminate subprocess"""
897 vlog('# Terminating process %d' % proc.pid)
930 vlog('# Terminating process %d' % proc.pid)
898 try:
931 try:
899 proc.terminate()
932 proc.terminate()
900 except OSError:
933 except OSError:
901 pass
934 pass
902
935
903
936
904 def killdaemons(pidfile):
937 def killdaemons(pidfile):
905 import killdaemons as killmod
938 import killdaemons as killmod
906
939
907 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
940 return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog)
908
941
909
942
910 class Test(unittest.TestCase):
943 class Test(unittest.TestCase):
911 """Encapsulates a single, runnable test.
944 """Encapsulates a single, runnable test.
912
945
913 While this class conforms to the unittest.TestCase API, it differs in that
946 While this class conforms to the unittest.TestCase API, it differs in that
914 instances need to be instantiated manually. (Typically, unittest.TestCase
947 instances need to be instantiated manually. (Typically, unittest.TestCase
915 classes are instantiated automatically by scanning modules.)
948 classes are instantiated automatically by scanning modules.)
916 """
949 """
917
950
918 # Status code reserved for skipped tests (used by hghave).
951 # Status code reserved for skipped tests (used by hghave).
919 SKIPPED_STATUS = 80
952 SKIPPED_STATUS = 80
920
953
921 def __init__(
954 def __init__(
922 self,
955 self,
923 path,
956 path,
924 outputdir,
957 outputdir,
925 tmpdir,
958 tmpdir,
926 keeptmpdir=False,
959 keeptmpdir=False,
927 debug=False,
960 debug=False,
928 first=False,
961 first=False,
929 timeout=None,
962 timeout=None,
930 startport=None,
963 startport=None,
931 extraconfigopts=None,
964 extraconfigopts=None,
932 shell=None,
965 shell=None,
933 hgcommand=None,
966 hgcommand=None,
934 slowtimeout=None,
967 slowtimeout=None,
935 usechg=False,
968 usechg=False,
936 chgdebug=False,
969 chgdebug=False,
970 rhg_fallback_exe=None,
937 useipv6=False,
971 useipv6=False,
938 ):
972 ):
939 """Create a test from parameters.
973 """Create a test from parameters.
940
974
941 path is the full path to the file defining the test.
975 path is the full path to the file defining the test.
942
976
943 tmpdir is the main temporary directory to use for this test.
977 tmpdir is the main temporary directory to use for this test.
944
978
945 keeptmpdir determines whether to keep the test's temporary directory
979 keeptmpdir determines whether to keep the test's temporary directory
946 after execution. It defaults to removal (False).
980 after execution. It defaults to removal (False).
947
981
948 debug mode will make the test execute verbosely, with unfiltered
982 debug mode will make the test execute verbosely, with unfiltered
949 output.
983 output.
950
984
951 timeout controls the maximum run time of the test. It is ignored when
985 timeout controls the maximum run time of the test. It is ignored when
952 debug is True. See slowtimeout for tests with #require slow.
986 debug is True. See slowtimeout for tests with #require slow.
953
987
954 slowtimeout overrides timeout if the test has #require slow.
988 slowtimeout overrides timeout if the test has #require slow.
955
989
956 startport controls the starting port number to use for this test. Each
990 startport controls the starting port number to use for this test. Each
957 test will reserve 3 port numbers for execution. It is the caller's
991 test will reserve 3 port numbers for execution. It is the caller's
958 responsibility to allocate a non-overlapping port range to Test
992 responsibility to allocate a non-overlapping port range to Test
959 instances.
993 instances.
960
994
961 extraconfigopts is an iterable of extra hgrc config options. Values
995 extraconfigopts is an iterable of extra hgrc config options. Values
962 must have the form "key=value" (something understood by hgrc). Values
996 must have the form "key=value" (something understood by hgrc). Values
963 of the form "foo.key=value" will result in "[foo] key=value".
997 of the form "foo.key=value" will result in "[foo] key=value".
964
998
965 shell is the shell to execute tests in.
999 shell is the shell to execute tests in.
966 """
1000 """
967 if timeout is None:
1001 if timeout is None:
968 timeout = defaults['timeout']
1002 timeout = defaults['timeout']
969 if startport is None:
1003 if startport is None:
970 startport = defaults['port']
1004 startport = defaults['port']
971 if slowtimeout is None:
1005 if slowtimeout is None:
972 slowtimeout = defaults['slowtimeout']
1006 slowtimeout = defaults['slowtimeout']
973 self.path = path
1007 self.path = path
974 self.relpath = os.path.relpath(path)
1008 self.relpath = os.path.relpath(path)
975 self.bname = os.path.basename(path)
1009 self.bname = os.path.basename(path)
976 self.name = _bytes2sys(self.bname)
1010 self.name = _bytes2sys(self.bname)
977 self._testdir = os.path.dirname(path)
1011 self._testdir = os.path.dirname(path)
978 self._outputdir = outputdir
1012 self._outputdir = outputdir
979 self._tmpname = os.path.basename(path)
1013 self._tmpname = os.path.basename(path)
980 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
1014 self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname)
981
1015
982 self._threadtmp = tmpdir
1016 self._threadtmp = tmpdir
983 self._keeptmpdir = keeptmpdir
1017 self._keeptmpdir = keeptmpdir
984 self._debug = debug
1018 self._debug = debug
985 self._first = first
1019 self._first = first
986 self._timeout = timeout
1020 self._timeout = timeout
987 self._slowtimeout = slowtimeout
1021 self._slowtimeout = slowtimeout
988 self._startport = startport
1022 self._startport = startport
989 self._extraconfigopts = extraconfigopts or []
1023 self._extraconfigopts = extraconfigopts or []
990 self._shell = _sys2bytes(shell)
1024 self._shell = _sys2bytes(shell)
991 self._hgcommand = hgcommand or b'hg'
1025 self._hgcommand = hgcommand or b'hg'
992 self._usechg = usechg
1026 self._usechg = usechg
993 self._chgdebug = chgdebug
1027 self._chgdebug = chgdebug
1028 self._rhg_fallback_exe = rhg_fallback_exe
994 self._useipv6 = useipv6
1029 self._useipv6 = useipv6
995
1030
996 self._aborted = False
1031 self._aborted = False
997 self._daemonpids = []
1032 self._daemonpids = []
998 self._finished = None
1033 self._finished = None
999 self._ret = None
1034 self._ret = None
1000 self._out = None
1035 self._out = None
1001 self._skipped = None
1036 self._skipped = None
1002 self._testtmp = None
1037 self._testtmp = None
1003 self._chgsockdir = None
1038 self._chgsockdir = None
1004
1039
1005 self._refout = self.readrefout()
1040 self._refout = self.readrefout()
1006
1041
1007 def readrefout(self):
1042 def readrefout(self):
1008 """read reference output"""
1043 """read reference output"""
1009 # If we're not in --debug mode and reference output file exists,
1044 # If we're not in --debug mode and reference output file exists,
1010 # check test output against it.
1045 # check test output against it.
1011 if self._debug:
1046 if self._debug:
1012 return None # to match "out is None"
1047 return None # to match "out is None"
1013 elif os.path.exists(self.refpath):
1048 elif os.path.exists(self.refpath):
1014 with open(self.refpath, 'rb') as f:
1049 with open(self.refpath, 'rb') as f:
1015 return f.read().splitlines(True)
1050 return f.read().splitlines(True)
1016 else:
1051 else:
1017 return []
1052 return []
1018
1053
1019 # needed to get base class __repr__ running
1054 # needed to get base class __repr__ running
1020 @property
1055 @property
1021 def _testMethodName(self):
1056 def _testMethodName(self):
1022 return self.name
1057 return self.name
1023
1058
1024 def __str__(self):
1059 def __str__(self):
1025 return self.name
1060 return self.name
1026
1061
1027 def shortDescription(self):
1062 def shortDescription(self):
1028 return self.name
1063 return self.name
1029
1064
1030 def setUp(self):
1065 def setUp(self):
1031 """Tasks to perform before run()."""
1066 """Tasks to perform before run()."""
1032 self._finished = False
1067 self._finished = False
1033 self._ret = None
1068 self._ret = None
1034 self._out = None
1069 self._out = None
1035 self._skipped = None
1070 self._skipped = None
1036
1071
1037 try:
1072 try:
1038 os.mkdir(self._threadtmp)
1073 os.mkdir(self._threadtmp)
1039 except OSError as e:
1074 except OSError as e:
1040 if e.errno != errno.EEXIST:
1075 if e.errno != errno.EEXIST:
1041 raise
1076 raise
1042
1077
1043 name = self._tmpname
1078 name = self._tmpname
1044 self._testtmp = os.path.join(self._threadtmp, name)
1079 self._testtmp = os.path.join(self._threadtmp, name)
1045 os.mkdir(self._testtmp)
1080 os.mkdir(self._testtmp)
1046
1081
1047 # Remove any previous output files.
1082 # Remove any previous output files.
1048 if os.path.exists(self.errpath):
1083 if os.path.exists(self.errpath):
1049 try:
1084 try:
1050 os.remove(self.errpath)
1085 os.remove(self.errpath)
1051 except OSError as e:
1086 except OSError as e:
1052 # We might have raced another test to clean up a .err
1087 # We might have raced another test to clean up a .err
1053 # file, so ignore ENOENT when removing a previous .err
1088 # file, so ignore ENOENT when removing a previous .err
1054 # file.
1089 # file.
1055 if e.errno != errno.ENOENT:
1090 if e.errno != errno.ENOENT:
1056 raise
1091 raise
1057
1092
1058 if self._usechg:
1093 if self._usechg:
1059 self._chgsockdir = os.path.join(
1094 self._chgsockdir = os.path.join(
1060 self._threadtmp, b'%s.chgsock' % name
1095 self._threadtmp, b'%s.chgsock' % name
1061 )
1096 )
1062 os.mkdir(self._chgsockdir)
1097 os.mkdir(self._chgsockdir)
1063
1098
1064 def run(self, result):
1099 def run(self, result):
1065 """Run this test and report results against a TestResult instance."""
1100 """Run this test and report results against a TestResult instance."""
1066 # This function is extremely similar to unittest.TestCase.run(). Once
1101 # This function is extremely similar to unittest.TestCase.run(). Once
1067 # we require Python 2.7 (or at least its version of unittest), this
1102 # we require Python 2.7 (or at least its version of unittest), this
1068 # function can largely go away.
1103 # function can largely go away.
1069 self._result = result
1104 self._result = result
1070 result.startTest(self)
1105 result.startTest(self)
1071 try:
1106 try:
1072 try:
1107 try:
1073 self.setUp()
1108 self.setUp()
1074 except (KeyboardInterrupt, SystemExit):
1109 except (KeyboardInterrupt, SystemExit):
1075 self._aborted = True
1110 self._aborted = True
1076 raise
1111 raise
1077 except Exception:
1112 except Exception:
1078 result.addError(self, sys.exc_info())
1113 result.addError(self, sys.exc_info())
1079 return
1114 return
1080
1115
1081 success = False
1116 success = False
1082 try:
1117 try:
1083 self.runTest()
1118 self.runTest()
1084 except KeyboardInterrupt:
1119 except KeyboardInterrupt:
1085 self._aborted = True
1120 self._aborted = True
1086 raise
1121 raise
1087 except unittest.SkipTest as e:
1122 except unittest.SkipTest as e:
1088 result.addSkip(self, str(e))
1123 result.addSkip(self, str(e))
1089 # The base class will have already counted this as a
1124 # The base class will have already counted this as a
1090 # test we "ran", but we want to exclude skipped tests
1125 # test we "ran", but we want to exclude skipped tests
1091 # from those we count towards those run.
1126 # from those we count towards those run.
1092 result.testsRun -= 1
1127 result.testsRun -= 1
1093 except self.failureException as e:
1128 except self.failureException as e:
1094 # This differs from unittest in that we don't capture
1129 # This differs from unittest in that we don't capture
1095 # the stack trace. This is for historical reasons and
1130 # the stack trace. This is for historical reasons and
1096 # this decision could be revisited in the future,
1131 # this decision could be revisited in the future,
1097 # especially for PythonTest instances.
1132 # especially for PythonTest instances.
1098 if result.addFailure(self, str(e)):
1133 if result.addFailure(self, str(e)):
1099 success = True
1134 success = True
1100 except Exception:
1135 except Exception:
1101 result.addError(self, sys.exc_info())
1136 result.addError(self, sys.exc_info())
1102 else:
1137 else:
1103 success = True
1138 success = True
1104
1139
1105 try:
1140 try:
1106 self.tearDown()
1141 self.tearDown()
1107 except (KeyboardInterrupt, SystemExit):
1142 except (KeyboardInterrupt, SystemExit):
1108 self._aborted = True
1143 self._aborted = True
1109 raise
1144 raise
1110 except Exception:
1145 except Exception:
1111 result.addError(self, sys.exc_info())
1146 result.addError(self, sys.exc_info())
1112 success = False
1147 success = False
1113
1148
1114 if success:
1149 if success:
1115 result.addSuccess(self)
1150 result.addSuccess(self)
1116 finally:
1151 finally:
1117 result.stopTest(self, interrupted=self._aborted)
1152 result.stopTest(self, interrupted=self._aborted)
1118
1153
1119 def runTest(self):
1154 def runTest(self):
1120 """Run this test instance.
1155 """Run this test instance.
1121
1156
1122 This will return a tuple describing the result of the test.
1157 This will return a tuple describing the result of the test.
1123 """
1158 """
1124 env = self._getenv()
1159 env = self._getenv()
1125 self._genrestoreenv(env)
1160 self._genrestoreenv(env)
1126 self._daemonpids.append(env['DAEMON_PIDS'])
1161 self._daemonpids.append(env['DAEMON_PIDS'])
1127 self._createhgrc(env['HGRCPATH'])
1162 self._createhgrc(env['HGRCPATH'])
1128
1163
1129 vlog('# Test', self.name)
1164 vlog('# Test', self.name)
1130
1165
1131 ret, out = self._run(env)
1166 ret, out = self._run(env)
1132 self._finished = True
1167 self._finished = True
1133 self._ret = ret
1168 self._ret = ret
1134 self._out = out
1169 self._out = out
1135
1170
1136 def describe(ret):
1171 def describe(ret):
1137 if ret < 0:
1172 if ret < 0:
1138 return 'killed by signal: %d' % -ret
1173 return 'killed by signal: %d' % -ret
1139 return 'returned error code %d' % ret
1174 return 'returned error code %d' % ret
1140
1175
1141 self._skipped = False
1176 self._skipped = False
1142
1177
1143 if ret == self.SKIPPED_STATUS:
1178 if ret == self.SKIPPED_STATUS:
1144 if out is None: # Debug mode, nothing to parse.
1179 if out is None: # Debug mode, nothing to parse.
1145 missing = ['unknown']
1180 missing = ['unknown']
1146 failed = None
1181 failed = None
1147 else:
1182 else:
1148 missing, failed = TTest.parsehghaveoutput(out)
1183 missing, failed = TTest.parsehghaveoutput(out)
1149
1184
1150 if not missing:
1185 if not missing:
1151 missing = ['skipped']
1186 missing = ['skipped']
1152
1187
1153 if failed:
1188 if failed:
1154 self.fail('hg have failed checking for %s' % failed[-1])
1189 self.fail('hg have failed checking for %s' % failed[-1])
1155 else:
1190 else:
1156 self._skipped = True
1191 self._skipped = True
1157 raise unittest.SkipTest(missing[-1])
1192 raise unittest.SkipTest(missing[-1])
1158 elif ret == 'timeout':
1193 elif ret == 'timeout':
1159 self.fail('timed out')
1194 self.fail('timed out')
1160 elif ret is False:
1195 elif ret is False:
1161 self.fail('no result code from test')
1196 self.fail('no result code from test')
1162 elif out != self._refout:
1197 elif out != self._refout:
1163 # Diff generation may rely on written .err file.
1198 # Diff generation may rely on written .err file.
1164 if (
1199 if (
1165 (ret != 0 or out != self._refout)
1200 (ret != 0 or out != self._refout)
1166 and not self._skipped
1201 and not self._skipped
1167 and not self._debug
1202 and not self._debug
1168 ):
1203 ):
1169 with open(self.errpath, 'wb') as f:
1204 with open(self.errpath, 'wb') as f:
1170 for line in out:
1205 for line in out:
1171 f.write(line)
1206 f.write(line)
1172
1207
1173 # The result object handles diff calculation for us.
1208 # The result object handles diff calculation for us.
1174 with firstlock:
1209 with firstlock:
1175 if self._result.addOutputMismatch(self, ret, out, self._refout):
1210 if self._result.addOutputMismatch(self, ret, out, self._refout):
1176 # change was accepted, skip failing
1211 # change was accepted, skip failing
1177 return
1212 return
1178 if self._first:
1213 if self._first:
1179 global firsterror
1214 global firsterror
1180 firsterror = True
1215 firsterror = True
1181
1216
1182 if ret:
1217 if ret:
1183 msg = 'output changed and ' + describe(ret)
1218 msg = 'output changed and ' + describe(ret)
1184 else:
1219 else:
1185 msg = 'output changed'
1220 msg = 'output changed'
1186
1221
1187 self.fail(msg)
1222 self.fail(msg)
1188 elif ret:
1223 elif ret:
1189 self.fail(describe(ret))
1224 self.fail(describe(ret))
1190
1225
1191 def tearDown(self):
1226 def tearDown(self):
1192 """Tasks to perform after run()."""
1227 """Tasks to perform after run()."""
1193 for entry in self._daemonpids:
1228 for entry in self._daemonpids:
1194 killdaemons(entry)
1229 killdaemons(entry)
1195 self._daemonpids = []
1230 self._daemonpids = []
1196
1231
1197 if self._keeptmpdir:
1232 if self._keeptmpdir:
1198 log(
1233 log(
1199 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1234 '\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s'
1200 % (
1235 % (
1201 _bytes2sys(self._testtmp),
1236 _bytes2sys(self._testtmp),
1202 _bytes2sys(self._threadtmp),
1237 _bytes2sys(self._threadtmp),
1203 )
1238 )
1204 )
1239 )
1205 else:
1240 else:
1206 try:
1241 try:
1207 shutil.rmtree(self._testtmp)
1242 shutil.rmtree(self._testtmp)
1208 except OSError:
1243 except OSError:
1209 # unreadable directory may be left in $TESTTMP; fix permission
1244 # unreadable directory may be left in $TESTTMP; fix permission
1210 # and try again
1245 # and try again
1211 makecleanable(self._testtmp)
1246 makecleanable(self._testtmp)
1212 shutil.rmtree(self._testtmp, True)
1247 shutil.rmtree(self._testtmp, True)
1213 shutil.rmtree(self._threadtmp, True)
1248 shutil.rmtree(self._threadtmp, True)
1214
1249
1215 if self._usechg:
1250 if self._usechg:
1216 # chgservers will stop automatically after they find the socket
1251 # chgservers will stop automatically after they find the socket
1217 # files are deleted
1252 # files are deleted
1218 shutil.rmtree(self._chgsockdir, True)
1253 shutil.rmtree(self._chgsockdir, True)
1219
1254
1220 if (
1255 if (
1221 (self._ret != 0 or self._out != self._refout)
1256 (self._ret != 0 or self._out != self._refout)
1222 and not self._skipped
1257 and not self._skipped
1223 and not self._debug
1258 and not self._debug
1224 and self._out
1259 and self._out
1225 ):
1260 ):
1226 with open(self.errpath, 'wb') as f:
1261 with open(self.errpath, 'wb') as f:
1227 for line in self._out:
1262 for line in self._out:
1228 f.write(line)
1263 f.write(line)
1229
1264
1230 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1265 vlog("# Ret was:", self._ret, '(%s)' % self.name)
1231
1266
1232 def _run(self, env):
1267 def _run(self, env):
1233 # This should be implemented in child classes to run tests.
1268 # This should be implemented in child classes to run tests.
1234 raise unittest.SkipTest('unknown test type')
1269 raise unittest.SkipTest('unknown test type')
1235
1270
1236 def abort(self):
1271 def abort(self):
1237 """Terminate execution of this test."""
1272 """Terminate execution of this test."""
1238 self._aborted = True
1273 self._aborted = True
1239
1274
1240 def _portmap(self, i):
1275 def _portmap(self, i):
1241 offset = b'' if i == 0 else b'%d' % i
1276 offset = b'' if i == 0 else b'%d' % i
1242 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1277 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
1243
1278
1244 def _getreplacements(self):
1279 def _getreplacements(self):
1245 """Obtain a mapping of text replacements to apply to test output.
1280 """Obtain a mapping of text replacements to apply to test output.
1246
1281
1247 Test output needs to be normalized so it can be compared to expected
1282 Test output needs to be normalized so it can be compared to expected
1248 output. This function defines how some of that normalization will
1283 output. This function defines how some of that normalization will
1249 occur.
1284 occur.
1250 """
1285 """
1251 r = [
1286 r = [
1252 # This list should be parallel to defineport in _getenv
1287 # This list should be parallel to defineport in _getenv
1253 self._portmap(0),
1288 self._portmap(0),
1254 self._portmap(1),
1289 self._portmap(1),
1255 self._portmap(2),
1290 self._portmap(2),
1256 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1291 (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'),
1257 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1292 (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'),
1258 ]
1293 ]
1259 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1294 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
1260
1295
1261 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1296 replacementfile = os.path.join(self._testdir, b'common-pattern.py')
1262
1297
1263 if os.path.exists(replacementfile):
1298 if os.path.exists(replacementfile):
1264 data = {}
1299 data = {}
1265 with open(replacementfile, mode='rb') as source:
1300 with open(replacementfile, mode='rb') as source:
1266 # the intermediate 'compile' step help with debugging
1301 # the intermediate 'compile' step help with debugging
1267 code = compile(source.read(), replacementfile, 'exec')
1302 code = compile(source.read(), replacementfile, 'exec')
1268 exec(code, data)
1303 exec(code, data)
1269 for value in data.get('substitutions', ()):
1304 for value in data.get('substitutions', ()):
1270 if len(value) != 2:
1305 if len(value) != 2:
1271 msg = 'malformatted substitution in %s: %r'
1306 msg = 'malformatted substitution in %s: %r'
1272 msg %= (replacementfile, value)
1307 msg %= (replacementfile, value)
1273 raise ValueError(msg)
1308 raise ValueError(msg)
1274 r.append(value)
1309 r.append(value)
1275 return r
1310 return r
1276
1311
1277 def _escapepath(self, p):
1312 def _escapepath(self, p):
1278 if os.name == 'nt':
1313 if os.name == 'nt':
1279 return b''.join(
1314 return b''.join(
1280 c.isalpha()
1315 c.isalpha()
1281 and b'[%s%s]' % (c.lower(), c.upper())
1316 and b'[%s%s]' % (c.lower(), c.upper())
1282 or c in b'/\\'
1317 or c in b'/\\'
1283 and br'[/\\]'
1318 and br'[/\\]'
1284 or c.isdigit()
1319 or c.isdigit()
1285 and c
1320 and c
1286 or b'\\' + c
1321 or b'\\' + c
1287 for c in [p[i : i + 1] for i in range(len(p))]
1322 for c in [p[i : i + 1] for i in range(len(p))]
1288 )
1323 )
1289 else:
1324 else:
1290 return re.escape(p)
1325 return re.escape(p)
1291
1326
1292 def _localip(self):
1327 def _localip(self):
1293 if self._useipv6:
1328 if self._useipv6:
1294 return b'::1'
1329 return b'::1'
1295 else:
1330 else:
1296 return b'127.0.0.1'
1331 return b'127.0.0.1'
1297
1332
1298 def _genrestoreenv(self, testenv):
1333 def _genrestoreenv(self, testenv):
1299 """Generate a script that can be used by tests to restore the original
1334 """Generate a script that can be used by tests to restore the original
1300 environment."""
1335 environment."""
1301 # Put the restoreenv script inside self._threadtmp
1336 # Put the restoreenv script inside self._threadtmp
1302 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1337 scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
1303 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1338 testenv['HGTEST_RESTOREENV'] = _bytes2sys(scriptpath)
1304
1339
1305 # Only restore environment variable names that the shell allows
1340 # Only restore environment variable names that the shell allows
1306 # us to export.
1341 # us to export.
1307 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1342 name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$')
1308
1343
1309 # Do not restore these variables; otherwise tests would fail.
1344 # Do not restore these variables; otherwise tests would fail.
1310 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1345 reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'}
1311
1346
1312 with open(scriptpath, 'w') as envf:
1347 with open(scriptpath, 'w') as envf:
1313 for name, value in origenviron.items():
1348 for name, value in origenviron.items():
1314 if not name_regex.match(name):
1349 if not name_regex.match(name):
1315 # Skip environment variables with unusual names not
1350 # Skip environment variables with unusual names not
1316 # allowed by most shells.
1351 # allowed by most shells.
1317 continue
1352 continue
1318 if name in reqnames:
1353 if name in reqnames:
1319 continue
1354 continue
1320 envf.write('%s=%s\n' % (name, shellquote(value)))
1355 envf.write('%s=%s\n' % (name, shellquote(value)))
1321
1356
1322 for name in testenv:
1357 for name in testenv:
1323 if name in origenviron or name in reqnames:
1358 if name in origenviron or name in reqnames:
1324 continue
1359 continue
1325 envf.write('unset %s\n' % (name,))
1360 envf.write('unset %s\n' % (name,))
1326
1361
1327 def _getenv(self):
1362 def _getenv(self):
1328 """Obtain environment variables to use during test execution."""
1363 """Obtain environment variables to use during test execution."""
1329
1364
1330 def defineport(i):
1365 def defineport(i):
1331 offset = '' if i == 0 else '%s' % i
1366 offset = '' if i == 0 else '%s' % i
1332 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1367 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
1333
1368
1334 env = os.environ.copy()
1369 env = os.environ.copy()
1335 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1370 env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
1336 env['HGEMITWARNINGS'] = '1'
1371 env['HGEMITWARNINGS'] = '1'
1337 env['TESTTMP'] = _bytes2sys(self._testtmp)
1372 env['TESTTMP'] = _bytes2sys(self._testtmp)
1338 env['TESTNAME'] = self.name
1373 env['TESTNAME'] = self.name
1339 env['HOME'] = _bytes2sys(self._testtmp)
1374 env['HOME'] = _bytes2sys(self._testtmp)
1340 if os.name == 'nt':
1375 if os.name == 'nt':
1341 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1376 # py3.8+ ignores HOME: https://bugs.python.org/issue36264
1342 env['USERPROFILE'] = env['HOME']
1377 env['USERPROFILE'] = env['HOME']
1343 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1378 formated_timeout = _bytes2sys(b"%d" % default_defaults['timeout'][1])
1344 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1379 env['HGTEST_TIMEOUT_DEFAULT'] = formated_timeout
1345 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1380 env['HGTEST_TIMEOUT'] = _bytes2sys(b"%d" % self._timeout)
1346 # This number should match portneeded in _getport
1381 # This number should match portneeded in _getport
1347 for port in xrange(3):
1382 for port in xrange(3):
1348 # This list should be parallel to _portmap in _getreplacements
1383 # This list should be parallel to _portmap in _getreplacements
1349 defineport(port)
1384 defineport(port)
1350 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1385 env["HGRCPATH"] = _bytes2sys(os.path.join(self._threadtmp, b'.hgrc'))
1351 env["DAEMON_PIDS"] = _bytes2sys(
1386 env["DAEMON_PIDS"] = _bytes2sys(
1352 os.path.join(self._threadtmp, b'daemon.pids')
1387 os.path.join(self._threadtmp, b'daemon.pids')
1353 )
1388 )
1354 env["HGEDITOR"] = (
1389 env["HGEDITOR"] = (
1355 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1390 '"' + sysexecutable + '"' + ' -c "import sys; sys.exit(0)"'
1356 )
1391 )
1357 env["HGUSER"] = "test"
1392 env["HGUSER"] = "test"
1358 env["HGENCODING"] = "ascii"
1393 env["HGENCODING"] = "ascii"
1359 env["HGENCODINGMODE"] = "strict"
1394 env["HGENCODINGMODE"] = "strict"
1360 env["HGHOSTNAME"] = "test-hostname"
1395 env["HGHOSTNAME"] = "test-hostname"
1361 env['HGIPV6'] = str(int(self._useipv6))
1396 env['HGIPV6'] = str(int(self._useipv6))
1362 # See contrib/catapipe.py for how to use this functionality.
1397 # See contrib/catapipe.py for how to use this functionality.
1363 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1398 if 'HGTESTCATAPULTSERVERPIPE' not in env:
1364 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1399 # If we don't have HGTESTCATAPULTSERVERPIPE explicitly set, pull the
1365 # non-test one in as a default, otherwise set to devnull
1400 # non-test one in as a default, otherwise set to devnull
1366 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1401 env['HGTESTCATAPULTSERVERPIPE'] = env.get(
1367 'HGCATAPULTSERVERPIPE', os.devnull
1402 'HGCATAPULTSERVERPIPE', os.devnull
1368 )
1403 )
1369
1404
1370 extraextensions = []
1405 extraextensions = []
1371 for opt in self._extraconfigopts:
1406 for opt in self._extraconfigopts:
1372 section, key = opt.split('.', 1)
1407 section, key = opt.split('.', 1)
1373 if section != 'extensions':
1408 if section != 'extensions':
1374 continue
1409 continue
1375 name = key.split('=', 1)[0]
1410 name = key.split('=', 1)[0]
1376 extraextensions.append(name)
1411 extraextensions.append(name)
1377
1412
1378 if extraextensions:
1413 if extraextensions:
1379 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1414 env['HGTESTEXTRAEXTENSIONS'] = ' '.join(extraextensions)
1380
1415
1381 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1416 # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
1382 # IP addresses.
1417 # IP addresses.
1383 env['LOCALIP'] = _bytes2sys(self._localip())
1418 env['LOCALIP'] = _bytes2sys(self._localip())
1384
1419
1385 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1420 # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
1386 # but this is needed for testing python instances like dummyssh,
1421 # but this is needed for testing python instances like dummyssh,
1387 # dummysmtpd.py, and dumbhttp.py.
1422 # dummysmtpd.py, and dumbhttp.py.
1388 if PYTHON3 and os.name == 'nt':
1423 if PYTHON3 and os.name == 'nt':
1389 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1424 env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1390
1425
1391 # Modified HOME in test environment can confuse Rust tools. So set
1426 # Modified HOME in test environment can confuse Rust tools. So set
1392 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1427 # CARGO_HOME and RUSTUP_HOME automatically if a Rust toolchain is
1393 # present and these variables aren't already defined.
1428 # present and these variables aren't already defined.
1394 cargo_home_path = os.path.expanduser('~/.cargo')
1429 cargo_home_path = os.path.expanduser('~/.cargo')
1395 rustup_home_path = os.path.expanduser('~/.rustup')
1430 rustup_home_path = os.path.expanduser('~/.rustup')
1396
1431
1397 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1432 if os.path.exists(cargo_home_path) and b'CARGO_HOME' not in osenvironb:
1398 env['CARGO_HOME'] = cargo_home_path
1433 env['CARGO_HOME'] = cargo_home_path
1399 if (
1434 if (
1400 os.path.exists(rustup_home_path)
1435 os.path.exists(rustup_home_path)
1401 and b'RUSTUP_HOME' not in osenvironb
1436 and b'RUSTUP_HOME' not in osenvironb
1402 ):
1437 ):
1403 env['RUSTUP_HOME'] = rustup_home_path
1438 env['RUSTUP_HOME'] = rustup_home_path
1404
1439
1405 # Reset some environment variables to well-known values so that
1440 # Reset some environment variables to well-known values so that
1406 # the tests produce repeatable output.
1441 # the tests produce repeatable output.
1407 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1442 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
1408 env['TZ'] = 'GMT'
1443 env['TZ'] = 'GMT'
1409 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1444 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1410 env['COLUMNS'] = '80'
1445 env['COLUMNS'] = '80'
1411 env['TERM'] = 'xterm'
1446 env['TERM'] = 'xterm'
1412
1447
1413 dropped = [
1448 dropped = [
1414 'CDPATH',
1449 'CDPATH',
1415 'CHGDEBUG',
1450 'CHGDEBUG',
1416 'EDITOR',
1451 'EDITOR',
1417 'GREP_OPTIONS',
1452 'GREP_OPTIONS',
1418 'HG',
1453 'HG',
1419 'HGMERGE',
1454 'HGMERGE',
1420 'HGPLAIN',
1455 'HGPLAIN',
1421 'HGPLAINEXCEPT',
1456 'HGPLAINEXCEPT',
1422 'HGPROF',
1457 'HGPROF',
1423 'http_proxy',
1458 'http_proxy',
1424 'no_proxy',
1459 'no_proxy',
1425 'NO_PROXY',
1460 'NO_PROXY',
1426 'PAGER',
1461 'PAGER',
1427 'VISUAL',
1462 'VISUAL',
1428 ]
1463 ]
1429
1464
1430 for k in dropped:
1465 for k in dropped:
1431 if k in env:
1466 if k in env:
1432 del env[k]
1467 del env[k]
1433
1468
1434 # unset env related to hooks
1469 # unset env related to hooks
1435 for k in list(env):
1470 for k in list(env):
1436 if k.startswith('HG_'):
1471 if k.startswith('HG_'):
1437 del env[k]
1472 del env[k]
1438
1473
1439 if self._usechg:
1474 if self._usechg:
1440 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1475 env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server')
1441 if self._chgdebug:
1476 if self._chgdebug:
1442 env['CHGDEBUG'] = 'true'
1477 env['CHGDEBUG'] = 'true'
1443
1478
1444 return env
1479 return env
1445
1480
1446 def _createhgrc(self, path):
1481 def _createhgrc(self, path):
1447 """Create an hgrc file for this test."""
1482 """Create an hgrc file for this test."""
1448 with open(path, 'wb') as hgrc:
1483 with open(path, 'wb') as hgrc:
1449 hgrc.write(b'[ui]\n')
1484 hgrc.write(b'[ui]\n')
1450 hgrc.write(b'slash = True\n')
1485 hgrc.write(b'slash = True\n')
1451 hgrc.write(b'interactive = False\n')
1486 hgrc.write(b'interactive = False\n')
1452 hgrc.write(b'detailed-exit-code = True\n')
1487 hgrc.write(b'detailed-exit-code = True\n')
1453 hgrc.write(b'merge = internal:merge\n')
1488 hgrc.write(b'merge = internal:merge\n')
1454 hgrc.write(b'mergemarkers = detailed\n')
1489 hgrc.write(b'mergemarkers = detailed\n')
1455 hgrc.write(b'promptecho = True\n')
1490 hgrc.write(b'promptecho = True\n')
1456 hgrc.write(b'timeout.warn=15\n')
1491 hgrc.write(b'timeout.warn=15\n')
1457 hgrc.write(b'[defaults]\n')
1492 hgrc.write(b'[defaults]\n')
1458 hgrc.write(b'[devel]\n')
1493 hgrc.write(b'[devel]\n')
1459 hgrc.write(b'all-warnings = true\n')
1494 hgrc.write(b'all-warnings = true\n')
1460 hgrc.write(b'default-date = 0 0\n')
1495 hgrc.write(b'default-date = 0 0\n')
1461 hgrc.write(b'[largefiles]\n')
1496 hgrc.write(b'[largefiles]\n')
1462 hgrc.write(
1497 hgrc.write(
1463 b'usercache = %s\n'
1498 b'usercache = %s\n'
1464 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1499 % (os.path.join(self._testtmp, b'.cache/largefiles'))
1465 )
1500 )
1466 hgrc.write(b'[lfs]\n')
1501 hgrc.write(b'[lfs]\n')
1467 hgrc.write(
1502 hgrc.write(
1468 b'usercache = %s\n'
1503 b'usercache = %s\n'
1469 % (os.path.join(self._testtmp, b'.cache/lfs'))
1504 % (os.path.join(self._testtmp, b'.cache/lfs'))
1470 )
1505 )
1471 hgrc.write(b'[web]\n')
1506 hgrc.write(b'[web]\n')
1472 hgrc.write(b'address = localhost\n')
1507 hgrc.write(b'address = localhost\n')
1473 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1508 hgrc.write(b'ipv6 = %r\n' % self._useipv6)
1474 hgrc.write(b'server-header = testing stub value\n')
1509 hgrc.write(b'server-header = testing stub value\n')
1475
1510
1511 if self._rhg_fallback_exe:
1512 hgrc.write(b'[rhg]\n')
1513 hgrc.write(
1514 b'fallback-executable = %s\n' % self._rhg_fallback_exe
1515 )
1516
1476 for opt in self._extraconfigopts:
1517 for opt in self._extraconfigopts:
1477 section, key = _sys2bytes(opt).split(b'.', 1)
1518 section, key = _sys2bytes(opt).split(b'.', 1)
1478 assert b'=' in key, (
1519 assert b'=' in key, (
1479 'extra config opt %s must ' 'have an = for assignment' % opt
1520 'extra config opt %s must ' 'have an = for assignment' % opt
1480 )
1521 )
1481 hgrc.write(b'[%s]\n%s\n' % (section, key))
1522 hgrc.write(b'[%s]\n%s\n' % (section, key))
1482
1523
1483 def fail(self, msg):
1524 def fail(self, msg):
1484 # unittest differentiates between errored and failed.
1525 # unittest differentiates between errored and failed.
1485 # Failed is denoted by AssertionError (by default at least).
1526 # Failed is denoted by AssertionError (by default at least).
1486 raise AssertionError(msg)
1527 raise AssertionError(msg)
1487
1528
1488 def _runcommand(self, cmd, env, normalizenewlines=False):
1529 def _runcommand(self, cmd, env, normalizenewlines=False):
1489 """Run command in a sub-process, capturing the output (stdout and
1530 """Run command in a sub-process, capturing the output (stdout and
1490 stderr).
1531 stderr).
1491
1532
1492 Return a tuple (exitcode, output). output is None in debug mode.
1533 Return a tuple (exitcode, output). output is None in debug mode.
1493 """
1534 """
1494 if self._debug:
1535 if self._debug:
1495 proc = subprocess.Popen(
1536 proc = subprocess.Popen(
1496 _bytes2sys(cmd),
1537 _bytes2sys(cmd),
1497 shell=True,
1538 shell=True,
1498 cwd=_bytes2sys(self._testtmp),
1539 cwd=_bytes2sys(self._testtmp),
1499 env=env,
1540 env=env,
1500 )
1541 )
1501 ret = proc.wait()
1542 ret = proc.wait()
1502 return (ret, None)
1543 return (ret, None)
1503
1544
1504 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1545 proc = Popen4(cmd, self._testtmp, self._timeout, env)
1505
1546
1506 def cleanup():
1547 def cleanup():
1507 terminate(proc)
1548 terminate(proc)
1508 ret = proc.wait()
1549 ret = proc.wait()
1509 if ret == 0:
1550 if ret == 0:
1510 ret = signal.SIGTERM << 8
1551 ret = signal.SIGTERM << 8
1511 killdaemons(env['DAEMON_PIDS'])
1552 killdaemons(env['DAEMON_PIDS'])
1512 return ret
1553 return ret
1513
1554
1514 proc.tochild.close()
1555 proc.tochild.close()
1515
1556
1516 try:
1557 try:
1517 output = proc.fromchild.read()
1558 output = proc.fromchild.read()
1518 except KeyboardInterrupt:
1559 except KeyboardInterrupt:
1519 vlog('# Handling keyboard interrupt')
1560 vlog('# Handling keyboard interrupt')
1520 cleanup()
1561 cleanup()
1521 raise
1562 raise
1522
1563
1523 ret = proc.wait()
1564 ret = proc.wait()
1524 if wifexited(ret):
1565 if wifexited(ret):
1525 ret = os.WEXITSTATUS(ret)
1566 ret = os.WEXITSTATUS(ret)
1526
1567
1527 if proc.timeout:
1568 if proc.timeout:
1528 ret = 'timeout'
1569 ret = 'timeout'
1529
1570
1530 if ret:
1571 if ret:
1531 killdaemons(env['DAEMON_PIDS'])
1572 killdaemons(env['DAEMON_PIDS'])
1532
1573
1533 for s, r in self._getreplacements():
1574 for s, r in self._getreplacements():
1534 output = re.sub(s, r, output)
1575 output = re.sub(s, r, output)
1535
1576
1536 if normalizenewlines:
1577 if normalizenewlines:
1537 output = output.replace(b'\r\n', b'\n')
1578 output = output.replace(b'\r\n', b'\n')
1538
1579
1539 return ret, output.splitlines(True)
1580 return ret, output.splitlines(True)
1540
1581
1541
1582
1542 class PythonTest(Test):
1583 class PythonTest(Test):
1543 """A Python-based test."""
1584 """A Python-based test."""
1544
1585
1545 @property
1586 @property
1546 def refpath(self):
1587 def refpath(self):
1547 return os.path.join(self._testdir, b'%s.out' % self.bname)
1588 return os.path.join(self._testdir, b'%s.out' % self.bname)
1548
1589
1549 def _run(self, env):
1590 def _run(self, env):
1550 # Quote the python(3) executable for Windows
1591 # Quote the python(3) executable for Windows
1551 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1592 cmd = b'"%s" "%s"' % (PYTHON, self.path)
1552 vlog("# Running", cmd.decode("utf-8"))
1593 vlog("# Running", cmd.decode("utf-8"))
1553 normalizenewlines = os.name == 'nt'
1594 normalizenewlines = os.name == 'nt'
1554 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1595 result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines)
1555 if self._aborted:
1596 if self._aborted:
1556 raise KeyboardInterrupt()
1597 raise KeyboardInterrupt()
1557
1598
1558 return result
1599 return result
1559
1600
1560
1601
1561 # Some glob patterns apply only in some circumstances, so the script
1602 # Some glob patterns apply only in some circumstances, so the script
1562 # might want to remove (glob) annotations that otherwise should be
1603 # might want to remove (glob) annotations that otherwise should be
1563 # retained.
1604 # retained.
1564 checkcodeglobpats = [
1605 checkcodeglobpats = [
1565 # On Windows it looks like \ doesn't require a (glob), but we know
1606 # On Windows it looks like \ doesn't require a (glob), but we know
1566 # better.
1607 # better.
1567 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1608 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
1568 re.compile(br'^moving \S+/.*[^)]$'),
1609 re.compile(br'^moving \S+/.*[^)]$'),
1569 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1610 re.compile(br'^pulling from \$TESTTMP/.*[^)]$'),
1570 # Not all platforms have 127.0.0.1 as loopback (though most do),
1611 # Not all platforms have 127.0.0.1 as loopback (though most do),
1571 # so we always glob that too.
1612 # so we always glob that too.
1572 re.compile(br'.*\$LOCALIP.*$'),
1613 re.compile(br'.*\$LOCALIP.*$'),
1573 ]
1614 ]
1574
1615
1575 bchr = chr
1616 bchr = chr
1576 if PYTHON3:
1617 if PYTHON3:
1577 bchr = lambda x: bytes([x])
1618 bchr = lambda x: bytes([x])
1578
1619
1579 WARN_UNDEFINED = 1
1620 WARN_UNDEFINED = 1
1580 WARN_YES = 2
1621 WARN_YES = 2
1581 WARN_NO = 3
1622 WARN_NO = 3
1582
1623
1583 MARK_OPTIONAL = b" (?)\n"
1624 MARK_OPTIONAL = b" (?)\n"
1584
1625
1585
1626
1586 def isoptional(line):
1627 def isoptional(line):
1587 return line.endswith(MARK_OPTIONAL)
1628 return line.endswith(MARK_OPTIONAL)
1588
1629
1589
1630
1590 class TTest(Test):
1631 class TTest(Test):
1591 """A "t test" is a test backed by a .t file."""
1632 """A "t test" is a test backed by a .t file."""
1592
1633
1593 SKIPPED_PREFIX = b'skipped: '
1634 SKIPPED_PREFIX = b'skipped: '
1594 FAILED_PREFIX = b'hghave check failed: '
1635 FAILED_PREFIX = b'hghave check failed: '
1595 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1636 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
1596
1637
1597 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1638 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
1598 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1639 ESCAPEMAP = {bchr(i): br'\x%02x' % i for i in range(256)}
1599 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1640 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
1600
1641
1601 def __init__(self, path, *args, **kwds):
1642 def __init__(self, path, *args, **kwds):
1602 # accept an extra "case" parameter
1643 # accept an extra "case" parameter
1603 case = kwds.pop('case', [])
1644 case = kwds.pop('case', [])
1604 self._case = case
1645 self._case = case
1605 self._allcases = {x for y in parsettestcases(path) for x in y}
1646 self._allcases = {x for y in parsettestcases(path) for x in y}
1606 super(TTest, self).__init__(path, *args, **kwds)
1647 super(TTest, self).__init__(path, *args, **kwds)
1607 if case:
1648 if case:
1608 casepath = b'#'.join(case)
1649 casepath = b'#'.join(case)
1609 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1650 self.name = '%s#%s' % (self.name, _bytes2sys(casepath))
1610 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1651 self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
1611 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1652 self._tmpname += b'-%s' % casepath.replace(b'#', b'-')
1612 self._have = {}
1653 self._have = {}
1613
1654
1614 @property
1655 @property
1615 def refpath(self):
1656 def refpath(self):
1616 return os.path.join(self._testdir, self.bname)
1657 return os.path.join(self._testdir, self.bname)
1617
1658
1618 def _run(self, env):
1659 def _run(self, env):
1619 with open(self.path, 'rb') as f:
1660 with open(self.path, 'rb') as f:
1620 lines = f.readlines()
1661 lines = f.readlines()
1621
1662
1622 # .t file is both reference output and the test input, keep reference
1663 # .t file is both reference output and the test input, keep reference
1623 # output updated with the the test input. This avoids some race
1664 # output updated with the the test input. This avoids some race
1624 # conditions where the reference output does not match the actual test.
1665 # conditions where the reference output does not match the actual test.
1625 if self._refout is not None:
1666 if self._refout is not None:
1626 self._refout = lines
1667 self._refout = lines
1627
1668
1628 salt, script, after, expected = self._parsetest(lines)
1669 salt, script, after, expected = self._parsetest(lines)
1629
1670
1630 # Write out the generated script.
1671 # Write out the generated script.
1631 fname = b'%s.sh' % self._testtmp
1672 fname = b'%s.sh' % self._testtmp
1632 with open(fname, 'wb') as f:
1673 with open(fname, 'wb') as f:
1633 for l in script:
1674 for l in script:
1634 f.write(l)
1675 f.write(l)
1635
1676
1636 cmd = b'%s "%s"' % (self._shell, fname)
1677 cmd = b'%s "%s"' % (self._shell, fname)
1637 vlog("# Running", cmd.decode("utf-8"))
1678 vlog("# Running", cmd.decode("utf-8"))
1638
1679
1639 exitcode, output = self._runcommand(cmd, env)
1680 exitcode, output = self._runcommand(cmd, env)
1640
1681
1641 if self._aborted:
1682 if self._aborted:
1642 raise KeyboardInterrupt()
1683 raise KeyboardInterrupt()
1643
1684
1644 # Do not merge output if skipped. Return hghave message instead.
1685 # Do not merge output if skipped. Return hghave message instead.
1645 # Similarly, with --debug, output is None.
1686 # Similarly, with --debug, output is None.
1646 if exitcode == self.SKIPPED_STATUS or output is None:
1687 if exitcode == self.SKIPPED_STATUS or output is None:
1647 return exitcode, output
1688 return exitcode, output
1648
1689
1649 return self._processoutput(exitcode, output, salt, after, expected)
1690 return self._processoutput(exitcode, output, salt, after, expected)
1650
1691
1651 def _hghave(self, reqs):
1692 def _hghave(self, reqs):
1652 allreqs = b' '.join(reqs)
1693 allreqs = b' '.join(reqs)
1653
1694
1654 self._detectslow(reqs)
1695 self._detectslow(reqs)
1655
1696
1656 if allreqs in self._have:
1697 if allreqs in self._have:
1657 return self._have.get(allreqs)
1698 return self._have.get(allreqs)
1658
1699
1659 # TODO do something smarter when all other uses of hghave are gone.
1700 # TODO do something smarter when all other uses of hghave are gone.
1660 runtestdir = osenvironb[b'RUNTESTDIR']
1701 runtestdir = osenvironb[b'RUNTESTDIR']
1661 tdir = runtestdir.replace(b'\\', b'/')
1702 tdir = runtestdir.replace(b'\\', b'/')
1662 proc = Popen4(
1703 proc = Popen4(
1663 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1704 b'%s -c "%s/hghave %s"' % (self._shell, tdir, allreqs),
1664 self._testtmp,
1705 self._testtmp,
1665 0,
1706 0,
1666 self._getenv(),
1707 self._getenv(),
1667 )
1708 )
1668 stdout, stderr = proc.communicate()
1709 stdout, stderr = proc.communicate()
1669 ret = proc.wait()
1710 ret = proc.wait()
1670 if wifexited(ret):
1711 if wifexited(ret):
1671 ret = os.WEXITSTATUS(ret)
1712 ret = os.WEXITSTATUS(ret)
1672 if ret == 2:
1713 if ret == 2:
1673 print(stdout.decode('utf-8'))
1714 print(stdout.decode('utf-8'))
1674 sys.exit(1)
1715 sys.exit(1)
1675
1716
1676 if ret != 0:
1717 if ret != 0:
1677 self._have[allreqs] = (False, stdout)
1718 self._have[allreqs] = (False, stdout)
1678 return False, stdout
1719 return False, stdout
1679
1720
1680 self._have[allreqs] = (True, None)
1721 self._have[allreqs] = (True, None)
1681 return True, None
1722 return True, None
1682
1723
1683 def _detectslow(self, reqs):
1724 def _detectslow(self, reqs):
1684 """update the timeout of slow test when appropriate"""
1725 """update the timeout of slow test when appropriate"""
1685 if b'slow' in reqs:
1726 if b'slow' in reqs:
1686 self._timeout = self._slowtimeout
1727 self._timeout = self._slowtimeout
1687
1728
1688 def _iftest(self, args):
1729 def _iftest(self, args):
1689 # implements "#if"
1730 # implements "#if"
1690 reqs = []
1731 reqs = []
1691 for arg in args:
1732 for arg in args:
1692 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1733 if arg.startswith(b'no-') and arg[3:] in self._allcases:
1693 if arg[3:] in self._case:
1734 if arg[3:] in self._case:
1694 return False
1735 return False
1695 elif arg in self._allcases:
1736 elif arg in self._allcases:
1696 if arg not in self._case:
1737 if arg not in self._case:
1697 return False
1738 return False
1698 else:
1739 else:
1699 reqs.append(arg)
1740 reqs.append(arg)
1700 self._detectslow(reqs)
1741 self._detectslow(reqs)
1701 return self._hghave(reqs)[0]
1742 return self._hghave(reqs)[0]
1702
1743
1703 def _parsetest(self, lines):
1744 def _parsetest(self, lines):
1704 # We generate a shell script which outputs unique markers to line
1745 # We generate a shell script which outputs unique markers to line
1705 # up script results with our source. These markers include input
1746 # up script results with our source. These markers include input
1706 # line number and the last return code.
1747 # line number and the last return code.
1707 salt = b"SALT%d" % time.time()
1748 salt = b"SALT%d" % time.time()
1708
1749
1709 def addsalt(line, inpython):
1750 def addsalt(line, inpython):
1710 if inpython:
1751 if inpython:
1711 script.append(b'%s %d 0\n' % (salt, line))
1752 script.append(b'%s %d 0\n' % (salt, line))
1712 else:
1753 else:
1713 script.append(b'echo %s %d $?\n' % (salt, line))
1754 script.append(b'echo %s %d $?\n' % (salt, line))
1714
1755
1715 activetrace = []
1756 activetrace = []
1716 session = str(uuid.uuid4())
1757 session = str(uuid.uuid4())
1717 if PYTHON3:
1758 if PYTHON3:
1718 session = session.encode('ascii')
1759 session = session.encode('ascii')
1719 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1760 hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
1720 'HGCATAPULTSERVERPIPE'
1761 'HGCATAPULTSERVERPIPE'
1721 )
1762 )
1722
1763
1723 def toggletrace(cmd=None):
1764 def toggletrace(cmd=None):
1724 if not hgcatapult or hgcatapult == os.devnull:
1765 if not hgcatapult or hgcatapult == os.devnull:
1725 return
1766 return
1726
1767
1727 if activetrace:
1768 if activetrace:
1728 script.append(
1769 script.append(
1729 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1770 b'echo END %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1730 % (session, activetrace[0])
1771 % (session, activetrace[0])
1731 )
1772 )
1732 if cmd is None:
1773 if cmd is None:
1733 return
1774 return
1734
1775
1735 if isinstance(cmd, str):
1776 if isinstance(cmd, str):
1736 quoted = shellquote(cmd.strip())
1777 quoted = shellquote(cmd.strip())
1737 else:
1778 else:
1738 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1779 quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
1739 quoted = quoted.replace(b'\\', b'\\\\')
1780 quoted = quoted.replace(b'\\', b'\\\\')
1740 script.append(
1781 script.append(
1741 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1782 b'echo START %s %s >> "$HGTESTCATAPULTSERVERPIPE"\n'
1742 % (session, quoted)
1783 % (session, quoted)
1743 )
1784 )
1744 activetrace[0:] = [quoted]
1785 activetrace[0:] = [quoted]
1745
1786
1746 script = []
1787 script = []
1747
1788
1748 # After we run the shell script, we re-unify the script output
1789 # After we run the shell script, we re-unify the script output
1749 # with non-active parts of the source, with synchronization by our
1790 # with non-active parts of the source, with synchronization by our
1750 # SALT line number markers. The after table contains the non-active
1791 # SALT line number markers. The after table contains the non-active
1751 # components, ordered by line number.
1792 # components, ordered by line number.
1752 after = {}
1793 after = {}
1753
1794
1754 # Expected shell script output.
1795 # Expected shell script output.
1755 expected = {}
1796 expected = {}
1756
1797
1757 pos = prepos = -1
1798 pos = prepos = -1
1758
1799
1759 # True or False when in a true or false conditional section
1800 # True or False when in a true or false conditional section
1760 skipping = None
1801 skipping = None
1761
1802
1762 # We keep track of whether or not we're in a Python block so we
1803 # We keep track of whether or not we're in a Python block so we
1763 # can generate the surrounding doctest magic.
1804 # can generate the surrounding doctest magic.
1764 inpython = False
1805 inpython = False
1765
1806
1766 if self._debug:
1807 if self._debug:
1767 script.append(b'set -x\n')
1808 script.append(b'set -x\n')
1768 if self._hgcommand != b'hg':
1809 if self._hgcommand != b'hg':
1769 script.append(b'alias hg="%s"\n' % self._hgcommand)
1810 script.append(b'alias hg="%s"\n' % self._hgcommand)
1770 if os.getenv('MSYSTEM'):
1811 if os.getenv('MSYSTEM'):
1771 script.append(b'alias pwd="pwd -W"\n')
1812 script.append(b'alias pwd="pwd -W"\n')
1772
1813
1773 if hgcatapult and hgcatapult != os.devnull:
1814 if hgcatapult and hgcatapult != os.devnull:
1774 if PYTHON3:
1815 if PYTHON3:
1775 hgcatapult = hgcatapult.encode('utf8')
1816 hgcatapult = hgcatapult.encode('utf8')
1776 cataname = self.name.encode('utf8')
1817 cataname = self.name.encode('utf8')
1777 else:
1818 else:
1778 cataname = self.name
1819 cataname = self.name
1779
1820
1780 # Kludge: use a while loop to keep the pipe from getting
1821 # Kludge: use a while loop to keep the pipe from getting
1781 # closed by our echo commands. The still-running file gets
1822 # closed by our echo commands. The still-running file gets
1782 # reaped at the end of the script, which causes the while
1823 # reaped at the end of the script, which causes the while
1783 # loop to exit and closes the pipe. Sigh.
1824 # loop to exit and closes the pipe. Sigh.
1784 script.append(
1825 script.append(
1785 b'rtendtracing() {\n'
1826 b'rtendtracing() {\n'
1786 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1827 b' echo END %(session)s %(name)s >> %(catapult)s\n'
1787 b' rm -f "$TESTTMP/.still-running"\n'
1828 b' rm -f "$TESTTMP/.still-running"\n'
1788 b'}\n'
1829 b'}\n'
1789 b'trap "rtendtracing" 0\n'
1830 b'trap "rtendtracing" 0\n'
1790 b'touch "$TESTTMP/.still-running"\n'
1831 b'touch "$TESTTMP/.still-running"\n'
1791 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1832 b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
1792 b'> %(catapult)s &\n'
1833 b'> %(catapult)s &\n'
1793 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1834 b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
1794 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1835 b'echo START %(session)s %(name)s >> %(catapult)s\n'
1795 % {
1836 % {
1796 b'name': cataname,
1837 b'name': cataname,
1797 b'session': session,
1838 b'session': session,
1798 b'catapult': hgcatapult,
1839 b'catapult': hgcatapult,
1799 }
1840 }
1800 )
1841 )
1801
1842
1802 if self._case:
1843 if self._case:
1803 casestr = b'#'.join(self._case)
1844 casestr = b'#'.join(self._case)
1804 if isinstance(casestr, str):
1845 if isinstance(casestr, str):
1805 quoted = shellquote(casestr)
1846 quoted = shellquote(casestr)
1806 else:
1847 else:
1807 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1848 quoted = shellquote(casestr.decode('utf8')).encode('utf8')
1808 script.append(b'TESTCASE=%s\n' % quoted)
1849 script.append(b'TESTCASE=%s\n' % quoted)
1809 script.append(b'export TESTCASE\n')
1850 script.append(b'export TESTCASE\n')
1810
1851
1811 n = 0
1852 n = 0
1812 for n, l in enumerate(lines):
1853 for n, l in enumerate(lines):
1813 if not l.endswith(b'\n'):
1854 if not l.endswith(b'\n'):
1814 l += b'\n'
1855 l += b'\n'
1815 if l.startswith(b'#require'):
1856 if l.startswith(b'#require'):
1816 lsplit = l.split()
1857 lsplit = l.split()
1817 if len(lsplit) < 2 or lsplit[0] != b'#require':
1858 if len(lsplit) < 2 or lsplit[0] != b'#require':
1818 after.setdefault(pos, []).append(
1859 after.setdefault(pos, []).append(
1819 b' !!! invalid #require\n'
1860 b' !!! invalid #require\n'
1820 )
1861 )
1821 if not skipping:
1862 if not skipping:
1822 haveresult, message = self._hghave(lsplit[1:])
1863 haveresult, message = self._hghave(lsplit[1:])
1823 if not haveresult:
1864 if not haveresult:
1824 script = [b'echo "%s"\nexit 80\n' % message]
1865 script = [b'echo "%s"\nexit 80\n' % message]
1825 break
1866 break
1826 after.setdefault(pos, []).append(l)
1867 after.setdefault(pos, []).append(l)
1827 elif l.startswith(b'#if'):
1868 elif l.startswith(b'#if'):
1828 lsplit = l.split()
1869 lsplit = l.split()
1829 if len(lsplit) < 2 or lsplit[0] != b'#if':
1870 if len(lsplit) < 2 or lsplit[0] != b'#if':
1830 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1871 after.setdefault(pos, []).append(b' !!! invalid #if\n')
1831 if skipping is not None:
1872 if skipping is not None:
1832 after.setdefault(pos, []).append(b' !!! nested #if\n')
1873 after.setdefault(pos, []).append(b' !!! nested #if\n')
1833 skipping = not self._iftest(lsplit[1:])
1874 skipping = not self._iftest(lsplit[1:])
1834 after.setdefault(pos, []).append(l)
1875 after.setdefault(pos, []).append(l)
1835 elif l.startswith(b'#else'):
1876 elif l.startswith(b'#else'):
1836 if skipping is None:
1877 if skipping is None:
1837 after.setdefault(pos, []).append(b' !!! missing #if\n')
1878 after.setdefault(pos, []).append(b' !!! missing #if\n')
1838 skipping = not skipping
1879 skipping = not skipping
1839 after.setdefault(pos, []).append(l)
1880 after.setdefault(pos, []).append(l)
1840 elif l.startswith(b'#endif'):
1881 elif l.startswith(b'#endif'):
1841 if skipping is None:
1882 if skipping is None:
1842 after.setdefault(pos, []).append(b' !!! missing #if\n')
1883 after.setdefault(pos, []).append(b' !!! missing #if\n')
1843 skipping = None
1884 skipping = None
1844 after.setdefault(pos, []).append(l)
1885 after.setdefault(pos, []).append(l)
1845 elif skipping:
1886 elif skipping:
1846 after.setdefault(pos, []).append(l)
1887 after.setdefault(pos, []).append(l)
1847 elif l.startswith(b' >>> '): # python inlines
1888 elif l.startswith(b' >>> '): # python inlines
1848 after.setdefault(pos, []).append(l)
1889 after.setdefault(pos, []).append(l)
1849 prepos = pos
1890 prepos = pos
1850 pos = n
1891 pos = n
1851 if not inpython:
1892 if not inpython:
1852 # We've just entered a Python block. Add the header.
1893 # We've just entered a Python block. Add the header.
1853 inpython = True
1894 inpython = True
1854 addsalt(prepos, False) # Make sure we report the exit code.
1895 addsalt(prepos, False) # Make sure we report the exit code.
1855 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1896 script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
1856 addsalt(n, True)
1897 addsalt(n, True)
1857 script.append(l[2:])
1898 script.append(l[2:])
1858 elif l.startswith(b' ... '): # python inlines
1899 elif l.startswith(b' ... '): # python inlines
1859 after.setdefault(prepos, []).append(l)
1900 after.setdefault(prepos, []).append(l)
1860 script.append(l[2:])
1901 script.append(l[2:])
1861 elif l.startswith(b' $ '): # commands
1902 elif l.startswith(b' $ '): # commands
1862 if inpython:
1903 if inpython:
1863 script.append(b'EOF\n')
1904 script.append(b'EOF\n')
1864 inpython = False
1905 inpython = False
1865 after.setdefault(pos, []).append(l)
1906 after.setdefault(pos, []).append(l)
1866 prepos = pos
1907 prepos = pos
1867 pos = n
1908 pos = n
1868 addsalt(n, False)
1909 addsalt(n, False)
1869 rawcmd = l[4:]
1910 rawcmd = l[4:]
1870 cmd = rawcmd.split()
1911 cmd = rawcmd.split()
1871 toggletrace(rawcmd)
1912 toggletrace(rawcmd)
1872 if len(cmd) == 2 and cmd[0] == b'cd':
1913 if len(cmd) == 2 and cmd[0] == b'cd':
1873 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1914 rawcmd = b'cd %s || exit 1\n' % cmd[1]
1874 script.append(rawcmd)
1915 script.append(rawcmd)
1875 elif l.startswith(b' > '): # continuations
1916 elif l.startswith(b' > '): # continuations
1876 after.setdefault(prepos, []).append(l)
1917 after.setdefault(prepos, []).append(l)
1877 script.append(l[4:])
1918 script.append(l[4:])
1878 elif l.startswith(b' '): # results
1919 elif l.startswith(b' '): # results
1879 # Queue up a list of expected results.
1920 # Queue up a list of expected results.
1880 expected.setdefault(pos, []).append(l[2:])
1921 expected.setdefault(pos, []).append(l[2:])
1881 else:
1922 else:
1882 if inpython:
1923 if inpython:
1883 script.append(b'EOF\n')
1924 script.append(b'EOF\n')
1884 inpython = False
1925 inpython = False
1885 # Non-command/result. Queue up for merged output.
1926 # Non-command/result. Queue up for merged output.
1886 after.setdefault(pos, []).append(l)
1927 after.setdefault(pos, []).append(l)
1887
1928
1888 if inpython:
1929 if inpython:
1889 script.append(b'EOF\n')
1930 script.append(b'EOF\n')
1890 if skipping is not None:
1931 if skipping is not None:
1891 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1932 after.setdefault(pos, []).append(b' !!! missing #endif\n')
1892 addsalt(n + 1, False)
1933 addsalt(n + 1, False)
1893 # Need to end any current per-command trace
1934 # Need to end any current per-command trace
1894 if activetrace:
1935 if activetrace:
1895 toggletrace()
1936 toggletrace()
1896 return salt, script, after, expected
1937 return salt, script, after, expected
1897
1938
1898 def _processoutput(self, exitcode, output, salt, after, expected):
1939 def _processoutput(self, exitcode, output, salt, after, expected):
1899 # Merge the script output back into a unified test.
1940 # Merge the script output back into a unified test.
1900 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1941 warnonly = WARN_UNDEFINED # 1: not yet; 2: yes; 3: for sure not
1901 if exitcode != 0:
1942 if exitcode != 0:
1902 warnonly = WARN_NO
1943 warnonly = WARN_NO
1903
1944
1904 pos = -1
1945 pos = -1
1905 postout = []
1946 postout = []
1906 for out_rawline in output:
1947 for out_rawline in output:
1907 out_line, cmd_line = out_rawline, None
1948 out_line, cmd_line = out_rawline, None
1908 if salt in out_rawline:
1949 if salt in out_rawline:
1909 out_line, cmd_line = out_rawline.split(salt, 1)
1950 out_line, cmd_line = out_rawline.split(salt, 1)
1910
1951
1911 pos, postout, warnonly = self._process_out_line(
1952 pos, postout, warnonly = self._process_out_line(
1912 out_line, pos, postout, expected, warnonly
1953 out_line, pos, postout, expected, warnonly
1913 )
1954 )
1914 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1955 pos, postout = self._process_cmd_line(cmd_line, pos, postout, after)
1915
1956
1916 if pos in after:
1957 if pos in after:
1917 postout += after.pop(pos)
1958 postout += after.pop(pos)
1918
1959
1919 if warnonly == WARN_YES:
1960 if warnonly == WARN_YES:
1920 exitcode = False # Set exitcode to warned.
1961 exitcode = False # Set exitcode to warned.
1921
1962
1922 return exitcode, postout
1963 return exitcode, postout
1923
1964
1924 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1965 def _process_out_line(self, out_line, pos, postout, expected, warnonly):
1925 while out_line:
1966 while out_line:
1926 if not out_line.endswith(b'\n'):
1967 if not out_line.endswith(b'\n'):
1927 out_line += b' (no-eol)\n'
1968 out_line += b' (no-eol)\n'
1928
1969
1929 # Find the expected output at the current position.
1970 # Find the expected output at the current position.
1930 els = [None]
1971 els = [None]
1931 if expected.get(pos, None):
1972 if expected.get(pos, None):
1932 els = expected[pos]
1973 els = expected[pos]
1933
1974
1934 optional = []
1975 optional = []
1935 for i, el in enumerate(els):
1976 for i, el in enumerate(els):
1936 r = False
1977 r = False
1937 if el:
1978 if el:
1938 r, exact = self.linematch(el, out_line)
1979 r, exact = self.linematch(el, out_line)
1939 if isinstance(r, str):
1980 if isinstance(r, str):
1940 if r == '-glob':
1981 if r == '-glob':
1941 out_line = ''.join(el.rsplit(' (glob)', 1))
1982 out_line = ''.join(el.rsplit(' (glob)', 1))
1942 r = '' # Warn only this line.
1983 r = '' # Warn only this line.
1943 elif r == "retry":
1984 elif r == "retry":
1944 postout.append(b' ' + el)
1985 postout.append(b' ' + el)
1945 else:
1986 else:
1946 log('\ninfo, unknown linematch result: %r\n' % r)
1987 log('\ninfo, unknown linematch result: %r\n' % r)
1947 r = False
1988 r = False
1948 if r:
1989 if r:
1949 els.pop(i)
1990 els.pop(i)
1950 break
1991 break
1951 if el:
1992 if el:
1952 if isoptional(el):
1993 if isoptional(el):
1953 optional.append(i)
1994 optional.append(i)
1954 else:
1995 else:
1955 m = optline.match(el)
1996 m = optline.match(el)
1956 if m:
1997 if m:
1957 conditions = [c for c in m.group(2).split(b' ')]
1998 conditions = [c for c in m.group(2).split(b' ')]
1958
1999
1959 if not self._iftest(conditions):
2000 if not self._iftest(conditions):
1960 optional.append(i)
2001 optional.append(i)
1961 if exact:
2002 if exact:
1962 # Don't allow line to be matches against a later
2003 # Don't allow line to be matches against a later
1963 # line in the output
2004 # line in the output
1964 els.pop(i)
2005 els.pop(i)
1965 break
2006 break
1966
2007
1967 if r:
2008 if r:
1968 if r == "retry":
2009 if r == "retry":
1969 continue
2010 continue
1970 # clean up any optional leftovers
2011 # clean up any optional leftovers
1971 for i in optional:
2012 for i in optional:
1972 postout.append(b' ' + els[i])
2013 postout.append(b' ' + els[i])
1973 for i in reversed(optional):
2014 for i in reversed(optional):
1974 del els[i]
2015 del els[i]
1975 postout.append(b' ' + el)
2016 postout.append(b' ' + el)
1976 else:
2017 else:
1977 if self.NEEDESCAPE(out_line):
2018 if self.NEEDESCAPE(out_line):
1978 out_line = TTest._stringescape(
2019 out_line = TTest._stringescape(
1979 b'%s (esc)\n' % out_line.rstrip(b'\n')
2020 b'%s (esc)\n' % out_line.rstrip(b'\n')
1980 )
2021 )
1981 postout.append(b' ' + out_line) # Let diff deal with it.
2022 postout.append(b' ' + out_line) # Let diff deal with it.
1982 if r != '': # If line failed.
2023 if r != '': # If line failed.
1983 warnonly = WARN_NO
2024 warnonly = WARN_NO
1984 elif warnonly == WARN_UNDEFINED:
2025 elif warnonly == WARN_UNDEFINED:
1985 warnonly = WARN_YES
2026 warnonly = WARN_YES
1986 break
2027 break
1987 else:
2028 else:
1988 # clean up any optional leftovers
2029 # clean up any optional leftovers
1989 while expected.get(pos, None):
2030 while expected.get(pos, None):
1990 el = expected[pos].pop(0)
2031 el = expected[pos].pop(0)
1991 if el:
2032 if el:
1992 if not isoptional(el):
2033 if not isoptional(el):
1993 m = optline.match(el)
2034 m = optline.match(el)
1994 if m:
2035 if m:
1995 conditions = [c for c in m.group(2).split(b' ')]
2036 conditions = [c for c in m.group(2).split(b' ')]
1996
2037
1997 if self._iftest(conditions):
2038 if self._iftest(conditions):
1998 # Don't append as optional line
2039 # Don't append as optional line
1999 continue
2040 continue
2000 else:
2041 else:
2001 continue
2042 continue
2002 postout.append(b' ' + el)
2043 postout.append(b' ' + el)
2003 return pos, postout, warnonly
2044 return pos, postout, warnonly
2004
2045
2005 def _process_cmd_line(self, cmd_line, pos, postout, after):
2046 def _process_cmd_line(self, cmd_line, pos, postout, after):
2006 """process a "command" part of a line from unified test output"""
2047 """process a "command" part of a line from unified test output"""
2007 if cmd_line:
2048 if cmd_line:
2008 # Add on last return code.
2049 # Add on last return code.
2009 ret = int(cmd_line.split()[1])
2050 ret = int(cmd_line.split()[1])
2010 if ret != 0:
2051 if ret != 0:
2011 postout.append(b' [%d]\n' % ret)
2052 postout.append(b' [%d]\n' % ret)
2012 if pos in after:
2053 if pos in after:
2013 # Merge in non-active test bits.
2054 # Merge in non-active test bits.
2014 postout += after.pop(pos)
2055 postout += after.pop(pos)
2015 pos = int(cmd_line.split()[0])
2056 pos = int(cmd_line.split()[0])
2016 return pos, postout
2057 return pos, postout
2017
2058
2018 @staticmethod
2059 @staticmethod
2019 def rematch(el, l):
2060 def rematch(el, l):
2020 try:
2061 try:
2021 # parse any flags at the beginning of the regex. Only 'i' is
2062 # parse any flags at the beginning of the regex. Only 'i' is
2022 # supported right now, but this should be easy to extend.
2063 # supported right now, but this should be easy to extend.
2023 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2064 flags, el = re.match(br'^(\(\?i\))?(.*)', el).groups()[0:2]
2024 flags = flags or b''
2065 flags = flags or b''
2025 el = flags + b'(?:' + el + b')'
2066 el = flags + b'(?:' + el + b')'
2026 # use \Z to ensure that the regex matches to the end of the string
2067 # use \Z to ensure that the regex matches to the end of the string
2027 if os.name == 'nt':
2068 if os.name == 'nt':
2028 return re.match(el + br'\r?\n\Z', l)
2069 return re.match(el + br'\r?\n\Z', l)
2029 return re.match(el + br'\n\Z', l)
2070 return re.match(el + br'\n\Z', l)
2030 except re.error:
2071 except re.error:
2031 # el is an invalid regex
2072 # el is an invalid regex
2032 return False
2073 return False
2033
2074
2034 @staticmethod
2075 @staticmethod
2035 def globmatch(el, l):
2076 def globmatch(el, l):
2036 # The only supported special characters are * and ? plus / which also
2077 # The only supported special characters are * and ? plus / which also
2037 # matches \ on windows. Escaping of these characters is supported.
2078 # matches \ on windows. Escaping of these characters is supported.
2038 if el + b'\n' == l:
2079 if el + b'\n' == l:
2039 if os.altsep:
2080 if os.altsep:
2040 # matching on "/" is not needed for this line
2081 # matching on "/" is not needed for this line
2041 for pat in checkcodeglobpats:
2082 for pat in checkcodeglobpats:
2042 if pat.match(el):
2083 if pat.match(el):
2043 return True
2084 return True
2044 return b'-glob'
2085 return b'-glob'
2045 return True
2086 return True
2046 el = el.replace(b'$LOCALIP', b'*')
2087 el = el.replace(b'$LOCALIP', b'*')
2047 i, n = 0, len(el)
2088 i, n = 0, len(el)
2048 res = b''
2089 res = b''
2049 while i < n:
2090 while i < n:
2050 c = el[i : i + 1]
2091 c = el[i : i + 1]
2051 i += 1
2092 i += 1
2052 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2093 if c == b'\\' and i < n and el[i : i + 1] in b'*?\\/':
2053 res += el[i - 1 : i + 1]
2094 res += el[i - 1 : i + 1]
2054 i += 1
2095 i += 1
2055 elif c == b'*':
2096 elif c == b'*':
2056 res += b'.*'
2097 res += b'.*'
2057 elif c == b'?':
2098 elif c == b'?':
2058 res += b'.'
2099 res += b'.'
2059 elif c == b'/' and os.altsep:
2100 elif c == b'/' and os.altsep:
2060 res += b'[/\\\\]'
2101 res += b'[/\\\\]'
2061 else:
2102 else:
2062 res += re.escape(c)
2103 res += re.escape(c)
2063 return TTest.rematch(res, l)
2104 return TTest.rematch(res, l)
2064
2105
2065 def linematch(self, el, l):
2106 def linematch(self, el, l):
2066 if el == l: # perfect match (fast)
2107 if el == l: # perfect match (fast)
2067 return True, True
2108 return True, True
2068 retry = False
2109 retry = False
2069 if isoptional(el):
2110 if isoptional(el):
2070 retry = "retry"
2111 retry = "retry"
2071 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2112 el = el[: -len(MARK_OPTIONAL)] + b"\n"
2072 else:
2113 else:
2073 m = optline.match(el)
2114 m = optline.match(el)
2074 if m:
2115 if m:
2075 conditions = [c for c in m.group(2).split(b' ')]
2116 conditions = [c for c in m.group(2).split(b' ')]
2076
2117
2077 el = m.group(1) + b"\n"
2118 el = m.group(1) + b"\n"
2078 if not self._iftest(conditions):
2119 if not self._iftest(conditions):
2079 # listed feature missing, should not match
2120 # listed feature missing, should not match
2080 return "retry", False
2121 return "retry", False
2081
2122
2082 if el.endswith(b" (esc)\n"):
2123 if el.endswith(b" (esc)\n"):
2083 if PYTHON3:
2124 if PYTHON3:
2084 el = el[:-7].decode('unicode_escape') + '\n'
2125 el = el[:-7].decode('unicode_escape') + '\n'
2085 el = el.encode('latin-1')
2126 el = el.encode('latin-1')
2086 else:
2127 else:
2087 el = el[:-7].decode('string-escape') + '\n'
2128 el = el[:-7].decode('string-escape') + '\n'
2088 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2129 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
2089 return True, True
2130 return True, True
2090 if el.endswith(b" (re)\n"):
2131 if el.endswith(b" (re)\n"):
2091 return (TTest.rematch(el[:-6], l) or retry), False
2132 return (TTest.rematch(el[:-6], l) or retry), False
2092 if el.endswith(b" (glob)\n"):
2133 if el.endswith(b" (glob)\n"):
2093 # ignore '(glob)' added to l by 'replacements'
2134 # ignore '(glob)' added to l by 'replacements'
2094 if l.endswith(b" (glob)\n"):
2135 if l.endswith(b" (glob)\n"):
2095 l = l[:-8] + b"\n"
2136 l = l[:-8] + b"\n"
2096 return (TTest.globmatch(el[:-8], l) or retry), False
2137 return (TTest.globmatch(el[:-8], l) or retry), False
2097 if os.altsep:
2138 if os.altsep:
2098 _l = l.replace(b'\\', b'/')
2139 _l = l.replace(b'\\', b'/')
2099 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2140 if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
2100 return True, True
2141 return True, True
2101 return retry, True
2142 return retry, True
2102
2143
2103 @staticmethod
2144 @staticmethod
2104 def parsehghaveoutput(lines):
2145 def parsehghaveoutput(lines):
2105 """Parse hghave log lines.
2146 """Parse hghave log lines.
2106
2147
2107 Return tuple of lists (missing, failed):
2148 Return tuple of lists (missing, failed):
2108 * the missing/unknown features
2149 * the missing/unknown features
2109 * the features for which existence check failed"""
2150 * the features for which existence check failed"""
2110 missing = []
2151 missing = []
2111 failed = []
2152 failed = []
2112 for line in lines:
2153 for line in lines:
2113 if line.startswith(TTest.SKIPPED_PREFIX):
2154 if line.startswith(TTest.SKIPPED_PREFIX):
2114 line = line.splitlines()[0]
2155 line = line.splitlines()[0]
2115 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2156 missing.append(_bytes2sys(line[len(TTest.SKIPPED_PREFIX) :]))
2116 elif line.startswith(TTest.FAILED_PREFIX):
2157 elif line.startswith(TTest.FAILED_PREFIX):
2117 line = line.splitlines()[0]
2158 line = line.splitlines()[0]
2118 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2159 failed.append(_bytes2sys(line[len(TTest.FAILED_PREFIX) :]))
2119
2160
2120 return missing, failed
2161 return missing, failed
2121
2162
2122 @staticmethod
2163 @staticmethod
2123 def _escapef(m):
2164 def _escapef(m):
2124 return TTest.ESCAPEMAP[m.group(0)]
2165 return TTest.ESCAPEMAP[m.group(0)]
2125
2166
2126 @staticmethod
2167 @staticmethod
2127 def _stringescape(s):
2168 def _stringescape(s):
2128 return TTest.ESCAPESUB(TTest._escapef, s)
2169 return TTest.ESCAPESUB(TTest._escapef, s)
2129
2170
2130
2171
2131 iolock = threading.RLock()
2172 iolock = threading.RLock()
2132 firstlock = threading.RLock()
2173 firstlock = threading.RLock()
2133 firsterror = False
2174 firsterror = False
2134
2175
2135
2176
2136 class TestResult(unittest._TextTestResult):
2177 class TestResult(unittest._TextTestResult):
2137 """Holds results when executing via unittest."""
2178 """Holds results when executing via unittest."""
2138
2179
2139 # Don't worry too much about accessing the non-public _TextTestResult.
2180 # Don't worry too much about accessing the non-public _TextTestResult.
2140 # It is relatively common in Python testing tools.
2181 # It is relatively common in Python testing tools.
2141 def __init__(self, options, *args, **kwargs):
2182 def __init__(self, options, *args, **kwargs):
2142 super(TestResult, self).__init__(*args, **kwargs)
2183 super(TestResult, self).__init__(*args, **kwargs)
2143
2184
2144 self._options = options
2185 self._options = options
2145
2186
2146 # unittest.TestResult didn't have skipped until 2.7. We need to
2187 # unittest.TestResult didn't have skipped until 2.7. We need to
2147 # polyfill it.
2188 # polyfill it.
2148 self.skipped = []
2189 self.skipped = []
2149
2190
2150 # We have a custom "ignored" result that isn't present in any Python
2191 # We have a custom "ignored" result that isn't present in any Python
2151 # unittest implementation. It is very similar to skipped. It may make
2192 # unittest implementation. It is very similar to skipped. It may make
2152 # sense to map it into skip some day.
2193 # sense to map it into skip some day.
2153 self.ignored = []
2194 self.ignored = []
2154
2195
2155 self.times = []
2196 self.times = []
2156 self._firststarttime = None
2197 self._firststarttime = None
2157 # Data stored for the benefit of generating xunit reports.
2198 # Data stored for the benefit of generating xunit reports.
2158 self.successes = []
2199 self.successes = []
2159 self.faildata = {}
2200 self.faildata = {}
2160
2201
2161 if options.color == 'auto':
2202 if options.color == 'auto':
2162 self.color = pygmentspresent and self.stream.isatty()
2203 self.color = pygmentspresent and self.stream.isatty()
2163 elif options.color == 'never':
2204 elif options.color == 'never':
2164 self.color = False
2205 self.color = False
2165 else: # 'always', for testing purposes
2206 else: # 'always', for testing purposes
2166 self.color = pygmentspresent
2207 self.color = pygmentspresent
2167
2208
2168 def onStart(self, test):
2209 def onStart(self, test):
2169 """Can be overriden by custom TestResult"""
2210 """Can be overriden by custom TestResult"""
2170
2211
2171 def onEnd(self):
2212 def onEnd(self):
2172 """Can be overriden by custom TestResult"""
2213 """Can be overriden by custom TestResult"""
2173
2214
2174 def addFailure(self, test, reason):
2215 def addFailure(self, test, reason):
2175 self.failures.append((test, reason))
2216 self.failures.append((test, reason))
2176
2217
2177 if self._options.first:
2218 if self._options.first:
2178 self.stop()
2219 self.stop()
2179 else:
2220 else:
2180 with iolock:
2221 with iolock:
2181 if reason == "timed out":
2222 if reason == "timed out":
2182 self.stream.write('t')
2223 self.stream.write('t')
2183 else:
2224 else:
2184 if not self._options.nodiff:
2225 if not self._options.nodiff:
2185 self.stream.write('\n')
2226 self.stream.write('\n')
2186 # Exclude the '\n' from highlighting to lex correctly
2227 # Exclude the '\n' from highlighting to lex correctly
2187 formatted = 'ERROR: %s output changed\n' % test
2228 formatted = 'ERROR: %s output changed\n' % test
2188 self.stream.write(highlightmsg(formatted, self.color))
2229 self.stream.write(highlightmsg(formatted, self.color))
2189 self.stream.write('!')
2230 self.stream.write('!')
2190
2231
2191 self.stream.flush()
2232 self.stream.flush()
2192
2233
2193 def addSuccess(self, test):
2234 def addSuccess(self, test):
2194 with iolock:
2235 with iolock:
2195 super(TestResult, self).addSuccess(test)
2236 super(TestResult, self).addSuccess(test)
2196 self.successes.append(test)
2237 self.successes.append(test)
2197
2238
2198 def addError(self, test, err):
2239 def addError(self, test, err):
2199 super(TestResult, self).addError(test, err)
2240 super(TestResult, self).addError(test, err)
2200 if self._options.first:
2241 if self._options.first:
2201 self.stop()
2242 self.stop()
2202
2243
2203 # Polyfill.
2244 # Polyfill.
2204 def addSkip(self, test, reason):
2245 def addSkip(self, test, reason):
2205 self.skipped.append((test, reason))
2246 self.skipped.append((test, reason))
2206 with iolock:
2247 with iolock:
2207 if self.showAll:
2248 if self.showAll:
2208 self.stream.writeln('skipped %s' % reason)
2249 self.stream.writeln('skipped %s' % reason)
2209 else:
2250 else:
2210 self.stream.write('s')
2251 self.stream.write('s')
2211 self.stream.flush()
2252 self.stream.flush()
2212
2253
2213 def addIgnore(self, test, reason):
2254 def addIgnore(self, test, reason):
2214 self.ignored.append((test, reason))
2255 self.ignored.append((test, reason))
2215 with iolock:
2256 with iolock:
2216 if self.showAll:
2257 if self.showAll:
2217 self.stream.writeln('ignored %s' % reason)
2258 self.stream.writeln('ignored %s' % reason)
2218 else:
2259 else:
2219 if reason not in ('not retesting', "doesn't match keyword"):
2260 if reason not in ('not retesting', "doesn't match keyword"):
2220 self.stream.write('i')
2261 self.stream.write('i')
2221 else:
2262 else:
2222 self.testsRun += 1
2263 self.testsRun += 1
2223 self.stream.flush()
2264 self.stream.flush()
2224
2265
2225 def addOutputMismatch(self, test, ret, got, expected):
2266 def addOutputMismatch(self, test, ret, got, expected):
2226 """Record a mismatch in test output for a particular test."""
2267 """Record a mismatch in test output for a particular test."""
2227 if self.shouldStop or firsterror:
2268 if self.shouldStop or firsterror:
2228 # don't print, some other test case already failed and
2269 # don't print, some other test case already failed and
2229 # printed, we're just stale and probably failed due to our
2270 # printed, we're just stale and probably failed due to our
2230 # temp dir getting cleaned up.
2271 # temp dir getting cleaned up.
2231 return
2272 return
2232
2273
2233 accepted = False
2274 accepted = False
2234 lines = []
2275 lines = []
2235
2276
2236 with iolock:
2277 with iolock:
2237 if self._options.nodiff:
2278 if self._options.nodiff:
2238 pass
2279 pass
2239 elif self._options.view:
2280 elif self._options.view:
2240 v = self._options.view
2281 v = self._options.view
2241 subprocess.call(
2282 subprocess.call(
2242 r'"%s" "%s" "%s"'
2283 r'"%s" "%s" "%s"'
2243 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2284 % (v, _bytes2sys(test.refpath), _bytes2sys(test.errpath)),
2244 shell=True,
2285 shell=True,
2245 )
2286 )
2246 else:
2287 else:
2247 servefail, lines = getdiff(
2288 servefail, lines = getdiff(
2248 expected, got, test.refpath, test.errpath
2289 expected, got, test.refpath, test.errpath
2249 )
2290 )
2250 self.stream.write('\n')
2291 self.stream.write('\n')
2251 for line in lines:
2292 for line in lines:
2252 line = highlightdiff(line, self.color)
2293 line = highlightdiff(line, self.color)
2253 if PYTHON3:
2294 if PYTHON3:
2254 self.stream.flush()
2295 self.stream.flush()
2255 self.stream.buffer.write(line)
2296 self.stream.buffer.write(line)
2256 self.stream.buffer.flush()
2297 self.stream.buffer.flush()
2257 else:
2298 else:
2258 self.stream.write(line)
2299 self.stream.write(line)
2259 self.stream.flush()
2300 self.stream.flush()
2260
2301
2261 if servefail:
2302 if servefail:
2262 raise test.failureException(
2303 raise test.failureException(
2263 'server failed to start (HGPORT=%s)' % test._startport
2304 'server failed to start (HGPORT=%s)' % test._startport
2264 )
2305 )
2265
2306
2266 # handle interactive prompt without releasing iolock
2307 # handle interactive prompt without releasing iolock
2267 if self._options.interactive:
2308 if self._options.interactive:
2268 if test.readrefout() != expected:
2309 if test.readrefout() != expected:
2269 self.stream.write(
2310 self.stream.write(
2270 'Reference output has changed (run again to prompt '
2311 'Reference output has changed (run again to prompt '
2271 'changes)'
2312 'changes)'
2272 )
2313 )
2273 else:
2314 else:
2274 self.stream.write('Accept this change? [y/N] ')
2315 self.stream.write('Accept this change? [y/N] ')
2275 self.stream.flush()
2316 self.stream.flush()
2276 answer = sys.stdin.readline().strip()
2317 answer = sys.stdin.readline().strip()
2277 if answer.lower() in ('y', 'yes'):
2318 if answer.lower() in ('y', 'yes'):
2278 if test.path.endswith(b'.t'):
2319 if test.path.endswith(b'.t'):
2279 rename(test.errpath, test.path)
2320 rename(test.errpath, test.path)
2280 else:
2321 else:
2281 rename(test.errpath, b'%s.out' % test.path)
2322 rename(test.errpath, b'%s.out' % test.path)
2282 accepted = True
2323 accepted = True
2283 if not accepted:
2324 if not accepted:
2284 self.faildata[test.name] = b''.join(lines)
2325 self.faildata[test.name] = b''.join(lines)
2285
2326
2286 return accepted
2327 return accepted
2287
2328
2288 def startTest(self, test):
2329 def startTest(self, test):
2289 super(TestResult, self).startTest(test)
2330 super(TestResult, self).startTest(test)
2290
2331
2291 # os.times module computes the user time and system time spent by
2332 # os.times module computes the user time and system time spent by
2292 # child's processes along with real elapsed time taken by a process.
2333 # child's processes along with real elapsed time taken by a process.
2293 # This module has one limitation. It can only work for Linux user
2334 # This module has one limitation. It can only work for Linux user
2294 # and not for Windows. Hence why we fall back to another function
2335 # and not for Windows. Hence why we fall back to another function
2295 # for wall time calculations.
2336 # for wall time calculations.
2296 test.started_times = os.times()
2337 test.started_times = os.times()
2297 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2338 # TODO use a monotonic clock once support for Python 2.7 is dropped.
2298 test.started_time = time.time()
2339 test.started_time = time.time()
2299 if self._firststarttime is None: # thread racy but irrelevant
2340 if self._firststarttime is None: # thread racy but irrelevant
2300 self._firststarttime = test.started_time
2341 self._firststarttime = test.started_time
2301
2342
2302 def stopTest(self, test, interrupted=False):
2343 def stopTest(self, test, interrupted=False):
2303 super(TestResult, self).stopTest(test)
2344 super(TestResult, self).stopTest(test)
2304
2345
2305 test.stopped_times = os.times()
2346 test.stopped_times = os.times()
2306 stopped_time = time.time()
2347 stopped_time = time.time()
2307
2348
2308 starttime = test.started_times
2349 starttime = test.started_times
2309 endtime = test.stopped_times
2350 endtime = test.stopped_times
2310 origin = self._firststarttime
2351 origin = self._firststarttime
2311 self.times.append(
2352 self.times.append(
2312 (
2353 (
2313 test.name,
2354 test.name,
2314 endtime[2] - starttime[2], # user space CPU time
2355 endtime[2] - starttime[2], # user space CPU time
2315 endtime[3] - starttime[3], # sys space CPU time
2356 endtime[3] - starttime[3], # sys space CPU time
2316 stopped_time - test.started_time, # real time
2357 stopped_time - test.started_time, # real time
2317 test.started_time - origin, # start date in run context
2358 test.started_time - origin, # start date in run context
2318 stopped_time - origin, # end date in run context
2359 stopped_time - origin, # end date in run context
2319 )
2360 )
2320 )
2361 )
2321
2362
2322 if interrupted:
2363 if interrupted:
2323 with iolock:
2364 with iolock:
2324 self.stream.writeln(
2365 self.stream.writeln(
2325 'INTERRUPTED: %s (after %d seconds)'
2366 'INTERRUPTED: %s (after %d seconds)'
2326 % (test.name, self.times[-1][3])
2367 % (test.name, self.times[-1][3])
2327 )
2368 )
2328
2369
2329
2370
2330 def getTestResult():
2371 def getTestResult():
2331 """
2372 """
2332 Returns the relevant test result
2373 Returns the relevant test result
2333 """
2374 """
2334 if "CUSTOM_TEST_RESULT" in os.environ:
2375 if "CUSTOM_TEST_RESULT" in os.environ:
2335 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2376 testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
2336 return testresultmodule.TestResult
2377 return testresultmodule.TestResult
2337 else:
2378 else:
2338 return TestResult
2379 return TestResult
2339
2380
2340
2381
2341 class TestSuite(unittest.TestSuite):
2382 class TestSuite(unittest.TestSuite):
2342 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2383 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
2343
2384
2344 def __init__(
2385 def __init__(
2345 self,
2386 self,
2346 testdir,
2387 testdir,
2347 jobs=1,
2388 jobs=1,
2348 whitelist=None,
2389 whitelist=None,
2349 blacklist=None,
2390 blacklist=None,
2350 keywords=None,
2391 keywords=None,
2351 loop=False,
2392 loop=False,
2352 runs_per_test=1,
2393 runs_per_test=1,
2353 loadtest=None,
2394 loadtest=None,
2354 showchannels=False,
2395 showchannels=False,
2355 *args,
2396 *args,
2356 **kwargs
2397 **kwargs
2357 ):
2398 ):
2358 """Create a new instance that can run tests with a configuration.
2399 """Create a new instance that can run tests with a configuration.
2359
2400
2360 testdir specifies the directory where tests are executed from. This
2401 testdir specifies the directory where tests are executed from. This
2361 is typically the ``tests`` directory from Mercurial's source
2402 is typically the ``tests`` directory from Mercurial's source
2362 repository.
2403 repository.
2363
2404
2364 jobs specifies the number of jobs to run concurrently. Each test
2405 jobs specifies the number of jobs to run concurrently. Each test
2365 executes on its own thread. Tests actually spawn new processes, so
2406 executes on its own thread. Tests actually spawn new processes, so
2366 state mutation should not be an issue.
2407 state mutation should not be an issue.
2367
2408
2368 If there is only one job, it will use the main thread.
2409 If there is only one job, it will use the main thread.
2369
2410
2370 whitelist and blacklist denote tests that have been whitelisted and
2411 whitelist and blacklist denote tests that have been whitelisted and
2371 blacklisted, respectively. These arguments don't belong in TestSuite.
2412 blacklisted, respectively. These arguments don't belong in TestSuite.
2372 Instead, whitelist and blacklist should be handled by the thing that
2413 Instead, whitelist and blacklist should be handled by the thing that
2373 populates the TestSuite with tests. They are present to preserve
2414 populates the TestSuite with tests. They are present to preserve
2374 backwards compatible behavior which reports skipped tests as part
2415 backwards compatible behavior which reports skipped tests as part
2375 of the results.
2416 of the results.
2376
2417
2377 keywords denotes key words that will be used to filter which tests
2418 keywords denotes key words that will be used to filter which tests
2378 to execute. This arguably belongs outside of TestSuite.
2419 to execute. This arguably belongs outside of TestSuite.
2379
2420
2380 loop denotes whether to loop over tests forever.
2421 loop denotes whether to loop over tests forever.
2381 """
2422 """
2382 super(TestSuite, self).__init__(*args, **kwargs)
2423 super(TestSuite, self).__init__(*args, **kwargs)
2383
2424
2384 self._jobs = jobs
2425 self._jobs = jobs
2385 self._whitelist = whitelist
2426 self._whitelist = whitelist
2386 self._blacklist = blacklist
2427 self._blacklist = blacklist
2387 self._keywords = keywords
2428 self._keywords = keywords
2388 self._loop = loop
2429 self._loop = loop
2389 self._runs_per_test = runs_per_test
2430 self._runs_per_test = runs_per_test
2390 self._loadtest = loadtest
2431 self._loadtest = loadtest
2391 self._showchannels = showchannels
2432 self._showchannels = showchannels
2392
2433
2393 def run(self, result):
2434 def run(self, result):
2394 # We have a number of filters that need to be applied. We do this
2435 # We have a number of filters that need to be applied. We do this
2395 # here instead of inside Test because it makes the running logic for
2436 # here instead of inside Test because it makes the running logic for
2396 # Test simpler.
2437 # Test simpler.
2397 tests = []
2438 tests = []
2398 num_tests = [0]
2439 num_tests = [0]
2399 for test in self._tests:
2440 for test in self._tests:
2400
2441
2401 def get():
2442 def get():
2402 num_tests[0] += 1
2443 num_tests[0] += 1
2403 if getattr(test, 'should_reload', False):
2444 if getattr(test, 'should_reload', False):
2404 return self._loadtest(test, num_tests[0])
2445 return self._loadtest(test, num_tests[0])
2405 return test
2446 return test
2406
2447
2407 if not os.path.exists(test.path):
2448 if not os.path.exists(test.path):
2408 result.addSkip(test, "Doesn't exist")
2449 result.addSkip(test, "Doesn't exist")
2409 continue
2450 continue
2410
2451
2411 is_whitelisted = self._whitelist and (
2452 is_whitelisted = self._whitelist and (
2412 test.relpath in self._whitelist or test.bname in self._whitelist
2453 test.relpath in self._whitelist or test.bname in self._whitelist
2413 )
2454 )
2414 if not is_whitelisted:
2455 if not is_whitelisted:
2415 is_blacklisted = self._blacklist and (
2456 is_blacklisted = self._blacklist and (
2416 test.relpath in self._blacklist
2457 test.relpath in self._blacklist
2417 or test.bname in self._blacklist
2458 or test.bname in self._blacklist
2418 )
2459 )
2419 if is_blacklisted:
2460 if is_blacklisted:
2420 result.addSkip(test, 'blacklisted')
2461 result.addSkip(test, 'blacklisted')
2421 continue
2462 continue
2422 if self._keywords:
2463 if self._keywords:
2423 with open(test.path, 'rb') as f:
2464 with open(test.path, 'rb') as f:
2424 t = f.read().lower() + test.bname.lower()
2465 t = f.read().lower() + test.bname.lower()
2425 ignored = False
2466 ignored = False
2426 for k in self._keywords.lower().split():
2467 for k in self._keywords.lower().split():
2427 if k not in t:
2468 if k not in t:
2428 result.addIgnore(test, "doesn't match keyword")
2469 result.addIgnore(test, "doesn't match keyword")
2429 ignored = True
2470 ignored = True
2430 break
2471 break
2431
2472
2432 if ignored:
2473 if ignored:
2433 continue
2474 continue
2434 for _ in xrange(self._runs_per_test):
2475 for _ in xrange(self._runs_per_test):
2435 tests.append(get())
2476 tests.append(get())
2436
2477
2437 runtests = list(tests)
2478 runtests = list(tests)
2438 done = queue.Queue()
2479 done = queue.Queue()
2439 running = 0
2480 running = 0
2440
2481
2441 channels = [""] * self._jobs
2482 channels = [""] * self._jobs
2442
2483
2443 def job(test, result):
2484 def job(test, result):
2444 for n, v in enumerate(channels):
2485 for n, v in enumerate(channels):
2445 if not v:
2486 if not v:
2446 channel = n
2487 channel = n
2447 break
2488 break
2448 else:
2489 else:
2449 raise ValueError('Could not find output channel')
2490 raise ValueError('Could not find output channel')
2450 channels[channel] = "=" + test.name[5:].split(".")[0]
2491 channels[channel] = "=" + test.name[5:].split(".")[0]
2451 try:
2492 try:
2452 test(result)
2493 test(result)
2453 done.put(None)
2494 done.put(None)
2454 except KeyboardInterrupt:
2495 except KeyboardInterrupt:
2455 pass
2496 pass
2456 except: # re-raises
2497 except: # re-raises
2457 done.put(('!', test, 'run-test raised an error, see traceback'))
2498 done.put(('!', test, 'run-test raised an error, see traceback'))
2458 raise
2499 raise
2459 finally:
2500 finally:
2460 try:
2501 try:
2461 channels[channel] = ''
2502 channels[channel] = ''
2462 except IndexError:
2503 except IndexError:
2463 pass
2504 pass
2464
2505
2465 def stat():
2506 def stat():
2466 count = 0
2507 count = 0
2467 while channels:
2508 while channels:
2468 d = '\n%03s ' % count
2509 d = '\n%03s ' % count
2469 for n, v in enumerate(channels):
2510 for n, v in enumerate(channels):
2470 if v:
2511 if v:
2471 d += v[0]
2512 d += v[0]
2472 channels[n] = v[1:] or '.'
2513 channels[n] = v[1:] or '.'
2473 else:
2514 else:
2474 d += ' '
2515 d += ' '
2475 d += ' '
2516 d += ' '
2476 with iolock:
2517 with iolock:
2477 sys.stdout.write(d + ' ')
2518 sys.stdout.write(d + ' ')
2478 sys.stdout.flush()
2519 sys.stdout.flush()
2479 for x in xrange(10):
2520 for x in xrange(10):
2480 if channels:
2521 if channels:
2481 time.sleep(0.1)
2522 time.sleep(0.1)
2482 count += 1
2523 count += 1
2483
2524
2484 stoppedearly = False
2525 stoppedearly = False
2485
2526
2486 if self._showchannels:
2527 if self._showchannels:
2487 statthread = threading.Thread(target=stat, name="stat")
2528 statthread = threading.Thread(target=stat, name="stat")
2488 statthread.start()
2529 statthread.start()
2489
2530
2490 try:
2531 try:
2491 while tests or running:
2532 while tests or running:
2492 if not done.empty() or running == self._jobs or not tests:
2533 if not done.empty() or running == self._jobs or not tests:
2493 try:
2534 try:
2494 done.get(True, 1)
2535 done.get(True, 1)
2495 running -= 1
2536 running -= 1
2496 if result and result.shouldStop:
2537 if result and result.shouldStop:
2497 stoppedearly = True
2538 stoppedearly = True
2498 break
2539 break
2499 except queue.Empty:
2540 except queue.Empty:
2500 continue
2541 continue
2501 if tests and not running == self._jobs:
2542 if tests and not running == self._jobs:
2502 test = tests.pop(0)
2543 test = tests.pop(0)
2503 if self._loop:
2544 if self._loop:
2504 if getattr(test, 'should_reload', False):
2545 if getattr(test, 'should_reload', False):
2505 num_tests[0] += 1
2546 num_tests[0] += 1
2506 tests.append(self._loadtest(test, num_tests[0]))
2547 tests.append(self._loadtest(test, num_tests[0]))
2507 else:
2548 else:
2508 tests.append(test)
2549 tests.append(test)
2509 if self._jobs == 1:
2550 if self._jobs == 1:
2510 job(test, result)
2551 job(test, result)
2511 else:
2552 else:
2512 t = threading.Thread(
2553 t = threading.Thread(
2513 target=job, name=test.name, args=(test, result)
2554 target=job, name=test.name, args=(test, result)
2514 )
2555 )
2515 t.start()
2556 t.start()
2516 running += 1
2557 running += 1
2517
2558
2518 # If we stop early we still need to wait on started tests to
2559 # If we stop early we still need to wait on started tests to
2519 # finish. Otherwise, there is a race between the test completing
2560 # finish. Otherwise, there is a race between the test completing
2520 # and the test's cleanup code running. This could result in the
2561 # and the test's cleanup code running. This could result in the
2521 # test reporting incorrect.
2562 # test reporting incorrect.
2522 if stoppedearly:
2563 if stoppedearly:
2523 while running:
2564 while running:
2524 try:
2565 try:
2525 done.get(True, 1)
2566 done.get(True, 1)
2526 running -= 1
2567 running -= 1
2527 except queue.Empty:
2568 except queue.Empty:
2528 continue
2569 continue
2529 except KeyboardInterrupt:
2570 except KeyboardInterrupt:
2530 for test in runtests:
2571 for test in runtests:
2531 test.abort()
2572 test.abort()
2532
2573
2533 channels = []
2574 channels = []
2534
2575
2535 return result
2576 return result
2536
2577
2537
2578
2538 # Save the most recent 5 wall-clock runtimes of each test to a
2579 # Save the most recent 5 wall-clock runtimes of each test to a
2539 # human-readable text file named .testtimes. Tests are sorted
2580 # human-readable text file named .testtimes. Tests are sorted
2540 # alphabetically, while times for each test are listed from oldest to
2581 # alphabetically, while times for each test are listed from oldest to
2541 # newest.
2582 # newest.
2542
2583
2543
2584
2544 def loadtimes(outputdir):
2585 def loadtimes(outputdir):
2545 times = []
2586 times = []
2546 try:
2587 try:
2547 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2588 with open(os.path.join(outputdir, b'.testtimes')) as fp:
2548 for line in fp:
2589 for line in fp:
2549 m = re.match('(.*?) ([0-9. ]+)', line)
2590 m = re.match('(.*?) ([0-9. ]+)', line)
2550 times.append(
2591 times.append(
2551 (m.group(1), [float(t) for t in m.group(2).split()])
2592 (m.group(1), [float(t) for t in m.group(2).split()])
2552 )
2593 )
2553 except IOError as err:
2594 except IOError as err:
2554 if err.errno != errno.ENOENT:
2595 if err.errno != errno.ENOENT:
2555 raise
2596 raise
2556 return times
2597 return times
2557
2598
2558
2599
2559 def savetimes(outputdir, result):
2600 def savetimes(outputdir, result):
2560 saved = dict(loadtimes(outputdir))
2601 saved = dict(loadtimes(outputdir))
2561 maxruns = 5
2602 maxruns = 5
2562 skipped = {str(t[0]) for t in result.skipped}
2603 skipped = {str(t[0]) for t in result.skipped}
2563 for tdata in result.times:
2604 for tdata in result.times:
2564 test, real = tdata[0], tdata[3]
2605 test, real = tdata[0], tdata[3]
2565 if test not in skipped:
2606 if test not in skipped:
2566 ts = saved.setdefault(test, [])
2607 ts = saved.setdefault(test, [])
2567 ts.append(real)
2608 ts.append(real)
2568 ts[:] = ts[-maxruns:]
2609 ts[:] = ts[-maxruns:]
2569
2610
2570 fd, tmpname = tempfile.mkstemp(
2611 fd, tmpname = tempfile.mkstemp(
2571 prefix=b'.testtimes', dir=outputdir, text=True
2612 prefix=b'.testtimes', dir=outputdir, text=True
2572 )
2613 )
2573 with os.fdopen(fd, 'w') as fp:
2614 with os.fdopen(fd, 'w') as fp:
2574 for name, ts in sorted(saved.items()):
2615 for name, ts in sorted(saved.items()):
2575 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2616 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
2576 timepath = os.path.join(outputdir, b'.testtimes')
2617 timepath = os.path.join(outputdir, b'.testtimes')
2577 try:
2618 try:
2578 os.unlink(timepath)
2619 os.unlink(timepath)
2579 except OSError:
2620 except OSError:
2580 pass
2621 pass
2581 try:
2622 try:
2582 os.rename(tmpname, timepath)
2623 os.rename(tmpname, timepath)
2583 except OSError:
2624 except OSError:
2584 pass
2625 pass
2585
2626
2586
2627
2587 class TextTestRunner(unittest.TextTestRunner):
2628 class TextTestRunner(unittest.TextTestRunner):
2588 """Custom unittest test runner that uses appropriate settings."""
2629 """Custom unittest test runner that uses appropriate settings."""
2589
2630
2590 def __init__(self, runner, *args, **kwargs):
2631 def __init__(self, runner, *args, **kwargs):
2591 super(TextTestRunner, self).__init__(*args, **kwargs)
2632 super(TextTestRunner, self).__init__(*args, **kwargs)
2592
2633
2593 self._runner = runner
2634 self._runner = runner
2594
2635
2595 self._result = getTestResult()(
2636 self._result = getTestResult()(
2596 self._runner.options, self.stream, self.descriptions, self.verbosity
2637 self._runner.options, self.stream, self.descriptions, self.verbosity
2597 )
2638 )
2598
2639
2599 def listtests(self, test):
2640 def listtests(self, test):
2600 test = sorted(test, key=lambda t: t.name)
2641 test = sorted(test, key=lambda t: t.name)
2601
2642
2602 self._result.onStart(test)
2643 self._result.onStart(test)
2603
2644
2604 for t in test:
2645 for t in test:
2605 print(t.name)
2646 print(t.name)
2606 self._result.addSuccess(t)
2647 self._result.addSuccess(t)
2607
2648
2608 if self._runner.options.xunit:
2649 if self._runner.options.xunit:
2609 with open(self._runner.options.xunit, "wb") as xuf:
2650 with open(self._runner.options.xunit, "wb") as xuf:
2610 self._writexunit(self._result, xuf)
2651 self._writexunit(self._result, xuf)
2611
2652
2612 if self._runner.options.json:
2653 if self._runner.options.json:
2613 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2654 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2614 with open(jsonpath, 'w') as fp:
2655 with open(jsonpath, 'w') as fp:
2615 self._writejson(self._result, fp)
2656 self._writejson(self._result, fp)
2616
2657
2617 return self._result
2658 return self._result
2618
2659
2619 def run(self, test):
2660 def run(self, test):
2620 self._result.onStart(test)
2661 self._result.onStart(test)
2621 test(self._result)
2662 test(self._result)
2622
2663
2623 failed = len(self._result.failures)
2664 failed = len(self._result.failures)
2624 skipped = len(self._result.skipped)
2665 skipped = len(self._result.skipped)
2625 ignored = len(self._result.ignored)
2666 ignored = len(self._result.ignored)
2626
2667
2627 with iolock:
2668 with iolock:
2628 self.stream.writeln('')
2669 self.stream.writeln('')
2629
2670
2630 if not self._runner.options.noskips:
2671 if not self._runner.options.noskips:
2631 for test, msg in sorted(
2672 for test, msg in sorted(
2632 self._result.skipped, key=lambda s: s[0].name
2673 self._result.skipped, key=lambda s: s[0].name
2633 ):
2674 ):
2634 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2675 formatted = 'Skipped %s: %s\n' % (test.name, msg)
2635 msg = highlightmsg(formatted, self._result.color)
2676 msg = highlightmsg(formatted, self._result.color)
2636 self.stream.write(msg)
2677 self.stream.write(msg)
2637 for test, msg in sorted(
2678 for test, msg in sorted(
2638 self._result.failures, key=lambda f: f[0].name
2679 self._result.failures, key=lambda f: f[0].name
2639 ):
2680 ):
2640 formatted = 'Failed %s: %s\n' % (test.name, msg)
2681 formatted = 'Failed %s: %s\n' % (test.name, msg)
2641 self.stream.write(highlightmsg(formatted, self._result.color))
2682 self.stream.write(highlightmsg(formatted, self._result.color))
2642 for test, msg in sorted(
2683 for test, msg in sorted(
2643 self._result.errors, key=lambda e: e[0].name
2684 self._result.errors, key=lambda e: e[0].name
2644 ):
2685 ):
2645 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2686 self.stream.writeln('Errored %s: %s' % (test.name, msg))
2646
2687
2647 if self._runner.options.xunit:
2688 if self._runner.options.xunit:
2648 with open(self._runner.options.xunit, "wb") as xuf:
2689 with open(self._runner.options.xunit, "wb") as xuf:
2649 self._writexunit(self._result, xuf)
2690 self._writexunit(self._result, xuf)
2650
2691
2651 if self._runner.options.json:
2692 if self._runner.options.json:
2652 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2693 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
2653 with open(jsonpath, 'w') as fp:
2694 with open(jsonpath, 'w') as fp:
2654 self._writejson(self._result, fp)
2695 self._writejson(self._result, fp)
2655
2696
2656 self._runner._checkhglib('Tested')
2697 self._runner._checkhglib('Tested')
2657
2698
2658 savetimes(self._runner._outputdir, self._result)
2699 savetimes(self._runner._outputdir, self._result)
2659
2700
2660 if failed and self._runner.options.known_good_rev:
2701 if failed and self._runner.options.known_good_rev:
2661 self._bisecttests(t for t, m in self._result.failures)
2702 self._bisecttests(t for t, m in self._result.failures)
2662 self.stream.writeln(
2703 self.stream.writeln(
2663 '# Ran %d tests, %d skipped, %d failed.'
2704 '# Ran %d tests, %d skipped, %d failed.'
2664 % (self._result.testsRun, skipped + ignored, failed)
2705 % (self._result.testsRun, skipped + ignored, failed)
2665 )
2706 )
2666 if failed:
2707 if failed:
2667 self.stream.writeln(
2708 self.stream.writeln(
2668 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2709 'python hash seed: %s' % os.environ['PYTHONHASHSEED']
2669 )
2710 )
2670 if self._runner.options.time:
2711 if self._runner.options.time:
2671 self.printtimes(self._result.times)
2712 self.printtimes(self._result.times)
2672
2713
2673 if self._runner.options.exceptions:
2714 if self._runner.options.exceptions:
2674 exceptions = aggregateexceptions(
2715 exceptions = aggregateexceptions(
2675 os.path.join(self._runner._outputdir, b'exceptions')
2716 os.path.join(self._runner._outputdir, b'exceptions')
2676 )
2717 )
2677
2718
2678 self.stream.writeln('Exceptions Report:')
2719 self.stream.writeln('Exceptions Report:')
2679 self.stream.writeln(
2720 self.stream.writeln(
2680 '%d total from %d frames'
2721 '%d total from %d frames'
2681 % (exceptions['total'], len(exceptions['exceptioncounts']))
2722 % (exceptions['total'], len(exceptions['exceptioncounts']))
2682 )
2723 )
2683 combined = exceptions['combined']
2724 combined = exceptions['combined']
2684 for key in sorted(combined, key=combined.get, reverse=True):
2725 for key in sorted(combined, key=combined.get, reverse=True):
2685 frame, line, exc = key
2726 frame, line, exc = key
2686 totalcount, testcount, leastcount, leasttest = combined[key]
2727 totalcount, testcount, leastcount, leasttest = combined[key]
2687
2728
2688 self.stream.writeln(
2729 self.stream.writeln(
2689 '%d (%d tests)\t%s: %s (%s - %d total)'
2730 '%d (%d tests)\t%s: %s (%s - %d total)'
2690 % (
2731 % (
2691 totalcount,
2732 totalcount,
2692 testcount,
2733 testcount,
2693 frame,
2734 frame,
2694 exc,
2735 exc,
2695 leasttest,
2736 leasttest,
2696 leastcount,
2737 leastcount,
2697 )
2738 )
2698 )
2739 )
2699
2740
2700 self.stream.flush()
2741 self.stream.flush()
2701
2742
2702 return self._result
2743 return self._result
2703
2744
2704 def _bisecttests(self, tests):
2745 def _bisecttests(self, tests):
2705 bisectcmd = ['hg', 'bisect']
2746 bisectcmd = ['hg', 'bisect']
2706 bisectrepo = self._runner.options.bisect_repo
2747 bisectrepo = self._runner.options.bisect_repo
2707 if bisectrepo:
2748 if bisectrepo:
2708 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2749 bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
2709
2750
2710 def pread(args):
2751 def pread(args):
2711 env = os.environ.copy()
2752 env = os.environ.copy()
2712 env['HGPLAIN'] = '1'
2753 env['HGPLAIN'] = '1'
2713 p = subprocess.Popen(
2754 p = subprocess.Popen(
2714 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2755 args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env
2715 )
2756 )
2716 data = p.stdout.read()
2757 data = p.stdout.read()
2717 p.wait()
2758 p.wait()
2718 return data
2759 return data
2719
2760
2720 for test in tests:
2761 for test in tests:
2721 pread(bisectcmd + ['--reset']),
2762 pread(bisectcmd + ['--reset']),
2722 pread(bisectcmd + ['--bad', '.'])
2763 pread(bisectcmd + ['--bad', '.'])
2723 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2764 pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
2724 # TODO: we probably need to forward more options
2765 # TODO: we probably need to forward more options
2725 # that alter hg's behavior inside the tests.
2766 # that alter hg's behavior inside the tests.
2726 opts = ''
2767 opts = ''
2727 withhg = self._runner.options.with_hg
2768 withhg = self._runner.options.with_hg
2728 if withhg:
2769 if withhg:
2729 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2770 opts += ' --with-hg=%s ' % shellquote(_bytes2sys(withhg))
2730 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2771 rtc = '%s %s %s %s' % (sysexecutable, sys.argv[0], opts, test)
2731 data = pread(bisectcmd + ['--command', rtc])
2772 data = pread(bisectcmd + ['--command', rtc])
2732 m = re.search(
2773 m = re.search(
2733 (
2774 (
2734 br'\nThe first (?P<goodbad>bad|good) revision '
2775 br'\nThe first (?P<goodbad>bad|good) revision '
2735 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2776 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
2736 br'summary: +(?P<summary>[^\n]+)\n'
2777 br'summary: +(?P<summary>[^\n]+)\n'
2737 ),
2778 ),
2738 data,
2779 data,
2739 (re.MULTILINE | re.DOTALL),
2780 (re.MULTILINE | re.DOTALL),
2740 )
2781 )
2741 if m is None:
2782 if m is None:
2742 self.stream.writeln(
2783 self.stream.writeln(
2743 'Failed to identify failure point for %s' % test
2784 'Failed to identify failure point for %s' % test
2744 )
2785 )
2745 continue
2786 continue
2746 dat = m.groupdict()
2787 dat = m.groupdict()
2747 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2788 verb = 'broken' if dat['goodbad'] == b'bad' else 'fixed'
2748 self.stream.writeln(
2789 self.stream.writeln(
2749 '%s %s by %s (%s)'
2790 '%s %s by %s (%s)'
2750 % (
2791 % (
2751 test,
2792 test,
2752 verb,
2793 verb,
2753 dat['node'].decode('ascii'),
2794 dat['node'].decode('ascii'),
2754 dat['summary'].decode('utf8', 'ignore'),
2795 dat['summary'].decode('utf8', 'ignore'),
2755 )
2796 )
2756 )
2797 )
2757
2798
2758 def printtimes(self, times):
2799 def printtimes(self, times):
2759 # iolock held by run
2800 # iolock held by run
2760 self.stream.writeln('# Producing time report')
2801 self.stream.writeln('# Producing time report')
2761 times.sort(key=lambda t: (t[3]))
2802 times.sort(key=lambda t: (t[3]))
2762 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2803 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
2763 self.stream.writeln(
2804 self.stream.writeln(
2764 '%-7s %-7s %-7s %-7s %-7s %s'
2805 '%-7s %-7s %-7s %-7s %-7s %s'
2765 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2806 % ('start', 'end', 'cuser', 'csys', 'real', 'Test')
2766 )
2807 )
2767 for tdata in times:
2808 for tdata in times:
2768 test = tdata[0]
2809 test = tdata[0]
2769 cuser, csys, real, start, end = tdata[1:6]
2810 cuser, csys, real, start, end = tdata[1:6]
2770 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2811 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
2771
2812
2772 @staticmethod
2813 @staticmethod
2773 def _writexunit(result, outf):
2814 def _writexunit(result, outf):
2774 # See http://llg.cubic.org/docs/junit/ for a reference.
2815 # See http://llg.cubic.org/docs/junit/ for a reference.
2775 timesd = {t[0]: t[3] for t in result.times}
2816 timesd = {t[0]: t[3] for t in result.times}
2776 doc = minidom.Document()
2817 doc = minidom.Document()
2777 s = doc.createElement('testsuite')
2818 s = doc.createElement('testsuite')
2778 s.setAttribute('errors', "0") # TODO
2819 s.setAttribute('errors', "0") # TODO
2779 s.setAttribute('failures', str(len(result.failures)))
2820 s.setAttribute('failures', str(len(result.failures)))
2780 s.setAttribute('name', 'run-tests')
2821 s.setAttribute('name', 'run-tests')
2781 s.setAttribute(
2822 s.setAttribute(
2782 'skipped', str(len(result.skipped) + len(result.ignored))
2823 'skipped', str(len(result.skipped) + len(result.ignored))
2783 )
2824 )
2784 s.setAttribute('tests', str(result.testsRun))
2825 s.setAttribute('tests', str(result.testsRun))
2785 doc.appendChild(s)
2826 doc.appendChild(s)
2786 for tc in result.successes:
2827 for tc in result.successes:
2787 t = doc.createElement('testcase')
2828 t = doc.createElement('testcase')
2788 t.setAttribute('name', tc.name)
2829 t.setAttribute('name', tc.name)
2789 tctime = timesd.get(tc.name)
2830 tctime = timesd.get(tc.name)
2790 if tctime is not None:
2831 if tctime is not None:
2791 t.setAttribute('time', '%.3f' % tctime)
2832 t.setAttribute('time', '%.3f' % tctime)
2792 s.appendChild(t)
2833 s.appendChild(t)
2793 for tc, err in sorted(result.faildata.items()):
2834 for tc, err in sorted(result.faildata.items()):
2794 t = doc.createElement('testcase')
2835 t = doc.createElement('testcase')
2795 t.setAttribute('name', tc)
2836 t.setAttribute('name', tc)
2796 tctime = timesd.get(tc)
2837 tctime = timesd.get(tc)
2797 if tctime is not None:
2838 if tctime is not None:
2798 t.setAttribute('time', '%.3f' % tctime)
2839 t.setAttribute('time', '%.3f' % tctime)
2799 # createCDATASection expects a unicode or it will
2840 # createCDATASection expects a unicode or it will
2800 # convert using default conversion rules, which will
2841 # convert using default conversion rules, which will
2801 # fail if string isn't ASCII.
2842 # fail if string isn't ASCII.
2802 err = cdatasafe(err).decode('utf-8', 'replace')
2843 err = cdatasafe(err).decode('utf-8', 'replace')
2803 cd = doc.createCDATASection(err)
2844 cd = doc.createCDATASection(err)
2804 # Use 'failure' here instead of 'error' to match errors = 0,
2845 # Use 'failure' here instead of 'error' to match errors = 0,
2805 # failures = len(result.failures) in the testsuite element.
2846 # failures = len(result.failures) in the testsuite element.
2806 failelem = doc.createElement('failure')
2847 failelem = doc.createElement('failure')
2807 failelem.setAttribute('message', 'output changed')
2848 failelem.setAttribute('message', 'output changed')
2808 failelem.setAttribute('type', 'output-mismatch')
2849 failelem.setAttribute('type', 'output-mismatch')
2809 failelem.appendChild(cd)
2850 failelem.appendChild(cd)
2810 t.appendChild(failelem)
2851 t.appendChild(failelem)
2811 s.appendChild(t)
2852 s.appendChild(t)
2812 for tc, message in result.skipped:
2853 for tc, message in result.skipped:
2813 # According to the schema, 'skipped' has no attributes. So store
2854 # According to the schema, 'skipped' has no attributes. So store
2814 # the skip message as a text node instead.
2855 # the skip message as a text node instead.
2815 t = doc.createElement('testcase')
2856 t = doc.createElement('testcase')
2816 t.setAttribute('name', tc.name)
2857 t.setAttribute('name', tc.name)
2817 binmessage = message.encode('utf-8')
2858 binmessage = message.encode('utf-8')
2818 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2859 message = cdatasafe(binmessage).decode('utf-8', 'replace')
2819 cd = doc.createCDATASection(message)
2860 cd = doc.createCDATASection(message)
2820 skipelem = doc.createElement('skipped')
2861 skipelem = doc.createElement('skipped')
2821 skipelem.appendChild(cd)
2862 skipelem.appendChild(cd)
2822 t.appendChild(skipelem)
2863 t.appendChild(skipelem)
2823 s.appendChild(t)
2864 s.appendChild(t)
2824 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2865 outf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
2825
2866
2826 @staticmethod
2867 @staticmethod
2827 def _writejson(result, outf):
2868 def _writejson(result, outf):
2828 timesd = {}
2869 timesd = {}
2829 for tdata in result.times:
2870 for tdata in result.times:
2830 test = tdata[0]
2871 test = tdata[0]
2831 timesd[test] = tdata[1:]
2872 timesd[test] = tdata[1:]
2832
2873
2833 outcome = {}
2874 outcome = {}
2834 groups = [
2875 groups = [
2835 ('success', ((tc, None) for tc in result.successes)),
2876 ('success', ((tc, None) for tc in result.successes)),
2836 ('failure', result.failures),
2877 ('failure', result.failures),
2837 ('skip', result.skipped),
2878 ('skip', result.skipped),
2838 ]
2879 ]
2839 for res, testcases in groups:
2880 for res, testcases in groups:
2840 for tc, __ in testcases:
2881 for tc, __ in testcases:
2841 if tc.name in timesd:
2882 if tc.name in timesd:
2842 diff = result.faildata.get(tc.name, b'')
2883 diff = result.faildata.get(tc.name, b'')
2843 try:
2884 try:
2844 diff = diff.decode('unicode_escape')
2885 diff = diff.decode('unicode_escape')
2845 except UnicodeDecodeError as e:
2886 except UnicodeDecodeError as e:
2846 diff = '%r decoding diff, sorry' % e
2887 diff = '%r decoding diff, sorry' % e
2847 tres = {
2888 tres = {
2848 'result': res,
2889 'result': res,
2849 'time': ('%0.3f' % timesd[tc.name][2]),
2890 'time': ('%0.3f' % timesd[tc.name][2]),
2850 'cuser': ('%0.3f' % timesd[tc.name][0]),
2891 'cuser': ('%0.3f' % timesd[tc.name][0]),
2851 'csys': ('%0.3f' % timesd[tc.name][1]),
2892 'csys': ('%0.3f' % timesd[tc.name][1]),
2852 'start': ('%0.3f' % timesd[tc.name][3]),
2893 'start': ('%0.3f' % timesd[tc.name][3]),
2853 'end': ('%0.3f' % timesd[tc.name][4]),
2894 'end': ('%0.3f' % timesd[tc.name][4]),
2854 'diff': diff,
2895 'diff': diff,
2855 }
2896 }
2856 else:
2897 else:
2857 # blacklisted test
2898 # blacklisted test
2858 tres = {'result': res}
2899 tres = {'result': res}
2859
2900
2860 outcome[tc.name] = tres
2901 outcome[tc.name] = tres
2861 jsonout = json.dumps(
2902 jsonout = json.dumps(
2862 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2903 outcome, sort_keys=True, indent=4, separators=(',', ': ')
2863 )
2904 )
2864 outf.writelines(("testreport =", jsonout))
2905 outf.writelines(("testreport =", jsonout))
2865
2906
2866
2907
2867 def sorttests(testdescs, previoustimes, shuffle=False):
2908 def sorttests(testdescs, previoustimes, shuffle=False):
2868 """Do an in-place sort of tests."""
2909 """Do an in-place sort of tests."""
2869 if shuffle:
2910 if shuffle:
2870 random.shuffle(testdescs)
2911 random.shuffle(testdescs)
2871 return
2912 return
2872
2913
2873 if previoustimes:
2914 if previoustimes:
2874
2915
2875 def sortkey(f):
2916 def sortkey(f):
2876 f = f['path']
2917 f = f['path']
2877 if f in previoustimes:
2918 if f in previoustimes:
2878 # Use most recent time as estimate
2919 # Use most recent time as estimate
2879 return -(previoustimes[f][-1])
2920 return -(previoustimes[f][-1])
2880 else:
2921 else:
2881 # Default to a rather arbitrary value of 1 second for new tests
2922 # Default to a rather arbitrary value of 1 second for new tests
2882 return -1.0
2923 return -1.0
2883
2924
2884 else:
2925 else:
2885 # keywords for slow tests
2926 # keywords for slow tests
2886 slow = {
2927 slow = {
2887 b'svn': 10,
2928 b'svn': 10,
2888 b'cvs': 10,
2929 b'cvs': 10,
2889 b'hghave': 10,
2930 b'hghave': 10,
2890 b'largefiles-update': 10,
2931 b'largefiles-update': 10,
2891 b'run-tests': 10,
2932 b'run-tests': 10,
2892 b'corruption': 10,
2933 b'corruption': 10,
2893 b'race': 10,
2934 b'race': 10,
2894 b'i18n': 10,
2935 b'i18n': 10,
2895 b'check': 100,
2936 b'check': 100,
2896 b'gendoc': 100,
2937 b'gendoc': 100,
2897 b'contrib-perf': 200,
2938 b'contrib-perf': 200,
2898 b'merge-combination': 100,
2939 b'merge-combination': 100,
2899 }
2940 }
2900 perf = {}
2941 perf = {}
2901
2942
2902 def sortkey(f):
2943 def sortkey(f):
2903 # run largest tests first, as they tend to take the longest
2944 # run largest tests first, as they tend to take the longest
2904 f = f['path']
2945 f = f['path']
2905 try:
2946 try:
2906 return perf[f]
2947 return perf[f]
2907 except KeyError:
2948 except KeyError:
2908 try:
2949 try:
2909 val = -os.stat(f).st_size
2950 val = -os.stat(f).st_size
2910 except OSError as e:
2951 except OSError as e:
2911 if e.errno != errno.ENOENT:
2952 if e.errno != errno.ENOENT:
2912 raise
2953 raise
2913 perf[f] = -1e9 # file does not exist, tell early
2954 perf[f] = -1e9 # file does not exist, tell early
2914 return -1e9
2955 return -1e9
2915 for kw, mul in slow.items():
2956 for kw, mul in slow.items():
2916 if kw in f:
2957 if kw in f:
2917 val *= mul
2958 val *= mul
2918 if f.endswith(b'.py'):
2959 if f.endswith(b'.py'):
2919 val /= 10.0
2960 val /= 10.0
2920 perf[f] = val / 1000.0
2961 perf[f] = val / 1000.0
2921 return perf[f]
2962 return perf[f]
2922
2963
2923 testdescs.sort(key=sortkey)
2964 testdescs.sort(key=sortkey)
2924
2965
2925
2966
2926 class TestRunner(object):
2967 class TestRunner(object):
2927 """Holds context for executing tests.
2968 """Holds context for executing tests.
2928
2969
2929 Tests rely on a lot of state. This object holds it for them.
2970 Tests rely on a lot of state. This object holds it for them.
2930 """
2971 """
2931
2972
2932 # Programs required to run tests.
2973 # Programs required to run tests.
2933 REQUIREDTOOLS = [
2974 REQUIREDTOOLS = [
2934 b'diff',
2975 b'diff',
2935 b'grep',
2976 b'grep',
2936 b'unzip',
2977 b'unzip',
2937 b'gunzip',
2978 b'gunzip',
2938 b'bunzip2',
2979 b'bunzip2',
2939 b'sed',
2980 b'sed',
2940 ]
2981 ]
2941
2982
2942 # Maps file extensions to test class.
2983 # Maps file extensions to test class.
2943 TESTTYPES = [
2984 TESTTYPES = [
2944 (b'.py', PythonTest),
2985 (b'.py', PythonTest),
2945 (b'.t', TTest),
2986 (b'.t', TTest),
2946 ]
2987 ]
2947
2988
2948 def __init__(self):
2989 def __init__(self):
2949 self.options = None
2990 self.options = None
2950 self._hgroot = None
2991 self._hgroot = None
2951 self._testdir = None
2992 self._testdir = None
2952 self._outputdir = None
2993 self._outputdir = None
2953 self._hgtmp = None
2994 self._hgtmp = None
2954 self._installdir = None
2995 self._installdir = None
2955 self._bindir = None
2996 self._bindir = None
2956 self._tmpbindir = None
2997 self._tmpbindir = None
2957 self._pythondir = None
2998 self._pythondir = None
2958 self._coveragefile = None
2999 self._coveragefile = None
2959 self._createdfiles = []
3000 self._createdfiles = []
2960 self._hgcommand = None
3001 self._hgcommand = None
3002 self._rhg_fallback_exe = None
2961 self._hgpath = None
3003 self._hgpath = None
2962 self._portoffset = 0
3004 self._portoffset = 0
2963 self._ports = {}
3005 self._ports = {}
2964
3006
2965 def run(self, args, parser=None):
3007 def run(self, args, parser=None):
2966 """Run the test suite."""
3008 """Run the test suite."""
2967 oldmask = os.umask(0o22)
3009 oldmask = os.umask(0o22)
2968 try:
3010 try:
2969 parser = parser or getparser()
3011 parser = parser or getparser()
2970 options = parseargs(args, parser)
3012 options = parseargs(args, parser)
2971 tests = [_sys2bytes(a) for a in options.tests]
3013 tests = [_sys2bytes(a) for a in options.tests]
2972 if options.test_list is not None:
3014 if options.test_list is not None:
2973 for listfile in options.test_list:
3015 for listfile in options.test_list:
2974 with open(listfile, 'rb') as f:
3016 with open(listfile, 'rb') as f:
2975 tests.extend(t for t in f.read().splitlines() if t)
3017 tests.extend(t for t in f.read().splitlines() if t)
2976 self.options = options
3018 self.options = options
2977
3019
2978 self._checktools()
3020 self._checktools()
2979 testdescs = self.findtests(tests)
3021 testdescs = self.findtests(tests)
2980 if options.profile_runner:
3022 if options.profile_runner:
2981 import statprof
3023 import statprof
2982
3024
2983 statprof.start()
3025 statprof.start()
2984 result = self._run(testdescs)
3026 result = self._run(testdescs)
2985 if options.profile_runner:
3027 if options.profile_runner:
2986 statprof.stop()
3028 statprof.stop()
2987 statprof.display()
3029 statprof.display()
2988 return result
3030 return result
2989
3031
2990 finally:
3032 finally:
2991 os.umask(oldmask)
3033 os.umask(oldmask)
2992
3034
2993 def _run(self, testdescs):
3035 def _run(self, testdescs):
2994 testdir = getcwdb()
3036 testdir = getcwdb()
2995 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
3037 self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
2996 # assume all tests in same folder for now
3038 # assume all tests in same folder for now
2997 if testdescs:
3039 if testdescs:
2998 pathname = os.path.dirname(testdescs[0]['path'])
3040 pathname = os.path.dirname(testdescs[0]['path'])
2999 if pathname:
3041 if pathname:
3000 testdir = os.path.join(testdir, pathname)
3042 testdir = os.path.join(testdir, pathname)
3001 self._testdir = osenvironb[b'TESTDIR'] = testdir
3043 self._testdir = osenvironb[b'TESTDIR'] = testdir
3002 if self.options.outputdir:
3044 if self.options.outputdir:
3003 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3045 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3004 else:
3046 else:
3005 self._outputdir = getcwdb()
3047 self._outputdir = getcwdb()
3006 if testdescs and pathname:
3048 if testdescs and pathname:
3007 self._outputdir = os.path.join(self._outputdir, pathname)
3049 self._outputdir = os.path.join(self._outputdir, pathname)
3008 previoustimes = {}
3050 previoustimes = {}
3009 if self.options.order_by_runtime:
3051 if self.options.order_by_runtime:
3010 previoustimes = dict(loadtimes(self._outputdir))
3052 previoustimes = dict(loadtimes(self._outputdir))
3011 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3053 sorttests(testdescs, previoustimes, shuffle=self.options.random)
3012
3054
3013 if 'PYTHONHASHSEED' not in os.environ:
3055 if 'PYTHONHASHSEED' not in os.environ:
3014 # use a random python hash seed all the time
3056 # use a random python hash seed all the time
3015 # we do the randomness ourself to know what seed is used
3057 # we do the randomness ourself to know what seed is used
3016 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3058 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
3017
3059
3018 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3060 # Rayon (Rust crate for multi-threading) will use all logical CPU cores
3019 # by default, causing thrashing on high-cpu-count systems.
3061 # by default, causing thrashing on high-cpu-count systems.
3020 # Setting its limit to 3 during tests should still let us uncover
3062 # Setting its limit to 3 during tests should still let us uncover
3021 # multi-threading bugs while keeping the thrashing reasonable.
3063 # multi-threading bugs while keeping the thrashing reasonable.
3022 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3064 os.environ.setdefault("RAYON_NUM_THREADS", "3")
3023
3065
3024 if self.options.tmpdir:
3066 if self.options.tmpdir:
3025 self.options.keep_tmpdir = True
3067 self.options.keep_tmpdir = True
3026 tmpdir = _sys2bytes(self.options.tmpdir)
3068 tmpdir = _sys2bytes(self.options.tmpdir)
3027 if os.path.exists(tmpdir):
3069 if os.path.exists(tmpdir):
3028 # Meaning of tmpdir has changed since 1.3: we used to create
3070 # Meaning of tmpdir has changed since 1.3: we used to create
3029 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3071 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
3030 # tmpdir already exists.
3072 # tmpdir already exists.
3031 print("error: temp dir %r already exists" % tmpdir)
3073 print("error: temp dir %r already exists" % tmpdir)
3032 return 1
3074 return 1
3033
3075
3034 os.makedirs(tmpdir)
3076 os.makedirs(tmpdir)
3035 else:
3077 else:
3036 d = None
3078 d = None
3037 if os.name == 'nt':
3079 if os.name == 'nt':
3038 # without this, we get the default temp dir location, but
3080 # without this, we get the default temp dir location, but
3039 # in all lowercase, which causes troubles with paths (issue3490)
3081 # in all lowercase, which causes troubles with paths (issue3490)
3040 d = osenvironb.get(b'TMP', None)
3082 d = osenvironb.get(b'TMP', None)
3041 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3083 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
3042
3084
3043 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3085 self._hgtmp = osenvironb[b'HGTMP'] = os.path.realpath(tmpdir)
3044
3086
3045 if self.options.with_hg:
3087 if self.options.with_hg:
3046 self._installdir = None
3088 self._installdir = None
3047 whg = self.options.with_hg
3089 whg = self.options.with_hg
3048 self._bindir = os.path.dirname(os.path.realpath(whg))
3090 self._bindir = os.path.dirname(os.path.realpath(whg))
3049 assert isinstance(self._bindir, bytes)
3091 assert isinstance(self._bindir, bytes)
3050 self._hgcommand = os.path.basename(whg)
3092 self._hgcommand = os.path.basename(whg)
3051 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3093 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
3052 os.makedirs(self._tmpbindir)
3094 os.makedirs(self._tmpbindir)
3053
3095
3054 normbin = os.path.normpath(os.path.abspath(whg))
3096 normbin = os.path.normpath(os.path.abspath(whg))
3055 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3097 normbin = normbin.replace(_sys2bytes(os.sep), b'/')
3056
3098
3057 # Other Python scripts in the test harness need to
3099 # Other Python scripts in the test harness need to
3058 # `import mercurial`. If `hg` is a Python script, we assume
3100 # `import mercurial`. If `hg` is a Python script, we assume
3059 # the Mercurial modules are relative to its path and tell the tests
3101 # the Mercurial modules are relative to its path and tell the tests
3060 # to load Python modules from its directory.
3102 # to load Python modules from its directory.
3061 with open(whg, 'rb') as fh:
3103 with open(whg, 'rb') as fh:
3062 initial = fh.read(1024)
3104 initial = fh.read(1024)
3063
3105
3064 if re.match(b'#!.*python', initial):
3106 if re.match(b'#!.*python', initial):
3065 self._pythondir = self._bindir
3107 self._pythondir = self._bindir
3066 # If it looks like our in-repo Rust binary, use the source root.
3108 # If it looks like our in-repo Rust binary, use the source root.
3067 # This is a bit hacky. But rhg is still not supported outside the
3109 # This is a bit hacky. But rhg is still not supported outside the
3068 # source directory. So until it is, do the simple thing.
3110 # source directory. So until it is, do the simple thing.
3069 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3111 elif re.search(b'/rust/target/[^/]+/hg', normbin):
3070 self._pythondir = os.path.dirname(self._testdir)
3112 self._pythondir = os.path.dirname(self._testdir)
3071 # Fall back to the legacy behavior.
3113 # Fall back to the legacy behavior.
3072 else:
3114 else:
3073 self._pythondir = self._bindir
3115 self._pythondir = self._bindir
3074
3116
3075 else:
3117 else:
3076 self._installdir = os.path.join(self._hgtmp, b"install")
3118 self._installdir = os.path.join(self._hgtmp, b"install")
3077 self._bindir = os.path.join(self._installdir, b"bin")
3119 self._bindir = os.path.join(self._installdir, b"bin")
3078 self._hgcommand = b'hg'
3120 self._hgcommand = b'hg'
3079 self._tmpbindir = self._bindir
3121 self._tmpbindir = self._bindir
3080 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3122 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
3081
3123
3082 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3124 # Force the use of hg.exe instead of relying on MSYS to recognize hg is
3083 # a python script and feed it to python.exe. Legacy stdio is force
3125 # a python script and feed it to python.exe. Legacy stdio is force
3084 # enabled by hg.exe, and this is a more realistic way to launch hg
3126 # enabled by hg.exe, and this is a more realistic way to launch hg
3085 # anyway.
3127 # anyway.
3086 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3128 if os.name == 'nt' and not self._hgcommand.endswith(b'.exe'):
3087 self._hgcommand += b'.exe'
3129 self._hgcommand += b'.exe'
3088
3130
3089 # set CHGHG, then replace "hg" command by "chg"
3131 # set CHGHG, then replace "hg" command by "chg"
3090 chgbindir = self._bindir
3132 chgbindir = self._bindir
3091 if self.options.chg or self.options.with_chg:
3133 if self.options.chg or self.options.with_chg:
3092 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3134 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
3093 else:
3135 else:
3094 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3136 osenvironb.pop(b'CHGHG', None) # drop flag for hghave
3095 if self.options.chg:
3137 if self.options.chg:
3096 self._hgcommand = b'chg'
3138 self._hgcommand = b'chg'
3097 elif self.options.with_chg:
3139 elif self.options.with_chg:
3098 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3140 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
3099 self._hgcommand = os.path.basename(self.options.with_chg)
3141 self._hgcommand = os.path.basename(self.options.with_chg)
3100
3142
3143 # set fallback executable path, then replace "hg" command by "rhg"
3144 rhgbindir = self._bindir
3145 if self.options.rhg or self.options.with_rhg:
3146 self._rhg_fallback_exe = os.path.join(self._bindir, self._hgcommand)
3147 if self.options.rhg:
3148 self._hgcommand = b'rhg'
3149 elif self.options.with_rhg:
3150 rhgbindir = os.path.dirname(os.path.realpath(self.options.with_rhg))
3151 self._hgcommand = os.path.basename(self.options.with_rhg)
3152
3101 osenvironb[b"BINDIR"] = self._bindir
3153 osenvironb[b"BINDIR"] = self._bindir
3102 osenvironb[b"PYTHON"] = PYTHON
3154 osenvironb[b"PYTHON"] = PYTHON
3103
3155
3104 fileb = _sys2bytes(__file__)
3156 fileb = _sys2bytes(__file__)
3105 runtestdir = os.path.abspath(os.path.dirname(fileb))
3157 runtestdir = os.path.abspath(os.path.dirname(fileb))
3106 osenvironb[b'RUNTESTDIR'] = runtestdir
3158 osenvironb[b'RUNTESTDIR'] = runtestdir
3107 if PYTHON3:
3159 if PYTHON3:
3108 sepb = _sys2bytes(os.pathsep)
3160 sepb = _sys2bytes(os.pathsep)
3109 else:
3161 else:
3110 sepb = os.pathsep
3162 sepb = os.pathsep
3111 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3163 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
3112 if os.path.islink(__file__):
3164 if os.path.islink(__file__):
3113 # test helper will likely be at the end of the symlink
3165 # test helper will likely be at the end of the symlink
3114 realfile = os.path.realpath(fileb)
3166 realfile = os.path.realpath(fileb)
3115 realdir = os.path.abspath(os.path.dirname(realfile))
3167 realdir = os.path.abspath(os.path.dirname(realfile))
3116 path.insert(2, realdir)
3168 path.insert(2, realdir)
3117 if chgbindir != self._bindir:
3169 if chgbindir != self._bindir:
3118 path.insert(1, chgbindir)
3170 path.insert(1, chgbindir)
3171 if rhgbindir != self._bindir:
3172 path.insert(1, rhgbindir)
3119 if self._testdir != runtestdir:
3173 if self._testdir != runtestdir:
3120 path = [self._testdir] + path
3174 path = [self._testdir] + path
3121 if self._tmpbindir != self._bindir:
3175 if self._tmpbindir != self._bindir:
3122 path = [self._tmpbindir] + path
3176 path = [self._tmpbindir] + path
3123 osenvironb[b"PATH"] = sepb.join(path)
3177 osenvironb[b"PATH"] = sepb.join(path)
3124
3178
3125 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3179 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
3126 # can run .../tests/run-tests.py test-foo where test-foo
3180 # can run .../tests/run-tests.py test-foo where test-foo
3127 # adds an extension to HGRC. Also include run-test.py directory to
3181 # adds an extension to HGRC. Also include run-test.py directory to
3128 # import modules like heredoctest.
3182 # import modules like heredoctest.
3129 pypath = [self._pythondir, self._testdir, runtestdir]
3183 pypath = [self._pythondir, self._testdir, runtestdir]
3130 # We have to augment PYTHONPATH, rather than simply replacing
3184 # We have to augment PYTHONPATH, rather than simply replacing
3131 # it, in case external libraries are only available via current
3185 # it, in case external libraries are only available via current
3132 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3186 # PYTHONPATH. (In particular, the Subversion bindings on OS X
3133 # are in /opt/subversion.)
3187 # are in /opt/subversion.)
3134 oldpypath = osenvironb.get(IMPL_PATH)
3188 oldpypath = osenvironb.get(IMPL_PATH)
3135 if oldpypath:
3189 if oldpypath:
3136 pypath.append(oldpypath)
3190 pypath.append(oldpypath)
3137 osenvironb[IMPL_PATH] = sepb.join(pypath)
3191 osenvironb[IMPL_PATH] = sepb.join(pypath)
3138
3192
3139 if self.options.pure:
3193 if self.options.pure:
3140 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3194 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
3141 os.environ["HGMODULEPOLICY"] = "py"
3195 os.environ["HGMODULEPOLICY"] = "py"
3142 if self.options.rust:
3196 if self.options.rust:
3143 os.environ["HGMODULEPOLICY"] = "rust+c"
3197 os.environ["HGMODULEPOLICY"] = "rust+c"
3144 if self.options.no_rust:
3198 if self.options.no_rust:
3145 current_policy = os.environ.get("HGMODULEPOLICY", "")
3199 current_policy = os.environ.get("HGMODULEPOLICY", "")
3146 if current_policy.startswith("rust+"):
3200 if current_policy.startswith("rust+"):
3147 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3201 os.environ["HGMODULEPOLICY"] = current_policy[len("rust+") :]
3148 os.environ.pop("HGWITHRUSTEXT", None)
3202 os.environ.pop("HGWITHRUSTEXT", None)
3149
3203
3150 if self.options.allow_slow_tests:
3204 if self.options.allow_slow_tests:
3151 os.environ["HGTEST_SLOW"] = "slow"
3205 os.environ["HGTEST_SLOW"] = "slow"
3152 elif 'HGTEST_SLOW' in os.environ:
3206 elif 'HGTEST_SLOW' in os.environ:
3153 del os.environ['HGTEST_SLOW']
3207 del os.environ['HGTEST_SLOW']
3154
3208
3155 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3209 self._coveragefile = os.path.join(self._testdir, b'.coverage')
3156
3210
3157 if self.options.exceptions:
3211 if self.options.exceptions:
3158 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3212 exceptionsdir = os.path.join(self._outputdir, b'exceptions')
3159 try:
3213 try:
3160 os.makedirs(exceptionsdir)
3214 os.makedirs(exceptionsdir)
3161 except OSError as e:
3215 except OSError as e:
3162 if e.errno != errno.EEXIST:
3216 if e.errno != errno.EEXIST:
3163 raise
3217 raise
3164
3218
3165 # Remove all existing exception reports.
3219 # Remove all existing exception reports.
3166 for f in os.listdir(exceptionsdir):
3220 for f in os.listdir(exceptionsdir):
3167 os.unlink(os.path.join(exceptionsdir, f))
3221 os.unlink(os.path.join(exceptionsdir, f))
3168
3222
3169 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3223 osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
3170 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3224 logexceptions = os.path.join(self._testdir, b'logexceptions.py')
3171 self.options.extra_config_opt.append(
3225 self.options.extra_config_opt.append(
3172 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3226 'extensions.logexceptions=%s' % logexceptions.decode('utf-8')
3173 )
3227 )
3174
3228
3175 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3229 vlog("# Using TESTDIR", _bytes2sys(self._testdir))
3176 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3230 vlog("# Using RUNTESTDIR", _bytes2sys(osenvironb[b'RUNTESTDIR']))
3177 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3231 vlog("# Using HGTMP", _bytes2sys(self._hgtmp))
3178 vlog("# Using PATH", os.environ["PATH"])
3232 vlog("# Using PATH", os.environ["PATH"])
3179 vlog(
3233 vlog(
3180 "# Using",
3234 "# Using",
3181 _bytes2sys(IMPL_PATH),
3235 _bytes2sys(IMPL_PATH),
3182 _bytes2sys(osenvironb[IMPL_PATH]),
3236 _bytes2sys(osenvironb[IMPL_PATH]),
3183 )
3237 )
3184 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3238 vlog("# Writing to directory", _bytes2sys(self._outputdir))
3185
3239
3186 try:
3240 try:
3187 return self._runtests(testdescs) or 0
3241 return self._runtests(testdescs) or 0
3188 finally:
3242 finally:
3189 time.sleep(0.1)
3243 time.sleep(0.1)
3190 self._cleanup()
3244 self._cleanup()
3191
3245
3192 def findtests(self, args):
3246 def findtests(self, args):
3193 """Finds possible test files from arguments.
3247 """Finds possible test files from arguments.
3194
3248
3195 If you wish to inject custom tests into the test harness, this would
3249 If you wish to inject custom tests into the test harness, this would
3196 be a good function to monkeypatch or override in a derived class.
3250 be a good function to monkeypatch or override in a derived class.
3197 """
3251 """
3198 if not args:
3252 if not args:
3199 if self.options.changed:
3253 if self.options.changed:
3200 proc = Popen4(
3254 proc = Popen4(
3201 b'hg st --rev "%s" -man0 .'
3255 b'hg st --rev "%s" -man0 .'
3202 % _sys2bytes(self.options.changed),
3256 % _sys2bytes(self.options.changed),
3203 None,
3257 None,
3204 0,
3258 0,
3205 )
3259 )
3206 stdout, stderr = proc.communicate()
3260 stdout, stderr = proc.communicate()
3207 args = stdout.strip(b'\0').split(b'\0')
3261 args = stdout.strip(b'\0').split(b'\0')
3208 else:
3262 else:
3209 args = os.listdir(b'.')
3263 args = os.listdir(b'.')
3210
3264
3211 expanded_args = []
3265 expanded_args = []
3212 for arg in args:
3266 for arg in args:
3213 if os.path.isdir(arg):
3267 if os.path.isdir(arg):
3214 if not arg.endswith(b'/'):
3268 if not arg.endswith(b'/'):
3215 arg += b'/'
3269 arg += b'/'
3216 expanded_args.extend([arg + a for a in os.listdir(arg)])
3270 expanded_args.extend([arg + a for a in os.listdir(arg)])
3217 else:
3271 else:
3218 expanded_args.append(arg)
3272 expanded_args.append(arg)
3219 args = expanded_args
3273 args = expanded_args
3220
3274
3221 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3275 testcasepattern = re.compile(br'([\w-]+\.t|py)(?:#([a-zA-Z0-9_\-.#]+))')
3222 tests = []
3276 tests = []
3223 for t in args:
3277 for t in args:
3224 case = []
3278 case = []
3225
3279
3226 if not (
3280 if not (
3227 os.path.basename(t).startswith(b'test-')
3281 os.path.basename(t).startswith(b'test-')
3228 and (t.endswith(b'.py') or t.endswith(b'.t'))
3282 and (t.endswith(b'.py') or t.endswith(b'.t'))
3229 ):
3283 ):
3230
3284
3231 m = testcasepattern.match(os.path.basename(t))
3285 m = testcasepattern.match(os.path.basename(t))
3232 if m is not None:
3286 if m is not None:
3233 t_basename, casestr = m.groups()
3287 t_basename, casestr = m.groups()
3234 t = os.path.join(os.path.dirname(t), t_basename)
3288 t = os.path.join(os.path.dirname(t), t_basename)
3235 if casestr:
3289 if casestr:
3236 case = casestr.split(b'#')
3290 case = casestr.split(b'#')
3237 else:
3291 else:
3238 continue
3292 continue
3239
3293
3240 if t.endswith(b'.t'):
3294 if t.endswith(b'.t'):
3241 # .t file may contain multiple test cases
3295 # .t file may contain multiple test cases
3242 casedimensions = parsettestcases(t)
3296 casedimensions = parsettestcases(t)
3243 if casedimensions:
3297 if casedimensions:
3244 cases = []
3298 cases = []
3245
3299
3246 def addcases(case, casedimensions):
3300 def addcases(case, casedimensions):
3247 if not casedimensions:
3301 if not casedimensions:
3248 cases.append(case)
3302 cases.append(case)
3249 else:
3303 else:
3250 for c in casedimensions[0]:
3304 for c in casedimensions[0]:
3251 addcases(case + [c], casedimensions[1:])
3305 addcases(case + [c], casedimensions[1:])
3252
3306
3253 addcases([], casedimensions)
3307 addcases([], casedimensions)
3254 if case and case in cases:
3308 if case and case in cases:
3255 cases = [case]
3309 cases = [case]
3256 elif case:
3310 elif case:
3257 # Ignore invalid cases
3311 # Ignore invalid cases
3258 cases = []
3312 cases = []
3259 else:
3313 else:
3260 pass
3314 pass
3261 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3315 tests += [{'path': t, 'case': c} for c in sorted(cases)]
3262 else:
3316 else:
3263 tests.append({'path': t})
3317 tests.append({'path': t})
3264 else:
3318 else:
3265 tests.append({'path': t})
3319 tests.append({'path': t})
3266
3320
3267 if self.options.retest:
3321 if self.options.retest:
3268 retest_args = []
3322 retest_args = []
3269 for test in tests:
3323 for test in tests:
3270 errpath = self._geterrpath(test)
3324 errpath = self._geterrpath(test)
3271 if os.path.exists(errpath):
3325 if os.path.exists(errpath):
3272 retest_args.append(test)
3326 retest_args.append(test)
3273 tests = retest_args
3327 tests = retest_args
3274 return tests
3328 return tests
3275
3329
3276 def _runtests(self, testdescs):
3330 def _runtests(self, testdescs):
3277 def _reloadtest(test, i):
3331 def _reloadtest(test, i):
3278 # convert a test back to its description dict
3332 # convert a test back to its description dict
3279 desc = {'path': test.path}
3333 desc = {'path': test.path}
3280 case = getattr(test, '_case', [])
3334 case = getattr(test, '_case', [])
3281 if case:
3335 if case:
3282 desc['case'] = case
3336 desc['case'] = case
3283 return self._gettest(desc, i)
3337 return self._gettest(desc, i)
3284
3338
3285 try:
3339 try:
3286 if self.options.restart:
3340 if self.options.restart:
3287 orig = list(testdescs)
3341 orig = list(testdescs)
3288 while testdescs:
3342 while testdescs:
3289 desc = testdescs[0]
3343 desc = testdescs[0]
3290 errpath = self._geterrpath(desc)
3344 errpath = self._geterrpath(desc)
3291 if os.path.exists(errpath):
3345 if os.path.exists(errpath):
3292 break
3346 break
3293 testdescs.pop(0)
3347 testdescs.pop(0)
3294 if not testdescs:
3348 if not testdescs:
3295 print("running all tests")
3349 print("running all tests")
3296 testdescs = orig
3350 testdescs = orig
3297
3351
3298 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3352 tests = [self._gettest(d, i) for i, d in enumerate(testdescs)]
3299 num_tests = len(tests) * self.options.runs_per_test
3353 num_tests = len(tests) * self.options.runs_per_test
3300
3354
3301 jobs = min(num_tests, self.options.jobs)
3355 jobs = min(num_tests, self.options.jobs)
3302
3356
3303 failed = False
3357 failed = False
3304 kws = self.options.keywords
3358 kws = self.options.keywords
3305 if kws is not None and PYTHON3:
3359 if kws is not None and PYTHON3:
3306 kws = kws.encode('utf-8')
3360 kws = kws.encode('utf-8')
3307
3361
3308 suite = TestSuite(
3362 suite = TestSuite(
3309 self._testdir,
3363 self._testdir,
3310 jobs=jobs,
3364 jobs=jobs,
3311 whitelist=self.options.whitelisted,
3365 whitelist=self.options.whitelisted,
3312 blacklist=self.options.blacklist,
3366 blacklist=self.options.blacklist,
3313 keywords=kws,
3367 keywords=kws,
3314 loop=self.options.loop,
3368 loop=self.options.loop,
3315 runs_per_test=self.options.runs_per_test,
3369 runs_per_test=self.options.runs_per_test,
3316 showchannels=self.options.showchannels,
3370 showchannels=self.options.showchannels,
3317 tests=tests,
3371 tests=tests,
3318 loadtest=_reloadtest,
3372 loadtest=_reloadtest,
3319 )
3373 )
3320 verbosity = 1
3374 verbosity = 1
3321 if self.options.list_tests:
3375 if self.options.list_tests:
3322 verbosity = 0
3376 verbosity = 0
3323 elif self.options.verbose:
3377 elif self.options.verbose:
3324 verbosity = 2
3378 verbosity = 2
3325 runner = TextTestRunner(self, verbosity=verbosity)
3379 runner = TextTestRunner(self, verbosity=verbosity)
3326
3380
3327 if self.options.list_tests:
3381 if self.options.list_tests:
3328 result = runner.listtests(suite)
3382 result = runner.listtests(suite)
3329 else:
3383 else:
3330 if self._installdir:
3384 if self._installdir:
3331 self._installhg()
3385 self._installhg()
3332 self._checkhglib("Testing")
3386 self._checkhglib("Testing")
3333 else:
3387 else:
3334 self._usecorrectpython()
3388 self._usecorrectpython()
3335 if self.options.chg:
3389 if self.options.chg:
3336 assert self._installdir
3390 assert self._installdir
3337 self._installchg()
3391 self._installchg()
3338
3392
3339 log(
3393 log(
3340 'running %d tests using %d parallel processes'
3394 'running %d tests using %d parallel processes'
3341 % (num_tests, jobs)
3395 % (num_tests, jobs)
3342 )
3396 )
3343
3397
3344 result = runner.run(suite)
3398 result = runner.run(suite)
3345
3399
3346 if result.failures or result.errors:
3400 if result.failures or result.errors:
3347 failed = True
3401 failed = True
3348
3402
3349 result.onEnd()
3403 result.onEnd()
3350
3404
3351 if self.options.anycoverage:
3405 if self.options.anycoverage:
3352 self._outputcoverage()
3406 self._outputcoverage()
3353 except KeyboardInterrupt:
3407 except KeyboardInterrupt:
3354 failed = True
3408 failed = True
3355 print("\ninterrupted!")
3409 print("\ninterrupted!")
3356
3410
3357 if failed:
3411 if failed:
3358 return 1
3412 return 1
3359
3413
3360 def _geterrpath(self, test):
3414 def _geterrpath(self, test):
3361 # test['path'] is a relative path
3415 # test['path'] is a relative path
3362 if 'case' in test:
3416 if 'case' in test:
3363 # for multiple dimensions test cases
3417 # for multiple dimensions test cases
3364 casestr = b'#'.join(test['case'])
3418 casestr = b'#'.join(test['case'])
3365 errpath = b'%s#%s.err' % (test['path'], casestr)
3419 errpath = b'%s#%s.err' % (test['path'], casestr)
3366 else:
3420 else:
3367 errpath = b'%s.err' % test['path']
3421 errpath = b'%s.err' % test['path']
3368 if self.options.outputdir:
3422 if self.options.outputdir:
3369 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3423 self._outputdir = canonpath(_sys2bytes(self.options.outputdir))
3370 errpath = os.path.join(self._outputdir, errpath)
3424 errpath = os.path.join(self._outputdir, errpath)
3371 return errpath
3425 return errpath
3372
3426
3373 def _getport(self, count):
3427 def _getport(self, count):
3374 port = self._ports.get(count) # do we have a cached entry?
3428 port = self._ports.get(count) # do we have a cached entry?
3375 if port is None:
3429 if port is None:
3376 portneeded = 3
3430 portneeded = 3
3377 # above 100 tries we just give up and let test reports failure
3431 # above 100 tries we just give up and let test reports failure
3378 for tries in xrange(100):
3432 for tries in xrange(100):
3379 allfree = True
3433 allfree = True
3380 port = self.options.port + self._portoffset
3434 port = self.options.port + self._portoffset
3381 for idx in xrange(portneeded):
3435 for idx in xrange(portneeded):
3382 if not checkportisavailable(port + idx):
3436 if not checkportisavailable(port + idx):
3383 allfree = False
3437 allfree = False
3384 break
3438 break
3385 self._portoffset += portneeded
3439 self._portoffset += portneeded
3386 if allfree:
3440 if allfree:
3387 break
3441 break
3388 self._ports[count] = port
3442 self._ports[count] = port
3389 return port
3443 return port
3390
3444
3391 def _gettest(self, testdesc, count):
3445 def _gettest(self, testdesc, count):
3392 """Obtain a Test by looking at its filename.
3446 """Obtain a Test by looking at its filename.
3393
3447
3394 Returns a Test instance. The Test may not be runnable if it doesn't
3448 Returns a Test instance. The Test may not be runnable if it doesn't
3395 map to a known type.
3449 map to a known type.
3396 """
3450 """
3397 path = testdesc['path']
3451 path = testdesc['path']
3398 lctest = path.lower()
3452 lctest = path.lower()
3399 testcls = Test
3453 testcls = Test
3400
3454
3401 for ext, cls in self.TESTTYPES:
3455 for ext, cls in self.TESTTYPES:
3402 if lctest.endswith(ext):
3456 if lctest.endswith(ext):
3403 testcls = cls
3457 testcls = cls
3404 break
3458 break
3405
3459
3406 refpath = os.path.join(getcwdb(), path)
3460 refpath = os.path.join(getcwdb(), path)
3407 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3461 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
3408
3462
3409 # extra keyword parameters. 'case' is used by .t tests
3463 # extra keyword parameters. 'case' is used by .t tests
3410 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3464 kwds = {k: testdesc[k] for k in ['case'] if k in testdesc}
3411
3465
3412 t = testcls(
3466 t = testcls(
3413 refpath,
3467 refpath,
3414 self._outputdir,
3468 self._outputdir,
3415 tmpdir,
3469 tmpdir,
3416 keeptmpdir=self.options.keep_tmpdir,
3470 keeptmpdir=self.options.keep_tmpdir,
3417 debug=self.options.debug,
3471 debug=self.options.debug,
3418 first=self.options.first,
3472 first=self.options.first,
3419 timeout=self.options.timeout,
3473 timeout=self.options.timeout,
3420 startport=self._getport(count),
3474 startport=self._getport(count),
3421 extraconfigopts=self.options.extra_config_opt,
3475 extraconfigopts=self.options.extra_config_opt,
3422 shell=self.options.shell,
3476 shell=self.options.shell,
3423 hgcommand=self._hgcommand,
3477 hgcommand=self._hgcommand,
3424 usechg=bool(self.options.with_chg or self.options.chg),
3478 usechg=bool(self.options.with_chg or self.options.chg),
3425 chgdebug=self.options.chg_debug,
3479 chgdebug=self.options.chg_debug,
3480 rhg_fallback_exe=self._rhg_fallback_exe,
3426 useipv6=useipv6,
3481 useipv6=useipv6,
3427 **kwds
3482 **kwds
3428 )
3483 )
3429 t.should_reload = True
3484 t.should_reload = True
3430 return t
3485 return t
3431
3486
3432 def _cleanup(self):
3487 def _cleanup(self):
3433 """Clean up state from this test invocation."""
3488 """Clean up state from this test invocation."""
3434 if self.options.keep_tmpdir:
3489 if self.options.keep_tmpdir:
3435 return
3490 return
3436
3491
3437 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3492 vlog("# Cleaning up HGTMP", _bytes2sys(self._hgtmp))
3438 shutil.rmtree(self._hgtmp, True)
3493 shutil.rmtree(self._hgtmp, True)
3439 for f in self._createdfiles:
3494 for f in self._createdfiles:
3440 try:
3495 try:
3441 os.remove(f)
3496 os.remove(f)
3442 except OSError:
3497 except OSError:
3443 pass
3498 pass
3444
3499
3445 def _usecorrectpython(self):
3500 def _usecorrectpython(self):
3446 """Configure the environment to use the appropriate Python in tests."""
3501 """Configure the environment to use the appropriate Python in tests."""
3447 # Tests must use the same interpreter as us or bad things will happen.
3502 # Tests must use the same interpreter as us or bad things will happen.
3448 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3503 pyexename = sys.platform == 'win32' and b'python.exe' or b'python3'
3449
3504
3450 # os.symlink() is a thing with py3 on Windows, but it requires
3505 # os.symlink() is a thing with py3 on Windows, but it requires
3451 # Administrator rights.
3506 # Administrator rights.
3452 if getattr(os, 'symlink', None) and os.name != 'nt':
3507 if getattr(os, 'symlink', None) and os.name != 'nt':
3453 vlog(
3508 vlog(
3454 "# Making python executable in test path a symlink to '%s'"
3509 "# Making python executable in test path a symlink to '%s'"
3455 % sysexecutable
3510 % sysexecutable
3456 )
3511 )
3457 mypython = os.path.join(self._tmpbindir, pyexename)
3512 mypython = os.path.join(self._tmpbindir, pyexename)
3458 try:
3513 try:
3459 if os.readlink(mypython) == sysexecutable:
3514 if os.readlink(mypython) == sysexecutable:
3460 return
3515 return
3461 os.unlink(mypython)
3516 os.unlink(mypython)
3462 except OSError as err:
3517 except OSError as err:
3463 if err.errno != errno.ENOENT:
3518 if err.errno != errno.ENOENT:
3464 raise
3519 raise
3465 if self._findprogram(pyexename) != sysexecutable:
3520 if self._findprogram(pyexename) != sysexecutable:
3466 try:
3521 try:
3467 os.symlink(sysexecutable, mypython)
3522 os.symlink(sysexecutable, mypython)
3468 self._createdfiles.append(mypython)
3523 self._createdfiles.append(mypython)
3469 except OSError as err:
3524 except OSError as err:
3470 # child processes may race, which is harmless
3525 # child processes may race, which is harmless
3471 if err.errno != errno.EEXIST:
3526 if err.errno != errno.EEXIST:
3472 raise
3527 raise
3473 else:
3528 else:
3474 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3529 # Windows doesn't have `python3.exe`, and MSYS cannot understand the
3475 # reparse point with that name provided by Microsoft. Create a
3530 # reparse point with that name provided by Microsoft. Create a
3476 # simple script on PATH with that name that delegates to the py3
3531 # simple script on PATH with that name that delegates to the py3
3477 # launcher so the shebang lines work.
3532 # launcher so the shebang lines work.
3478 if os.getenv('MSYSTEM'):
3533 if os.getenv('MSYSTEM'):
3479 with open(osenvironb[b'RUNTESTDIR'] + b'/python3', 'wb') as f:
3534 with open(osenvironb[b'RUNTESTDIR'] + b'/python3', 'wb') as f:
3480 f.write(b'#!/bin/sh\n')
3535 f.write(b'#!/bin/sh\n')
3481 f.write(b'py -3 "$@"\n')
3536 f.write(b'py -3 "$@"\n')
3482
3537
3483 exedir, exename = os.path.split(sysexecutable)
3538 exedir, exename = os.path.split(sysexecutable)
3484 vlog(
3539 vlog(
3485 "# Modifying search path to find %s as %s in '%s'"
3540 "# Modifying search path to find %s as %s in '%s'"
3486 % (exename, pyexename, exedir)
3541 % (exename, pyexename, exedir)
3487 )
3542 )
3488 path = os.environ['PATH'].split(os.pathsep)
3543 path = os.environ['PATH'].split(os.pathsep)
3489 while exedir in path:
3544 while exedir in path:
3490 path.remove(exedir)
3545 path.remove(exedir)
3491
3546
3492 # Binaries installed by pip into the user area like pylint.exe may
3547 # Binaries installed by pip into the user area like pylint.exe may
3493 # not be in PATH by default.
3548 # not be in PATH by default.
3494 extra_paths = [exedir]
3549 extra_paths = [exedir]
3495 vi = sys.version_info
3550 vi = sys.version_info
3496 if 'APPDATA' in os.environ:
3551 if 'APPDATA' in os.environ:
3497 scripts_dir = os.path.join(
3552 scripts_dir = os.path.join(
3498 os.environ['APPDATA'],
3553 os.environ['APPDATA'],
3499 'Python',
3554 'Python',
3500 'Python%d%d' % (vi[0], vi[1]),
3555 'Python%d%d' % (vi[0], vi[1]),
3501 'Scripts',
3556 'Scripts',
3502 )
3557 )
3503
3558
3504 if vi.major == 2:
3559 if vi.major == 2:
3505 scripts_dir = os.path.join(
3560 scripts_dir = os.path.join(
3506 os.environ['APPDATA'],
3561 os.environ['APPDATA'],
3507 'Python',
3562 'Python',
3508 'Scripts',
3563 'Scripts',
3509 )
3564 )
3510
3565
3511 extra_paths.append(scripts_dir)
3566 extra_paths.append(scripts_dir)
3512
3567
3513 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3568 os.environ['PATH'] = os.pathsep.join(extra_paths + path)
3514 if not self._findprogram(pyexename):
3569 if not self._findprogram(pyexename):
3515 print("WARNING: Cannot find %s in search path" % pyexename)
3570 print("WARNING: Cannot find %s in search path" % pyexename)
3516
3571
3517 def _installhg(self):
3572 def _installhg(self):
3518 """Install hg into the test environment.
3573 """Install hg into the test environment.
3519
3574
3520 This will also configure hg with the appropriate testing settings.
3575 This will also configure hg with the appropriate testing settings.
3521 """
3576 """
3522 vlog("# Performing temporary installation of HG")
3577 vlog("# Performing temporary installation of HG")
3523 installerrs = os.path.join(self._hgtmp, b"install.err")
3578 installerrs = os.path.join(self._hgtmp, b"install.err")
3524 compiler = ''
3579 compiler = ''
3525 if self.options.compiler:
3580 if self.options.compiler:
3526 compiler = '--compiler ' + self.options.compiler
3581 compiler = '--compiler ' + self.options.compiler
3527 setup_opts = b""
3582 setup_opts = b""
3528 if self.options.pure:
3583 if self.options.pure:
3529 setup_opts = b"--pure"
3584 setup_opts = b"--pure"
3530 elif self.options.rust:
3585 elif self.options.rust:
3531 setup_opts = b"--rust"
3586 setup_opts = b"--rust"
3532 elif self.options.no_rust:
3587 elif self.options.no_rust:
3533 setup_opts = b"--no-rust"
3588 setup_opts = b"--no-rust"
3534
3589
3535 # Run installer in hg root
3590 # Run installer in hg root
3536 script = os.path.realpath(sys.argv[0])
3591 script = os.path.realpath(sys.argv[0])
3537 exe = sysexecutable
3592 exe = sysexecutable
3538 if PYTHON3:
3593 if PYTHON3:
3539 compiler = _sys2bytes(compiler)
3594 compiler = _sys2bytes(compiler)
3540 script = _sys2bytes(script)
3595 script = _sys2bytes(script)
3541 exe = _sys2bytes(exe)
3596 exe = _sys2bytes(exe)
3542 hgroot = os.path.dirname(os.path.dirname(script))
3597 hgroot = os.path.dirname(os.path.dirname(script))
3543 self._hgroot = hgroot
3598 self._hgroot = hgroot
3544 os.chdir(hgroot)
3599 os.chdir(hgroot)
3545 nohome = b'--home=""'
3600 nohome = b'--home=""'
3546 if os.name == 'nt':
3601 if os.name == 'nt':
3547 # The --home="" trick works only on OS where os.sep == '/'
3602 # The --home="" trick works only on OS where os.sep == '/'
3548 # because of a distutils convert_path() fast-path. Avoid it at
3603 # because of a distutils convert_path() fast-path. Avoid it at
3549 # least on Windows for now, deal with .pydistutils.cfg bugs
3604 # least on Windows for now, deal with .pydistutils.cfg bugs
3550 # when they happen.
3605 # when they happen.
3551 nohome = b''
3606 nohome = b''
3552 cmd = (
3607 cmd = (
3553 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3608 b'"%(exe)s" setup.py %(setup_opts)s clean --all'
3554 b' build %(compiler)s --build-base="%(base)s"'
3609 b' build %(compiler)s --build-base="%(base)s"'
3555 b' install --force --prefix="%(prefix)s"'
3610 b' install --force --prefix="%(prefix)s"'
3556 b' --install-lib="%(libdir)s"'
3611 b' --install-lib="%(libdir)s"'
3557 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3612 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
3558 % {
3613 % {
3559 b'exe': exe,
3614 b'exe': exe,
3560 b'setup_opts': setup_opts,
3615 b'setup_opts': setup_opts,
3561 b'compiler': compiler,
3616 b'compiler': compiler,
3562 b'base': os.path.join(self._hgtmp, b"build"),
3617 b'base': os.path.join(self._hgtmp, b"build"),
3563 b'prefix': self._installdir,
3618 b'prefix': self._installdir,
3564 b'libdir': self._pythondir,
3619 b'libdir': self._pythondir,
3565 b'bindir': self._bindir,
3620 b'bindir': self._bindir,
3566 b'nohome': nohome,
3621 b'nohome': nohome,
3567 b'logfile': installerrs,
3622 b'logfile': installerrs,
3568 }
3623 }
3569 )
3624 )
3570
3625
3571 # setuptools requires install directories to exist.
3626 # setuptools requires install directories to exist.
3572 def makedirs(p):
3627 def makedirs(p):
3573 try:
3628 try:
3574 os.makedirs(p)
3629 os.makedirs(p)
3575 except OSError as e:
3630 except OSError as e:
3576 if e.errno != errno.EEXIST:
3631 if e.errno != errno.EEXIST:
3577 raise
3632 raise
3578
3633
3579 makedirs(self._pythondir)
3634 makedirs(self._pythondir)
3580 makedirs(self._bindir)
3635 makedirs(self._bindir)
3581
3636
3582 vlog("# Running", cmd.decode("utf-8"))
3637 vlog("# Running", cmd.decode("utf-8"))
3583 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3638 if subprocess.call(_bytes2sys(cmd), shell=True) == 0:
3584 if not self.options.verbose:
3639 if not self.options.verbose:
3585 try:
3640 try:
3586 os.remove(installerrs)
3641 os.remove(installerrs)
3587 except OSError as e:
3642 except OSError as e:
3588 if e.errno != errno.ENOENT:
3643 if e.errno != errno.ENOENT:
3589 raise
3644 raise
3590 else:
3645 else:
3591 with open(installerrs, 'rb') as f:
3646 with open(installerrs, 'rb') as f:
3592 for line in f:
3647 for line in f:
3593 if PYTHON3:
3648 if PYTHON3:
3594 sys.stdout.buffer.write(line)
3649 sys.stdout.buffer.write(line)
3595 else:
3650 else:
3596 sys.stdout.write(line)
3651 sys.stdout.write(line)
3597 sys.exit(1)
3652 sys.exit(1)
3598 os.chdir(self._testdir)
3653 os.chdir(self._testdir)
3599
3654
3600 self._usecorrectpython()
3655 self._usecorrectpython()
3601
3656
3602 hgbat = os.path.join(self._bindir, b'hg.bat')
3657 hgbat = os.path.join(self._bindir, b'hg.bat')
3603 if os.path.isfile(hgbat):
3658 if os.path.isfile(hgbat):
3604 # hg.bat expects to be put in bin/scripts while run-tests.py
3659 # hg.bat expects to be put in bin/scripts while run-tests.py
3605 # installation layout put it in bin/ directly. Fix it
3660 # installation layout put it in bin/ directly. Fix it
3606 with open(hgbat, 'rb') as f:
3661 with open(hgbat, 'rb') as f:
3607 data = f.read()
3662 data = f.read()
3608 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3663 if br'"%~dp0..\python" "%~dp0hg" %*' in data:
3609 data = data.replace(
3664 data = data.replace(
3610 br'"%~dp0..\python" "%~dp0hg" %*',
3665 br'"%~dp0..\python" "%~dp0hg" %*',
3611 b'"%~dp0python" "%~dp0hg" %*',
3666 b'"%~dp0python" "%~dp0hg" %*',
3612 )
3667 )
3613 with open(hgbat, 'wb') as f:
3668 with open(hgbat, 'wb') as f:
3614 f.write(data)
3669 f.write(data)
3615 else:
3670 else:
3616 print('WARNING: cannot fix hg.bat reference to python.exe')
3671 print('WARNING: cannot fix hg.bat reference to python.exe')
3617
3672
3618 if self.options.anycoverage:
3673 if self.options.anycoverage:
3619 custom = os.path.join(
3674 custom = os.path.join(
3620 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3675 osenvironb[b'RUNTESTDIR'], b'sitecustomize.py'
3621 )
3676 )
3622 target = os.path.join(self._pythondir, b'sitecustomize.py')
3677 target = os.path.join(self._pythondir, b'sitecustomize.py')
3623 vlog('# Installing coverage trigger to %s' % target)
3678 vlog('# Installing coverage trigger to %s' % target)
3624 shutil.copyfile(custom, target)
3679 shutil.copyfile(custom, target)
3625 rc = os.path.join(self._testdir, b'.coveragerc')
3680 rc = os.path.join(self._testdir, b'.coveragerc')
3626 vlog('# Installing coverage rc to %s' % rc)
3681 vlog('# Installing coverage rc to %s' % rc)
3627 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3682 osenvironb[b'COVERAGE_PROCESS_START'] = rc
3628 covdir = os.path.join(self._installdir, b'..', b'coverage')
3683 covdir = os.path.join(self._installdir, b'..', b'coverage')
3629 try:
3684 try:
3630 os.mkdir(covdir)
3685 os.mkdir(covdir)
3631 except OSError as e:
3686 except OSError as e:
3632 if e.errno != errno.EEXIST:
3687 if e.errno != errno.EEXIST:
3633 raise
3688 raise
3634
3689
3635 osenvironb[b'COVERAGE_DIR'] = covdir
3690 osenvironb[b'COVERAGE_DIR'] = covdir
3636
3691
3637 def _checkhglib(self, verb):
3692 def _checkhglib(self, verb):
3638 """Ensure that the 'mercurial' package imported by python is
3693 """Ensure that the 'mercurial' package imported by python is
3639 the one we expect it to be. If not, print a warning to stderr."""
3694 the one we expect it to be. If not, print a warning to stderr."""
3640 if (self._bindir == self._pythondir) and (
3695 if (self._bindir == self._pythondir) and (
3641 self._bindir != self._tmpbindir
3696 self._bindir != self._tmpbindir
3642 ):
3697 ):
3643 # The pythondir has been inferred from --with-hg flag.
3698 # The pythondir has been inferred from --with-hg flag.
3644 # We cannot expect anything sensible here.
3699 # We cannot expect anything sensible here.
3645 return
3700 return
3646 expecthg = os.path.join(self._pythondir, b'mercurial')
3701 expecthg = os.path.join(self._pythondir, b'mercurial')
3647 actualhg = self._gethgpath()
3702 actualhg = self._gethgpath()
3648 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3703 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
3649 sys.stderr.write(
3704 sys.stderr.write(
3650 'warning: %s with unexpected mercurial lib: %s\n'
3705 'warning: %s with unexpected mercurial lib: %s\n'
3651 ' (expected %s)\n' % (verb, actualhg, expecthg)
3706 ' (expected %s)\n' % (verb, actualhg, expecthg)
3652 )
3707 )
3653
3708
3654 def _gethgpath(self):
3709 def _gethgpath(self):
3655 """Return the path to the mercurial package that is actually found by
3710 """Return the path to the mercurial package that is actually found by
3656 the current Python interpreter."""
3711 the current Python interpreter."""
3657 if self._hgpath is not None:
3712 if self._hgpath is not None:
3658 return self._hgpath
3713 return self._hgpath
3659
3714
3660 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3715 cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
3661 cmd = cmd % PYTHON
3716 cmd = cmd % PYTHON
3662 if PYTHON3:
3717 if PYTHON3:
3663 cmd = _bytes2sys(cmd)
3718 cmd = _bytes2sys(cmd)
3664
3719
3665 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3720 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
3666 out, err = p.communicate()
3721 out, err = p.communicate()
3667
3722
3668 self._hgpath = out.strip()
3723 self._hgpath = out.strip()
3669
3724
3670 return self._hgpath
3725 return self._hgpath
3671
3726
3672 def _installchg(self):
3727 def _installchg(self):
3673 """Install chg into the test environment"""
3728 """Install chg into the test environment"""
3674 vlog('# Performing temporary installation of CHG')
3729 vlog('# Performing temporary installation of CHG')
3675 assert os.path.dirname(self._bindir) == self._installdir
3730 assert os.path.dirname(self._bindir) == self._installdir
3676 assert self._hgroot, 'must be called after _installhg()'
3731 assert self._hgroot, 'must be called after _installhg()'
3677 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3732 cmd = b'"%(make)s" clean install PREFIX="%(prefix)s"' % {
3678 b'make': b'make', # TODO: switch by option or environment?
3733 b'make': b'make', # TODO: switch by option or environment?
3679 b'prefix': self._installdir,
3734 b'prefix': self._installdir,
3680 }
3735 }
3681 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3736 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
3682 vlog("# Running", cmd)
3737 vlog("# Running", cmd)
3683 proc = subprocess.Popen(
3738 proc = subprocess.Popen(
3684 cmd,
3739 cmd,
3685 shell=True,
3740 shell=True,
3686 cwd=cwd,
3741 cwd=cwd,
3687 stdin=subprocess.PIPE,
3742 stdin=subprocess.PIPE,
3688 stdout=subprocess.PIPE,
3743 stdout=subprocess.PIPE,
3689 stderr=subprocess.STDOUT,
3744 stderr=subprocess.STDOUT,
3690 )
3745 )
3691 out, _err = proc.communicate()
3746 out, _err = proc.communicate()
3692 if proc.returncode != 0:
3747 if proc.returncode != 0:
3693 if PYTHON3:
3748 if PYTHON3:
3694 sys.stdout.buffer.write(out)
3749 sys.stdout.buffer.write(out)
3695 else:
3750 else:
3696 sys.stdout.write(out)
3751 sys.stdout.write(out)
3697 sys.exit(1)
3752 sys.exit(1)
3698
3753
3699 def _outputcoverage(self):
3754 def _outputcoverage(self):
3700 """Produce code coverage output."""
3755 """Produce code coverage output."""
3701 import coverage
3756 import coverage
3702
3757
3703 coverage = coverage.coverage
3758 coverage = coverage.coverage
3704
3759
3705 vlog('# Producing coverage report')
3760 vlog('# Producing coverage report')
3706 # chdir is the easiest way to get short, relative paths in the
3761 # chdir is the easiest way to get short, relative paths in the
3707 # output.
3762 # output.
3708 os.chdir(self._hgroot)
3763 os.chdir(self._hgroot)
3709 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3764 covdir = os.path.join(_bytes2sys(self._installdir), '..', 'coverage')
3710 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3765 cov = coverage(data_file=os.path.join(covdir, 'cov'))
3711
3766
3712 # Map install directory paths back to source directory.
3767 # Map install directory paths back to source directory.
3713 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3768 cov.config.paths['srcdir'] = ['.', _bytes2sys(self._pythondir)]
3714
3769
3715 cov.combine()
3770 cov.combine()
3716
3771
3717 omit = [
3772 omit = [
3718 _bytes2sys(os.path.join(x, b'*'))
3773 _bytes2sys(os.path.join(x, b'*'))
3719 for x in [self._bindir, self._testdir]
3774 for x in [self._bindir, self._testdir]
3720 ]
3775 ]
3721 cov.report(ignore_errors=True, omit=omit)
3776 cov.report(ignore_errors=True, omit=omit)
3722
3777
3723 if self.options.htmlcov:
3778 if self.options.htmlcov:
3724 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3779 htmldir = os.path.join(_bytes2sys(self._outputdir), 'htmlcov')
3725 cov.html_report(directory=htmldir, omit=omit)
3780 cov.html_report(directory=htmldir, omit=omit)
3726 if self.options.annotate:
3781 if self.options.annotate:
3727 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3782 adir = os.path.join(_bytes2sys(self._outputdir), 'annotated')
3728 if not os.path.isdir(adir):
3783 if not os.path.isdir(adir):
3729 os.mkdir(adir)
3784 os.mkdir(adir)
3730 cov.annotate(directory=adir, omit=omit)
3785 cov.annotate(directory=adir, omit=omit)
3731
3786
3732 def _findprogram(self, program):
3787 def _findprogram(self, program):
3733 """Search PATH for a executable program"""
3788 """Search PATH for a executable program"""
3734 dpb = _sys2bytes(os.defpath)
3789 dpb = _sys2bytes(os.defpath)
3735 sepb = _sys2bytes(os.pathsep)
3790 sepb = _sys2bytes(os.pathsep)
3736 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3791 for p in osenvironb.get(b'PATH', dpb).split(sepb):
3737 name = os.path.join(p, program)
3792 name = os.path.join(p, program)
3738 if os.name == 'nt' or os.access(name, os.X_OK):
3793 if os.name == 'nt' or os.access(name, os.X_OK):
3739 return _bytes2sys(name)
3794 return _bytes2sys(name)
3740 return None
3795 return None
3741
3796
3742 def _checktools(self):
3797 def _checktools(self):
3743 """Ensure tools required to run tests are present."""
3798 """Ensure tools required to run tests are present."""
3744 for p in self.REQUIREDTOOLS:
3799 for p in self.REQUIREDTOOLS:
3745 if os.name == 'nt' and not p.endswith(b'.exe'):
3800 if os.name == 'nt' and not p.endswith(b'.exe'):
3746 p += b'.exe'
3801 p += b'.exe'
3747 found = self._findprogram(p)
3802 found = self._findprogram(p)
3748 p = p.decode("utf-8")
3803 p = p.decode("utf-8")
3749 if found:
3804 if found:
3750 vlog("# Found prerequisite", p, "at", found)
3805 vlog("# Found prerequisite", p, "at", found)
3751 else:
3806 else:
3752 print("WARNING: Did not find prerequisite tool: %s " % p)
3807 print("WARNING: Did not find prerequisite tool: %s " % p)
3753
3808
3754
3809
3755 def aggregateexceptions(path):
3810 def aggregateexceptions(path):
3756 exceptioncounts = collections.Counter()
3811 exceptioncounts = collections.Counter()
3757 testsbyfailure = collections.defaultdict(set)
3812 testsbyfailure = collections.defaultdict(set)
3758 failuresbytest = collections.defaultdict(set)
3813 failuresbytest = collections.defaultdict(set)
3759
3814
3760 for f in os.listdir(path):
3815 for f in os.listdir(path):
3761 with open(os.path.join(path, f), 'rb') as fh:
3816 with open(os.path.join(path, f), 'rb') as fh:
3762 data = fh.read().split(b'\0')
3817 data = fh.read().split(b'\0')
3763 if len(data) != 5:
3818 if len(data) != 5:
3764 continue
3819 continue
3765
3820
3766 exc, mainframe, hgframe, hgline, testname = data
3821 exc, mainframe, hgframe, hgline, testname = data
3767 exc = exc.decode('utf-8')
3822 exc = exc.decode('utf-8')
3768 mainframe = mainframe.decode('utf-8')
3823 mainframe = mainframe.decode('utf-8')
3769 hgframe = hgframe.decode('utf-8')
3824 hgframe = hgframe.decode('utf-8')
3770 hgline = hgline.decode('utf-8')
3825 hgline = hgline.decode('utf-8')
3771 testname = testname.decode('utf-8')
3826 testname = testname.decode('utf-8')
3772
3827
3773 key = (hgframe, hgline, exc)
3828 key = (hgframe, hgline, exc)
3774 exceptioncounts[key] += 1
3829 exceptioncounts[key] += 1
3775 testsbyfailure[key].add(testname)
3830 testsbyfailure[key].add(testname)
3776 failuresbytest[testname].add(key)
3831 failuresbytest[testname].add(key)
3777
3832
3778 # Find test having fewest failures for each failure.
3833 # Find test having fewest failures for each failure.
3779 leastfailing = {}
3834 leastfailing = {}
3780 for key, tests in testsbyfailure.items():
3835 for key, tests in testsbyfailure.items():
3781 fewesttest = None
3836 fewesttest = None
3782 fewestcount = 99999999
3837 fewestcount = 99999999
3783 for test in sorted(tests):
3838 for test in sorted(tests):
3784 if len(failuresbytest[test]) < fewestcount:
3839 if len(failuresbytest[test]) < fewestcount:
3785 fewesttest = test
3840 fewesttest = test
3786 fewestcount = len(failuresbytest[test])
3841 fewestcount = len(failuresbytest[test])
3787
3842
3788 leastfailing[key] = (fewestcount, fewesttest)
3843 leastfailing[key] = (fewestcount, fewesttest)
3789
3844
3790 # Create a combined counter so we can sort by total occurrences and
3845 # Create a combined counter so we can sort by total occurrences and
3791 # impacted tests.
3846 # impacted tests.
3792 combined = {}
3847 combined = {}
3793 for key in exceptioncounts:
3848 for key in exceptioncounts:
3794 combined[key] = (
3849 combined[key] = (
3795 exceptioncounts[key],
3850 exceptioncounts[key],
3796 len(testsbyfailure[key]),
3851 len(testsbyfailure[key]),
3797 leastfailing[key][0],
3852 leastfailing[key][0],
3798 leastfailing[key][1],
3853 leastfailing[key][1],
3799 )
3854 )
3800
3855
3801 return {
3856 return {
3802 'exceptioncounts': exceptioncounts,
3857 'exceptioncounts': exceptioncounts,
3803 'total': sum(exceptioncounts.values()),
3858 'total': sum(exceptioncounts.values()),
3804 'combined': combined,
3859 'combined': combined,
3805 'leastfailing': leastfailing,
3860 'leastfailing': leastfailing,
3806 'byfailure': testsbyfailure,
3861 'byfailure': testsbyfailure,
3807 'bytest': failuresbytest,
3862 'bytest': failuresbytest,
3808 }
3863 }
3809
3864
3810
3865
3811 if __name__ == '__main__':
3866 if __name__ == '__main__':
3812 runner = TestRunner()
3867 runner = TestRunner()
3813
3868
3814 try:
3869 try:
3815 import msvcrt
3870 import msvcrt
3816
3871
3817 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3872 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
3818 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3873 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
3819 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3874 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
3820 except ImportError:
3875 except ImportError:
3821 pass
3876 pass
3822
3877
3823 sys.exit(runner.run(sys.argv[1:]))
3878 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now