##// END OF EJS Templates
tests: remove Python 2 special cases in test-stdio.py
tests: remove Python 2 special cases in test-stdio.py

File last commit:

r49801:642e31cb default
r50192:402f9f0f default
Show More
badserverext.py
461 lines | 14.9 KiB | text/x-python | PythonLexer
test-http-bad-server: move the extension in `testlib`...
r49451 # badserverext.py - Extension making servers behave badly
#
# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
# no-check-code
"""Extension to make servers behave badly.
This extension is useful for testing Mercurial behavior when various network
events occur.
Various config options in the [badserver] section influence behavior:
test-http-bad-server: rename config to use `-`...
r49452 close-before-accept
test-http-bad-server: move the extension in `testlib`...
r49451 If true, close() the server socket when a new connection arrives before
accept() is called. The server will then exit.
test-http-bad-server: rename config to use `-`...
r49452 close-after-accept
test-http-bad-server: move the extension in `testlib`...
r49451 If true, the server will close() the client socket immediately after
accept().
test-http-bad-server: rename config to use `-`...
r49452 close-after-recv-bytes
test-http-bad-server: move the extension in `testlib`...
r49451 If defined, close the client socket after receiving this many bytes.
test-http-bad-server: document that the value are actually a list...
r49463 (The value is a list, multiple values can use used to close a series of requests
request)
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 close-after-recv-patterns
If defined, the `close-after-recv-bytes` values only start counting after the
`read` operation that encountered the defined patterns.
(The value is a list, multiple values can use used to close a series of requests
request)
test-http-bad-server: rename config to use `-`...
r49452 close-after-send-bytes
test-http-bad-server: move the extension in `testlib`...
r49451 If defined, close the client socket after sending this many bytes.
test-http-bad-server: document that the value are actually a list...
r49463 (The value is a list, multiple values can use used to close a series of requests
request)
test-bad-http-server: introduce sock closing when writing a pattern...
r49464
close-after-send-patterns
If defined, close the client socket after the configured regexp is seen.
(The value is a list, multiple values can use used to close a series of requests
request)
test-http-bad-server: move the extension in `testlib`...
r49451 """
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 import re
test-http-bad-server: move the extension in `testlib`...
r49451 import socket
from mercurial import (
pycompat,
registrar,
)
from mercurial.hgweb import server
configtable = {}
configitem = registrar.configitem(configtable)
configitem(
b'badserver',
test-http-bad-server: rename config to use `-`...
r49452 b'close-after-accept',
test-http-bad-server: move the extension in `testlib`...
r49451 default=False,
)
configitem(
b'badserver',
test-http-bad-server: rename config to use `-`...
r49452 b'close-after-recv-bytes',
test-http-bad-server: move the extension in `testlib`...
r49451 default=b'0',
)
configitem(
b'badserver',
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 b'close-after-recv-patterns',
default=b'',
)
configitem(
b'badserver',
test-http-bad-server: rename config to use `-`...
r49452 b'close-after-send-bytes',
test-http-bad-server: move the extension in `testlib`...
r49451 default=b'0',
)
configitem(
b'badserver',
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 b'close-after-send-patterns',
default=b'',
)
configitem(
b'badserver',
test-http-bad-server: rename config to use `-`...
r49452 b'close-before-accept',
test-http-bad-server: move the extension in `testlib`...
r49451 default=False,
)
test-http-bad-server: track close condition in an object...
r49456
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class ConditionTracker:
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 def __init__(
self,
close_after_recv_bytes,
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 close_after_recv_patterns,
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 close_after_send_bytes,
close_after_send_patterns,
):
test-http-bad-server: track close condition in an object...
r49456 self._all_close_after_recv_bytes = close_after_recv_bytes
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 self._all_close_after_recv_patterns = close_after_recv_patterns
test-http-bad-server: track close condition in an object...
r49456 self._all_close_after_send_bytes = close_after_send_bytes
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 self._all_close_after_send_patterns = close_after_send_patterns
test-http-bad-server: track close condition in an object...
r49456
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 self.target_recv_bytes = None
self.remaining_recv_bytes = None
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 self.recv_patterns = None
self.recv_data = b''
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 self.target_send_bytes = None
self.remaining_send_bytes = None
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 self.send_pattern = None
self.send_data = b''
test-http-bad-server: factor code dealing with "write" in the new object...
r49458
test-http-bad-server: track close condition in an object...
r49456 def start_next_request(self):
"""move to the next set of close condition"""
if self._all_close_after_recv_bytes:
self.target_recv_bytes = self._all_close_after_recv_bytes.pop(0)
self.remaining_recv_bytes = self.target_recv_bytes
else:
self.target_recv_bytes = None
self.remaining_recv_bytes = None
test-bad-http-server: introduce sock closing when writing a pattern...
r49464
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 self.recv_data = b''
if self._all_close_after_recv_patterns:
self.recv_pattern = self._all_close_after_recv_patterns.pop(0)
else:
self.recv_pattern = None
test-http-bad-server: track close condition in an object...
r49456 if self._all_close_after_send_bytes:
self.target_send_bytes = self._all_close_after_send_bytes.pop(0)
self.remaining_send_bytes = self.target_send_bytes
else:
self.target_send_bytes = None
self.remaining_send_bytes = None
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 self.send_data = b''
if self._all_close_after_send_patterns:
self.send_pattern = self._all_close_after_send_patterns.pop(0)
else:
self.send_pattern = None
test-http-bad-server: track close condition in an object...
r49456 def might_close(self):
"""True, if any processing will be needed"""
if self.remaining_recv_bytes is not None:
return True
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 if self.recv_pattern is not None:
return True
test-http-bad-server: track close condition in an object...
r49456 if self.remaining_send_bytes is not None:
return True
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 if self.send_pattern is not None:
return True
test-http-bad-server: track close condition in an object...
r49456 return False
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 def forward_write(self, obj, method, data, *args, **kwargs):
"""call an underlying write function until condition are met
When the condition are met the socket is closed
"""
remaining = self.remaining_send_bytes
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 pattern = self.send_pattern
test-http-bad-server: factor code dealing with "write" in the new object...
r49458
orig = object.__getattribute__(obj, '_orig')
bmethod = method.encode('ascii')
func = getattr(orig, method)
test-http-bad-server: refactor the writing logic to avoid early return...
r49462
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 if pattern:
self.send_data += data
pieces = pattern.split(self.send_data, maxsplit=1)
if len(pieces) > 1:
dropped = len(pieces[-1])
remaining = len(data) - dropped
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 if remaining:
remaining = max(0, remaining)
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 if not remaining:
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 newdata = data
else:
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 if remaining < len(data):
newdata = data[0:remaining]
else:
newdata = data
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 remaining -= len(newdata)
self.remaining_send_bytes = remaining
test-http-bad-server: factor code dealing with "write" in the new object...
r49458
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 result = func(newdata, *args, **kwargs)
test-http-bad-server: factor code dealing with "write" in the new object...
r49458
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 if remaining is None:
obj._writelog(b'%s(%d) -> %s' % (bmethod, len(data), data))
else:
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 msg = b'%s(%d from %d) -> (%d) %s'
msg %= (bmethod, len(newdata), len(data), remaining, newdata)
obj._writelog(msg)
test-http-bad-server: factor code dealing with "write" in the new object...
r49458
test-http-bad-server: refactor the writing logic to avoid early return...
r49462 if remaining is not None and remaining <= 0:
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 obj._writelog(b'write limit reached; closing socket')
object.__getattribute__(obj, '_cond_close')()
raise Exception('connection closed after sending N bytes')
return result
test-http-bad-server: factor code dealing with "read" in the new object...
r49459 def forward_read(self, obj, method, size=-1):
"""call an underlying read function until condition are met
When the condition are met the socket is closed
"""
remaining = self.remaining_recv_bytes
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 pattern = self.recv_pattern
test-http-bad-server: factor code dealing with "read" in the new object...
r49459
orig = object.__getattribute__(obj, '_orig')
bmethod = method.encode('ascii')
func = getattr(orig, method)
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 requested_size = size
actual_size = size
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 if pattern is None and remaining:
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 if size < 0:
actual_size = remaining
else:
actual_size = min(remaining, requested_size)
test-http-bad-server: factor code dealing with "read" in the new object...
r49459
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 result = func(actual_size)
test-http-bad-server: factor code dealing with "read" in the new object...
r49459
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 if pattern is None and remaining:
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 remaining -= len(result)
self.remaining_recv_bytes = remaining
test-http-bad-server: replace the default 65537 value in output...
r49461 if requested_size == 65537:
requested_repr = b'~'
else:
requested_repr = b'%d' % requested_size
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 if requested_size == actual_size:
test-http-bad-server: replace the default 65537 value in output...
r49461 msg = b'%s(%s) -> (%d) %s'
msg %= (bmethod, requested_repr, len(result), result)
test-http-bad-server: factor code dealing with "read" in the new object...
r49459 else:
test-http-bad-server: replace the default 65537 value in output...
r49461 msg = b'%s(%d from %s) -> (%d) %s'
msg %= (bmethod, actual_size, requested_repr, len(result), result)
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 obj._writelog(msg)
test-http-bad-server: factor code dealing with "read" in the new object...
r49459
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 if pattern is not None:
self.recv_data += result
if pattern.search(self.recv_data):
# start counting bytes starting with the next read
self.recv_pattern = None
test-http-bad-server: refactor the reading logic to avoid early return...
r49460 if remaining is not None and remaining <= 0:
test-http-bad-server: factor code dealing with "read" in the new object...
r49459 obj._writelog(b'read limit reached; closing socket')
obj._cond_close()
# This is the easiest way to abort the current request.
raise Exception('connection closed after receiving N bytes')
return result
test-http-bad-server: track close condition in an object...
r49456
test-http-bad-server: move the extension in `testlib`...
r49451 # We can't adjust __class__ on a socket instance. So we define a proxy type.
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class socketproxy:
test-http-bad-server: track close condition in an object...
r49456 __slots__ = ('_orig', '_logfp', '_cond')
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: track close condition in an object...
r49456 def __init__(self, obj, logfp, condition_tracked):
test-http-bad-server: move the extension in `testlib`...
r49451 object.__setattr__(self, '_orig', obj)
object.__setattr__(self, '_logfp', logfp)
test-http-bad-server: track close condition in an object...
r49456 object.__setattr__(self, '_cond', condition_tracked)
test-http-bad-server: move the extension in `testlib`...
r49451
def __getattribute__(self, name):
test-http-bad-server: track close condition in an object...
r49456 if name in ('makefile', 'sendall', '_writelog', '_cond_close'):
test-http-bad-server: move the extension in `testlib`...
r49451 return object.__getattribute__(self, name)
return getattr(object.__getattribute__(self, '_orig'), name)
def __delattr__(self, name):
delattr(object.__getattribute__(self, '_orig'), name)
def __setattr__(self, name, value):
setattr(object.__getattribute__(self, '_orig'), name, value)
def _writelog(self, msg):
msg = msg.replace(b'\r', b'\\r').replace(b'\n', b'\\n')
object.__getattribute__(self, '_logfp').write(msg)
object.__getattribute__(self, '_logfp').write(b'\n')
object.__getattribute__(self, '_logfp').flush()
def makefile(self, mode, bufsize):
f = object.__getattribute__(self, '_orig').makefile(mode, bufsize)
logfp = object.__getattribute__(self, '_logfp')
test-http-bad-server: track close condition in an object...
r49456 cond = object.__getattribute__(self, '_cond')
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: track close condition in an object...
r49456 return fileobjectproxy(f, logfp, cond)
test-http-bad-server: move the extension in `testlib`...
r49451
def sendall(self, data, flags=0):
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 cond = object.__getattribute__(self, '_cond')
return cond.forward_write(self, 'sendall', data, flags)
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 def _cond_close(self):
object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR)
test-http-bad-server: move the extension in `testlib`...
r49451
# We can't adjust __class__ on socket._fileobject, so define a proxy.
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class fileobjectproxy:
test-http-bad-server: track close condition in an object...
r49456 __slots__ = ('_orig', '_logfp', '_cond')
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: track close condition in an object...
r49456 def __init__(self, obj, logfp, condition_tracked):
test-http-bad-server: move the extension in `testlib`...
r49451 object.__setattr__(self, '_orig', obj)
object.__setattr__(self, '_logfp', logfp)
test-http-bad-server: track close condition in an object...
r49456 object.__setattr__(self, '_cond', condition_tracked)
test-http-bad-server: move the extension in `testlib`...
r49451
def __getattribute__(self, name):
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 if name in (
'_close',
'read',
'readline',
'write',
'_writelog',
'_cond_close',
):
test-http-bad-server: move the extension in `testlib`...
r49451 return object.__getattribute__(self, name)
return getattr(object.__getattribute__(self, '_orig'), name)
def __delattr__(self, name):
delattr(object.__getattribute__(self, '_orig'), name)
def __setattr__(self, name, value):
setattr(object.__getattribute__(self, '_orig'), name, value)
def _writelog(self, msg):
msg = msg.replace(b'\r', b'\\r').replace(b'\n', b'\\n')
object.__getattribute__(self, '_logfp').write(msg)
object.__getattribute__(self, '_logfp').write(b'\n')
object.__getattribute__(self, '_logfp').flush()
def _close(self):
# Python 3 uses an io.BufferedIO instance. Python 2 uses some file
# object wrapper.
if pycompat.ispy3:
orig = object.__getattribute__(self, '_orig')
if hasattr(orig, 'raw'):
orig.raw._sock.shutdown(socket.SHUT_RDWR)
else:
self.close()
else:
self._sock.shutdown(socket.SHUT_RDWR)
def read(self, size=-1):
test-http-bad-server: factor code dealing with "read" in the new object...
r49459 cond = object.__getattribute__(self, '_cond')
return cond.forward_read(self, 'read', size)
test-http-bad-server: move the extension in `testlib`...
r49451
def readline(self, size=-1):
test-http-bad-server: factor code dealing with "read" in the new object...
r49459 cond = object.__getattribute__(self, '_cond')
return cond.forward_read(self, 'readline', size)
test-http-bad-server: move the extension in `testlib`...
r49451
def write(self, data):
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 cond = object.__getattribute__(self, '_cond')
return cond.forward_write(self, 'write', data)
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: factor code dealing with "write" in the new object...
r49458 def _cond_close(self):
self._close()
test-http-bad-server: move the extension in `testlib`...
r49451
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 def process_bytes_config(value):
test-http-bad-server: track close condition in an object...
r49456 parts = value.split(b',')
integers = [int(v) for v in parts if v]
return [v if v else None for v in integers]
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 def process_pattern_config(value):
patterns = []
for p in value.split(b','):
if not p:
p = None
else:
p = re.compile(p, re.DOTALL | re.MULTILINE)
patterns.append(p)
return patterns
test-http-bad-server: move the extension in `testlib`...
r49451 def extsetup(ui):
# Change the base HTTP server class so various events can be performed.
# See SocketServer.BaseServer for how the specially named methods work.
class badserver(server.MercurialHTTPServer):
def __init__(self, ui, *args, **kwargs):
self._ui = ui
super(badserver, self).__init__(ui, *args, **kwargs)
test-http-bad-server: track close condition in an object...
r49456 all_recv_bytes = self._ui.config(
b'badserver', b'close-after-recv-bytes'
)
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 all_recv_bytes = process_bytes_config(all_recv_bytes)
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 all_recv_pattern = self._ui.config(
b'badserver', b'close-after-recv-patterns'
)
all_recv_pattern = process_pattern_config(all_recv_pattern)
test-http-bad-server: track close condition in an object...
r49456 all_send_bytes = self._ui.config(
b'badserver', b'close-after-send-bytes'
)
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 all_send_bytes = process_bytes_config(all_send_bytes)
all_send_patterns = self._ui.config(
b'badserver', b'close-after-send-patterns'
)
all_send_patterns = process_pattern_config(all_send_patterns)
self._cond = ConditionTracker(
all_recv_bytes,
test-http-bad-server: introduce socket closing after reading a pattern...
r49483 all_recv_pattern,
test-bad-http-server: introduce sock closing when writing a pattern...
r49464 all_send_bytes,
all_send_patterns,
)
test-http-bad-server: move the extension in `testlib`...
r49451
# Need to inherit object so super() works.
class badrequesthandler(self.RequestHandlerClass, object):
def send_header(self, name, value):
# Make headers deterministic to facilitate testing.
if name.lower() == 'date':
value = 'Fri, 14 Apr 2017 00:00:00 GMT'
elif name.lower() == 'server':
value = 'badhttpserver'
return super(badrequesthandler, self).send_header(
name, value
)
self.RequestHandlerClass = badrequesthandler
# Called to accept() a pending socket.
def get_request(self):
test-http-bad-server: rename config to use `-`...
r49452 if self._ui.configbool(b'badserver', b'close-before-accept'):
test-http-bad-server: move the extension in `testlib`...
r49451 self.socket.close()
# Tells the server to stop processing more requests.
self.__shutdown_request = True
# Simulate failure to stop processing this request.
raise socket.error('close before accept')
test-http-bad-server: rename config to use `-`...
r49452 if self._ui.configbool(b'badserver', b'close-after-accept'):
test-http-bad-server: move the extension in `testlib`...
r49451 request, client_address = super(badserver, self).get_request()
request.close()
raise socket.error('close after accept')
return super(badserver, self).get_request()
# Does heavy lifting of processing a request. Invokes
# self.finish_request() which calls self.RequestHandlerClass() which
# is a hgweb.server._httprequesthandler.
def process_request(self, socket, address):
# Wrap socket in a proxy if we need to count bytes.
test-http-bad-server: track close condition in an object...
r49456 self._cond.start_next_request()
test-http-bad-server: move the extension in `testlib`...
r49451
test-http-bad-server: track close condition in an object...
r49456 if self._cond.might_close():
test-http-bad-server: move the extension in `testlib`...
r49451 socket = socketproxy(
test-http-bad-server: track close condition in an object...
r49456 socket, self.errorlog, condition_tracked=self._cond
test-http-bad-server: move the extension in `testlib`...
r49451 )
return super(badserver, self).process_request(socket, address)
server.MercurialHTTPServer = badserver