# HG changeset patch # User Pierre-Yves David # Date 1642537783 -3600 # Node ID 089cb4d6af5a22c2040553b84d61f804be566183 # Parent 348d2c6b50485508cebead5967938f09c15e8c77 test-http-bad-server: move the extension in `testlib` This seems like a better location for it. Differential Revision: https://phab.mercurial-scm.org/D12036 diff -r 348d2c6b5048 -r 089cb4d6af5a tests/badserverext.py --- a/tests/badserverext.py Mon Jan 17 19:29:41 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,384 +0,0 @@ -# badserverext.py - Extension making servers behave badly -# -# Copyright 2017 Gregory Szorc -# -# 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: - -closebeforeaccept - If true, close() the server socket when a new connection arrives before - accept() is called. The server will then exit. - -closeafteraccept - If true, the server will close() the client socket immediately after - accept(). - -closeafterrecvbytes - If defined, close the client socket after receiving this many bytes. - -closeaftersendbytes - If defined, close the client socket after sending this many bytes. -""" - -from __future__ import absolute_import - -import socket - -from mercurial import ( - pycompat, - registrar, -) - -from mercurial.hgweb import server - -configtable = {} -configitem = registrar.configitem(configtable) - -configitem( - b'badserver', - b'closeafteraccept', - default=False, -) -configitem( - b'badserver', - b'closeafterrecvbytes', - default=b'0', -) -configitem( - b'badserver', - b'closeaftersendbytes', - default=b'0', -) -configitem( - b'badserver', - b'closebeforeaccept', - default=False, -) - -# We can't adjust __class__ on a socket instance. So we define a proxy type. -class socketproxy(object): - __slots__ = ( - '_orig', - '_logfp', - '_closeafterrecvbytes', - '_closeaftersendbytes', - ) - - def __init__( - self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 - ): - object.__setattr__(self, '_orig', obj) - object.__setattr__(self, '_logfp', logfp) - object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) - object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) - - def __getattribute__(self, name): - if name in ('makefile', 'sendall', '_writelog'): - 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') - closeafterrecvbytes = object.__getattribute__( - self, '_closeafterrecvbytes' - ) - closeaftersendbytes = object.__getattribute__( - self, '_closeaftersendbytes' - ) - - return fileobjectproxy( - f, - logfp, - closeafterrecvbytes=closeafterrecvbytes, - closeaftersendbytes=closeaftersendbytes, - ) - - def sendall(self, data, flags=0): - remaining = object.__getattribute__(self, '_closeaftersendbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').sendall(data, flags) - self._writelog(b'sendall(%d) -> %s' % (len(data), data)) - return result - - if len(data) > remaining: - newdata = data[0:remaining] - else: - newdata = data - - remaining -= len(newdata) - - result = object.__getattribute__(self, '_orig').sendall(newdata, flags) - - self._writelog( - b'sendall(%d from %d) -> (%d) %s' - % (len(newdata), len(data), remaining, newdata) - ) - - object.__setattr__(self, '_closeaftersendbytes', remaining) - - if remaining <= 0: - self._writelog(b'write limit reached; closing socket') - object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR) - - raise Exception('connection closed after sending N bytes') - - return result - - -# We can't adjust __class__ on socket._fileobject, so define a proxy. -class fileobjectproxy(object): - __slots__ = ( - '_orig', - '_logfp', - '_closeafterrecvbytes', - '_closeaftersendbytes', - ) - - def __init__( - self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 - ): - object.__setattr__(self, '_orig', obj) - object.__setattr__(self, '_logfp', logfp) - object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) - object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) - - def __getattribute__(self, name): - if name in ('_close', 'read', 'readline', 'write', '_writelog'): - 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): - remaining = object.__getattribute__(self, '_closeafterrecvbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').read(size) - self._writelog( - b'read(%d) -> (%d) (%s) %s' % (size, len(result), result) - ) - return result - - origsize = size - - if size < 0: - size = remaining - else: - size = min(remaining, size) - - result = object.__getattribute__(self, '_orig').read(size) - remaining -= len(result) - - self._writelog( - b'read(%d from %d) -> (%d) %s' - % (size, origsize, len(result), result) - ) - - object.__setattr__(self, '_closeafterrecvbytes', remaining) - - if remaining <= 0: - self._writelog(b'read limit reached, closing socket') - self._close() - - # This is the easiest way to abort the current request. - raise Exception('connection closed after receiving N bytes') - - return result - - def readline(self, size=-1): - remaining = object.__getattribute__(self, '_closeafterrecvbytes') - - # No read limit. Call original function. - if not remaining: - result = object.__getattribute__(self, '_orig').readline(size) - self._writelog( - b'readline(%d) -> (%d) %s' % (size, len(result), result) - ) - return result - - origsize = size - - if size < 0: - size = remaining - else: - size = min(remaining, size) - - result = object.__getattribute__(self, '_orig').readline(size) - remaining -= len(result) - - self._writelog( - b'readline(%d from %d) -> (%d) %s' - % (size, origsize, len(result), result) - ) - - object.__setattr__(self, '_closeafterrecvbytes', remaining) - - if remaining <= 0: - self._writelog(b'read limit reached; closing socket') - self._close() - - # This is the easiest way to abort the current request. - raise Exception('connection closed after receiving N bytes') - - return result - - def write(self, data): - remaining = object.__getattribute__(self, '_closeaftersendbytes') - - # No byte limit on this operation. Call original function. - if not remaining: - self._writelog(b'write(%d) -> %s' % (len(data), data)) - result = object.__getattribute__(self, '_orig').write(data) - return result - - if len(data) > remaining: - newdata = data[0:remaining] - else: - newdata = data - - remaining -= len(newdata) - - self._writelog( - b'write(%d from %d) -> (%d) %s' - % (len(newdata), len(data), remaining, newdata) - ) - - result = object.__getattribute__(self, '_orig').write(newdata) - - object.__setattr__(self, '_closeaftersendbytes', remaining) - - if remaining <= 0: - self._writelog(b'write limit reached; closing socket') - self._close() - - raise Exception('connection closed after sending N bytes') - - return result - - -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) - - recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes') - recvbytes = recvbytes.split(b',') - self.closeafterrecvbytes = [int(v) for v in recvbytes if v] - sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes') - sendbytes = sendbytes.split(b',') - self.closeaftersendbytes = [int(v) for v in sendbytes if v] - - # 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): - if self._ui.configbool(b'badserver', b'closebeforeaccept'): - 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') - - if self._ui.configbool(b'badserver', b'closeafteraccept'): - 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. - if self.closeafterrecvbytes: - closeafterrecvbytes = self.closeafterrecvbytes.pop(0) - else: - closeafterrecvbytes = 0 - if self.closeaftersendbytes: - closeaftersendbytes = self.closeaftersendbytes.pop(0) - else: - closeaftersendbytes = 0 - - if closeafterrecvbytes or closeaftersendbytes: - socket = socketproxy( - socket, - self.errorlog, - closeafterrecvbytes=closeafterrecvbytes, - closeaftersendbytes=closeaftersendbytes, - ) - - return super(badserver, self).process_request(socket, address) - - server.MercurialHTTPServer = badserver diff -r 348d2c6b5048 -r 089cb4d6af5a tests/test-check-code.t --- a/tests/test-check-code.t Mon Jan 17 19:29:41 2022 +0100 +++ b/tests/test-check-code.t Tue Jan 18 21:29:43 2022 +0100 @@ -33,7 +33,7 @@ Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) Skipping i18n/polib.py it has no-che?k-code (glob) Skipping mercurial/statprof.py it has no-che?k-code (glob) - Skipping tests/badserverext.py it has no-che?k-code (glob) + Skipping tests/testlib/badserverext.py it has no-che?k-code (glob) @commands in debugcommands.py should be in alphabetical order. diff -r 348d2c6b5048 -r 089cb4d6af5a tests/test-http-bad-server.t --- a/tests/test-http-bad-server.t Mon Jan 17 19:29:41 2022 +0100 +++ b/tests/test-http-bad-server.t Tue Jan 18 21:29:43 2022 +0100 @@ -30,7 +30,7 @@ $ cat > .hg/hgrc << EOF > [extensions] - > badserver = $TESTDIR/badserverext.py + > badserver = $TESTDIR/testlib/badserverext.py > [server] > compressionengines = none > EOF diff -r 348d2c6b5048 -r 089cb4d6af5a tests/testlib/badserverext.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testlib/badserverext.py Tue Jan 18 21:29:43 2022 +0100 @@ -0,0 +1,384 @@ +# badserverext.py - Extension making servers behave badly +# +# Copyright 2017 Gregory Szorc +# +# 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: + +closebeforeaccept + If true, close() the server socket when a new connection arrives before + accept() is called. The server will then exit. + +closeafteraccept + If true, the server will close() the client socket immediately after + accept(). + +closeafterrecvbytes + If defined, close the client socket after receiving this many bytes. + +closeaftersendbytes + If defined, close the client socket after sending this many bytes. +""" + +from __future__ import absolute_import + +import socket + +from mercurial import ( + pycompat, + registrar, +) + +from mercurial.hgweb import server + +configtable = {} +configitem = registrar.configitem(configtable) + +configitem( + b'badserver', + b'closeafteraccept', + default=False, +) +configitem( + b'badserver', + b'closeafterrecvbytes', + default=b'0', +) +configitem( + b'badserver', + b'closeaftersendbytes', + default=b'0', +) +configitem( + b'badserver', + b'closebeforeaccept', + default=False, +) + +# We can't adjust __class__ on a socket instance. So we define a proxy type. +class socketproxy(object): + __slots__ = ( + '_orig', + '_logfp', + '_closeafterrecvbytes', + '_closeaftersendbytes', + ) + + def __init__( + self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 + ): + object.__setattr__(self, '_orig', obj) + object.__setattr__(self, '_logfp', logfp) + object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) + object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) + + def __getattribute__(self, name): + if name in ('makefile', 'sendall', '_writelog'): + 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') + closeafterrecvbytes = object.__getattribute__( + self, '_closeafterrecvbytes' + ) + closeaftersendbytes = object.__getattribute__( + self, '_closeaftersendbytes' + ) + + return fileobjectproxy( + f, + logfp, + closeafterrecvbytes=closeafterrecvbytes, + closeaftersendbytes=closeaftersendbytes, + ) + + def sendall(self, data, flags=0): + remaining = object.__getattribute__(self, '_closeaftersendbytes') + + # No read limit. Call original function. + if not remaining: + result = object.__getattribute__(self, '_orig').sendall(data, flags) + self._writelog(b'sendall(%d) -> %s' % (len(data), data)) + return result + + if len(data) > remaining: + newdata = data[0:remaining] + else: + newdata = data + + remaining -= len(newdata) + + result = object.__getattribute__(self, '_orig').sendall(newdata, flags) + + self._writelog( + b'sendall(%d from %d) -> (%d) %s' + % (len(newdata), len(data), remaining, newdata) + ) + + object.__setattr__(self, '_closeaftersendbytes', remaining) + + if remaining <= 0: + self._writelog(b'write limit reached; closing socket') + object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR) + + raise Exception('connection closed after sending N bytes') + + return result + + +# We can't adjust __class__ on socket._fileobject, so define a proxy. +class fileobjectproxy(object): + __slots__ = ( + '_orig', + '_logfp', + '_closeafterrecvbytes', + '_closeaftersendbytes', + ) + + def __init__( + self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 + ): + object.__setattr__(self, '_orig', obj) + object.__setattr__(self, '_logfp', logfp) + object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) + object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) + + def __getattribute__(self, name): + if name in ('_close', 'read', 'readline', 'write', '_writelog'): + 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): + remaining = object.__getattribute__(self, '_closeafterrecvbytes') + + # No read limit. Call original function. + if not remaining: + result = object.__getattribute__(self, '_orig').read(size) + self._writelog( + b'read(%d) -> (%d) (%s) %s' % (size, len(result), result) + ) + return result + + origsize = size + + if size < 0: + size = remaining + else: + size = min(remaining, size) + + result = object.__getattribute__(self, '_orig').read(size) + remaining -= len(result) + + self._writelog( + b'read(%d from %d) -> (%d) %s' + % (size, origsize, len(result), result) + ) + + object.__setattr__(self, '_closeafterrecvbytes', remaining) + + if remaining <= 0: + self._writelog(b'read limit reached, closing socket') + self._close() + + # This is the easiest way to abort the current request. + raise Exception('connection closed after receiving N bytes') + + return result + + def readline(self, size=-1): + remaining = object.__getattribute__(self, '_closeafterrecvbytes') + + # No read limit. Call original function. + if not remaining: + result = object.__getattribute__(self, '_orig').readline(size) + self._writelog( + b'readline(%d) -> (%d) %s' % (size, len(result), result) + ) + return result + + origsize = size + + if size < 0: + size = remaining + else: + size = min(remaining, size) + + result = object.__getattribute__(self, '_orig').readline(size) + remaining -= len(result) + + self._writelog( + b'readline(%d from %d) -> (%d) %s' + % (size, origsize, len(result), result) + ) + + object.__setattr__(self, '_closeafterrecvbytes', remaining) + + if remaining <= 0: + self._writelog(b'read limit reached; closing socket') + self._close() + + # This is the easiest way to abort the current request. + raise Exception('connection closed after receiving N bytes') + + return result + + def write(self, data): + remaining = object.__getattribute__(self, '_closeaftersendbytes') + + # No byte limit on this operation. Call original function. + if not remaining: + self._writelog(b'write(%d) -> %s' % (len(data), data)) + result = object.__getattribute__(self, '_orig').write(data) + return result + + if len(data) > remaining: + newdata = data[0:remaining] + else: + newdata = data + + remaining -= len(newdata) + + self._writelog( + b'write(%d from %d) -> (%d) %s' + % (len(newdata), len(data), remaining, newdata) + ) + + result = object.__getattribute__(self, '_orig').write(newdata) + + object.__setattr__(self, '_closeaftersendbytes', remaining) + + if remaining <= 0: + self._writelog(b'write limit reached; closing socket') + self._close() + + raise Exception('connection closed after sending N bytes') + + return result + + +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) + + recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes') + recvbytes = recvbytes.split(b',') + self.closeafterrecvbytes = [int(v) for v in recvbytes if v] + sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes') + sendbytes = sendbytes.split(b',') + self.closeaftersendbytes = [int(v) for v in sendbytes if v] + + # 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): + if self._ui.configbool(b'badserver', b'closebeforeaccept'): + 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') + + if self._ui.configbool(b'badserver', b'closeafteraccept'): + 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. + if self.closeafterrecvbytes: + closeafterrecvbytes = self.closeafterrecvbytes.pop(0) + else: + closeafterrecvbytes = 0 + if self.closeaftersendbytes: + closeaftersendbytes = self.closeaftersendbytes.pop(0) + else: + closeaftersendbytes = 0 + + if closeafterrecvbytes or closeaftersendbytes: + socket = socketproxy( + socket, + self.errorlog, + closeafterrecvbytes=closeafterrecvbytes, + closeaftersendbytes=closeaftersendbytes, + ) + + return super(badserver, self).process_request(socket, address) + + server.MercurialHTTPServer = badserver