syncevolution/test/proxy.py
Patrick Ohly 656947c82f nightly testing: added HTTP resend tests based on an external proxy script
Compared to the existing Resend tests, the new ResendProxy tests have
the advantage that they cover one additional error case: loss of
connection before the server has a chance to send the full reply. This
happened to be one case that the syncevo-http-server wasn't handling
properly.

This failure cannot be simulated inside client-test because the
TransportAgent API doesn't provide enough control. The implementation
uses a twisted HTTP proxy which intercepts data transfer at three
different points (before sending, after sending and before receiving,
after receiving) and drops the client connection. The script listens
on four different ports, numbered consecutively, with the first one
not introducing errors, and all others representing one failure.

The test must set and unset proxy information in the underlying transport;
this patch makes that possible in SoupTransportAgent::setProxy().

To use the new tests, run "proxy.py <port>", then set
CLIENT_TEST_RESEND_PROXY=<port>.
2011-01-03 21:04:07 +01:00

103 lines
3.8 KiB
Python

from twisted.internet import reactor
from twisted.web import http
from twisted.web.proxy import Proxy, ProxyRequest, ProxyClient, ProxyClientFactory
from twisted.python import log
import sys
class ContentWrapper:
def __init__(self, content, parent):
self.content = content
self.parent = parent
def seek(self, a, b):
self.content.seek(a,b)
def read(self):
s = self.content.read()
if True:
log.msg("interrupt before send")
# This exception will abort contacting the server and appear as
# "Unhandled Error" in the proxy log. The proxy client will get
# an empty reply.
raise EOFError()
return s
def close(self):
self.content.close()
class MyProxyClient(ProxyClient):
def __init__(self, command, rest, version, headers, data, father):
ProxyClient.__init__(self, command, rest, version, headers, data, father)
def connectionMade(self):
ProxyClient.connectionMade(self)
log.msg("message sent")
# interrupt now before server can reply?
if self.father.mode == MyProxyRequest.INTERRUPT_AFTER_SEND:
log.msg("interrupt after sending")
# finish writing, but never read
self.transport.loseConnection()
# Be nice and report a real error back to the proxy client.
self.father.setResponseCode(501, "Gateway error")
self.father.responseHeaders.addRawHeader("Content-Type", "text/plain")
self.father.write("connection intentionally interrupted after sending and before receiving")
class MyProxyClientFactory(ProxyClientFactory):
protocol = MyProxyClient
def __init__(self, command, rest, version, headers, data, father):
ProxyClientFactory.__init__(self, command, rest, version, headers, data, father)
class MyProxyRequest(ProxyRequest):
protocols = {"http": MyProxyClientFactory}
INTERRUPT_BEFORE_SEND = 1
INTERRUPT_AFTER_SEND = 2
INTERRUPT_AFTER_RECEIVE = 3
baseport = 10000
def __init__(self, channel, queued, reactor=reactor):
ProxyRequest.__init__(self, channel, queued, reactor)
self.mode = channel.transport.server.port - self.baseport
def process(self):
log.msg("mode is", self.mode)
# override read() method so that we can influence the original
# process() without having to copy it; just replacing
# the read method inside the existing content instance
# would be easier, but turned out to be impossible (read-only
# attribute)
if self.mode == self.INTERRUPT_BEFORE_SEND:
# ContentWrapper will raise exception instead of delivering data
self.content = ContentWrapper(self.content, self)
ProxyRequest.process(self)
def write(self, content):
log.msg("reply:", content)
if self.mode == self.INTERRUPT_AFTER_RECEIVE:
# TODO: suppress original headers
# Original headers already sent to proxy client, but we
# can still suppress the actual data and close the
# connection to simulate a failure.
log.msg("interrupt after receive")
ProxyRequest.write(self, "")
self.transport.loseConnection()
else:
ProxyRequest.write(self, content)
class MyProxy(Proxy):
requestFactory = MyProxyRequest
if __name__ == '__main__':
log.startLogging(sys.stdout)
f = http.HTTPFactory()
f.protocol = MyProxy
reactor.listenTCP(MyProxyRequest.baseport + 0, f)
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_BEFORE_SEND, f)
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_SEND, f)
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_RECEIVE, f)
reactor.run()