[tor-commits] [bridgedb/master] Revise HTTPS DummyRequest test utility for changes in Twisted>14.0.2.
isis at torproject.org
isis at torproject.org
Thu Jul 28 16:41:11 UTC 2016
commit 22817ee11fd7d21cf38ee6050a9bcd3dd2b7849a
Author: Isis Lovecruft <isis at torproject.org>
Date: Mon May 2 16:16:40 2016 +0000
Revise HTTPS DummyRequest test utility for changes in Twisted>14.0.2.
---
test/https_helpers.py | 228 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 226 insertions(+), 2 deletions(-)
diff --git a/test/https_helpers.py b/test/https_helpers.py
index cc1ddfa..14755ec 100644
--- a/test/https_helpers.py
+++ b/test/https_helpers.py
@@ -13,6 +13,10 @@
import io
+from twisted.internet.defer import Deferred
+from twisted.internet.address import IPv4Address
+from twisted.web.http_headers import Headers
+from twisted.web.server import NOT_DONE_YET, Session
from twisted.web.test import requesthelper
from bridgedb.persistent import Conf
@@ -121,15 +125,232 @@ class DummyHTTPSDistributor(object):
return [self._bridge_class() for _ in range(self._bridgesPerResponseMin)]
-class DummyRequest(requesthelper.DummyRequest):
+class RequestHelperDummyRequest(object):
+ """
+ Represents a dummy or fake request.
+
+ Taken from Twisted-14.0.2 because this helper class changed in 16.0.0.
+
+ @ivar _finishedDeferreds: C{None} or a C{list} of L{Deferreds} which will
+ be called back with C{None} when C{finish} is called or which will be
+ errbacked if C{processingFailed} is called.
+
+ @type headers: C{dict}
+ @ivar headers: A mapping of header name to header value for all request
+ headers.
+
+ @type outgoingHeaders: C{dict}
+ @ivar outgoingHeaders: A mapping of header name to header value for all
+ response headers.
+
+ @type responseCode: C{int}
+ @ivar responseCode: The response code which was passed to
+ C{setResponseCode}.
+
+ @type written: C{list} of C{bytes}
+ @ivar written: The bytes which have been written to the request.
+ """
+ uri = b'http://dummy/'
+ method = b'GET'
+ client = None
+
+ def registerProducer(self, prod,s):
+ self.go = 1
+ while self.go:
+ prod.resumeProducing()
+
+ def unregisterProducer(self):
+ self.go = 0
+
+
+ def __init__(self, postpath, session=None):
+ self.sitepath = []
+ self.written = []
+ self.finished = 0
+ self.postpath = postpath
+ self.prepath = []
+ self.session = None
+ self.protoSession = session or Session(0, self)
+ self.args = {}
+ self.outgoingHeaders = {}
+ self.requestHeaders = Headers()
+ self.responseHeaders = Headers()
+ self.responseCode = None
+ self.headers = {}
+ self._finishedDeferreds = []
+ self._serverName = b"dummy"
+ self.clientproto = b"HTTP/1.0"
+
+ def getHeader(self, name):
+ """
+ Retrieve the value of a request header.
+
+ @type name: C{bytes}
+ @param name: The name of the request header for which to retrieve the
+ value. Header names are compared case-insensitively.
+
+ @rtype: C{bytes} or L{NoneType}
+ @return: The value of the specified request header.
+ """
+ return self.headers.get(name.lower(), None)
+
+
+ def getAllHeaders(self):
+ """
+ Retrieve all the values of the request headers as a dictionary.
+
+ @return: The entire C{headers} L{dict}.
+ """
+ return self.headers
+
+
+ def setHeader(self, name, value):
+ """TODO: make this assert on write() if the header is content-length
+ """
+ self.outgoingHeaders[name.lower()] = value
+
+ def getSession(self):
+ if self.session:
+ return self.session
+ assert not self.written, "Session cannot be requested after data has been written."
+ self.session = self.protoSession
+ return self.session
+
+
+ def render(self, resource):
+ """
+ Render the given resource as a response to this request.
+
+ This implementation only handles a few of the most common behaviors of
+ resources. It can handle a render method that returns a string or
+ C{NOT_DONE_YET}. It doesn't know anything about the semantics of
+ request methods (eg HEAD) nor how to set any particular headers.
+ Basically, it's largely broken, but sufficient for some tests at least.
+ It should B{not} be expanded to do all the same stuff L{Request} does.
+ Instead, L{DummyRequest} should be phased out and L{Request} (or some
+ other real code factored in a different way) used.
+ """
+ result = resource.render(self)
+ if result is NOT_DONE_YET:
+ return
+ self.write(result)
+ self.finish()
+
+
+ def write(self, data):
+ if not isinstance(data, bytes):
+ raise TypeError("write() only accepts bytes")
+ self.written.append(data)
+
+ def notifyFinish(self):
+ """
+ Return a L{Deferred} which is called back with C{None} when the request
+ is finished. This will probably only work if you haven't called
+ C{finish} yet.
+ """
+ finished = Deferred()
+ self._finishedDeferreds.append(finished)
+ return finished
+
+
+ def finish(self):
+ """
+ Record that the request is finished and callback and L{Deferred}s
+ waiting for notification of this.
+ """
+ self.finished = self.finished + 1
+ if self._finishedDeferreds is not None:
+ observers = self._finishedDeferreds
+ self._finishedDeferreds = None
+ for obs in observers:
+ obs.callback(None)
+
+
+ def processingFailed(self, reason):
+ """
+ Errback and L{Deferreds} waiting for finish notification.
+ """
+ if self._finishedDeferreds is not None:
+ observers = self._finishedDeferreds
+ self._finishedDeferreds = None
+ for obs in observers:
+ obs.errback(reason)
+
+
+ def addArg(self, name, value):
+ self.args[name] = [value]
+
+
+ def setResponseCode(self, code, message=None):
+ """
+ Set the HTTP status response code, but takes care that this is called
+ before any data is written.
+ """
+ assert not self.written, "Response code cannot be set after data has been written: %s." % "@@@@".join(self.written)
+ self.responseCode = code
+ self.responseMessage = message
+
+
+ def setLastModified(self, when):
+ assert not self.written, "Last-Modified cannot be set after data has been written: %s." % "@@@@".join(self.written)
+
+
+ def setETag(self, tag):
+ assert not self.written, "ETag cannot be set after data has been written: %s." % "@@@@".join(self.written)
+
+
+ def getClientIP(self):
+ """
+ Return the IPv4 address of the client which made this request, if there
+ is one, otherwise C{None}.
+ """
+ if isinstance(self.client, IPv4Address):
+ return self.client.host
+ return None
+
+
+ def getRequestHostname(self):
+ """
+ Get a dummy hostname associated to the HTTP request.
+
+ @rtype: C{bytes}
+ @returns: a dummy hostname
+ """
+ return self._serverName
+
+
+ def getHost(self):
+ """
+ Get a dummy transport's host.
+
+ @rtype: C{IPv4Address}
+ @returns: a dummy transport's host
+ """
+ return IPv4Address('TCP', '127.0.0.1', 80)
+
+
+ def getClient(self):
+ """
+ Stub to get the client doing the HTTP request.
+ This merely just ensures that this method exists here. Feel free to
+ extend it.
+ """
+
+
+class DummyRequest(RequestHelperDummyRequest):
"""Wrapper for :api:`twisted.test.requesthelper.DummyRequest` to add
redirect support.
"""
def __init__(self, *args, **kwargs):
- requesthelper.DummyRequest.__init__(self, *args, **kwargs)
+ RequestHelperDummyRequest.__init__(self, *args, **kwargs)
self.redirect = self._redirect(self)
self.content = io.StringIO()
+ self.headers = {} # Needed for Twisted>14.0.2
+ #self.outgoingHeaders = {}
+ #self.responseHeaders = Headers()
+ #self.requestHeaders = Headers()
+
def writeContent(self, data):
"""Add some **data** to the faked body of this request.
@@ -159,3 +380,6 @@ class DummyRequest(requesthelper.DummyRequest):
newRequest = type(request)
newRequest.uri = request.uri
return newRequest
+
+
+#DummyRequest = RequestHelperDummyRequest
More information about the tor-commits
mailing list