[Checkins] SVN: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/ Initial attempt at replacing zope.testbrowser.wsgi with WebTest integration.

Brian Sutherland jinty at web.de
Wed Feb 2 13:02:30 EST 2011


Log message for revision 120055:
  Initial attempt at replacing zope.testbrowser.wsgi with WebTest integration.

Changed:
  U   zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/README.txt
  U   zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/tests/test_doctests.py
  D   zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/webtest.py
  D   zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py
  A   zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py

-=-
Modified: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/README.txt
===================================================================
--- zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/README.txt	2011-02-02 15:30:40 UTC (rev 120054)
+++ zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/README.txt	2011-02-02 18:02:30 UTC (rev 120055)
@@ -21,18 +21,24 @@
 ~~~~~~~~~~~~~~~~~
 
 There is also a special version of the ``Browser`` class which uses
-`wsgi_intercept`_ and can be used to do functional testing of WSGI
+`WebTest`_ and can be used to do functional testing of WSGI
 applications, it can be imported from ``zope.testbrowser.wsgi``:
 
     >>> from zope.testbrowser.wsgi import Browser as WSGIBrowser
-    >>> browser = WSGIBrowser()
+    >>> from wsgiref.simple_server import demo_app
+    >>> browser = WSGIBrowser('http://localhost/', wsgi_app=demo_app)
+    >>> print browser.contents
+    Hello world!
+    ...
 
-.. _`wsgi_intercept`: http://pypi.python.org/pypi/wsgi_intercept
+.. _`WebTest`: http://pypi.python.org/pypi/WebTest
 
 To use this browser you have to:
 
   * use the `wsgi` extra of the ``zope.testbrowser`` egg,
 
+You can also use it with zope layers by:
+
   * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
     ``make_wsgi_app`` method,
 
@@ -47,18 +53,6 @@
 
 Where ``simple_app`` is the callable of your WSGI application.
 
-Zope 3 Test Browser
-~~~~~~~~~~~~~~~~~~~
-
-WSGI applications can also be tested directly when wrapped by WebTest:
-
-    >>> from zope.testbrowser.webtest import Browser as WSGIBrowser
-    >>> from wsgiref.simple_server import demo_app
-    >>> browser = WSGIBrowser(demo_app, url='http://localhost/')
-    >>> print browser.contents
-    Hello world!
-    ...
-
 Bowser Usage
 ------------
 

Modified: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/tests/test_doctests.py
===================================================================
--- zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/tests/test_doctests.py	2011-02-02 15:30:40 UTC (rev 120054)
+++ zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/tests/test_doctests.py	2011-02-02 18:02:30 UTC (rev 120055)
@@ -16,11 +16,12 @@
 import unittest
 
 import zope.testbrowser.ftests.wsgitestapp
-import zope.testbrowser.webtest
+import zope.testbrowser.wsgi
 
 def make_browser(*args, **kw):
-    app = zope.testbrowser.ftests.wsgitestapp.WSGITestApplication()
-    return zope.testbrowser.webtest.Browser(app, *args, **kw)
+    assert 'wsgi_app' not in kw
+    kw['wsgi_app'] = zope.testbrowser.ftests.wsgitestapp.WSGITestApplication()
+    return zope.testbrowser.wsgi.Browser(*args, **kw)
 
 def test_suite():
     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS

Deleted: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/webtest.py
===================================================================
--- zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/webtest.py	2011-02-02 15:30:40 UTC (rev 120054)
+++ zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/webtest.py	2011-02-02 18:02:30 UTC (rev 120055)
@@ -1,147 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2010 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""WSGI-specific testing code
-"""
-
-from __future__ import absolute_import
-
-import cStringIO
-import Cookie
-import httplib
-import socket
-import sys
-
-import mechanize
-from webtest import TestApp
-
-import zope.testbrowser.browser
-import zope.testbrowser.connection
-
-class WSGIConnection(object):
-    """A ``mechanize`` compatible connection object."""
-
-    def __init__(self, test_app, host, timeout=None):
-        self._test_app = TestApp(test_app)
-        self.host = host
-
-    def set_debuglevel(self, level):
-        pass
-
-    def _quote(self, url):
-        # XXX: is this necessary with WebTest? Was cargeo-culted from the 
-        # Zope Publisher Connection
-        return url.replace(' ', '%20')
-
-    def request(self, method, url, body=None, headers=None):
-        """Send a request to the publisher.
-
-        The response will be stored in ``self.response``.
-        """
-        if body is None:
-            body = ''
-
-        if url == '':
-            url = '/'
-
-        url = self._quote(url)
-
-        # Extract the handle_error option header
-        if sys.version_info >= (2,5):
-            handle_errors_key = 'X-Zope-Handle-Errors'
-        else:
-            handle_errors_key = 'X-zope-handle-errors'
-        handle_errors_header = headers.get(handle_errors_key, True)
-        if handle_errors_key in headers:
-            del headers[handle_errors_key]
-
-        # Translate string to boolean.
-        handle_errors = {'False': False}.get(handle_errors_header, True)
-        extra_environ = {}
-        if not handle_errors:
-            # There doesn't seem to be a "Right Way" to do this
-            extra_environ['wsgi.handleErrors'] = False # zope.app.wsgi does this
-            extra_environ['paste.throw_errors'] = True # the paste way of doing this
-
-        scheme_key = 'X-Zope-Scheme'
-        extra_environ['wsgi.url_scheme'] = headers.get(scheme_key, 'http')
-        if scheme_key in headers:
-            del headers[scheme_key]
-
-        app = self._test_app
-
-        # clear our app cookies so that our testbrowser cookie headers don't
-        # get stomped
-        app.cookies.clear()
-
-        # pass the request to webtest
-        if method == 'GET':
-            assert not body, body
-            response = app.get(url, headers=headers, expect_errors=True, extra_environ=extra_environ)
-        elif method == 'POST':
-            response = app.post(url, body, headers=headers, expect_errors=True, extra_environ=extra_environ)
-        else:
-            raise Exception('Couldnt handle method %s' % method)
-
-        self.response = response
-
-    def getresponse(self):
-        """Return a ``mechanize`` compatible response.
-
-        The goal of ths method is to convert the WebTest's reseponse to
-        a ``mechanize`` compatible response, which is also understood by
-        mechanize.
-        """
-        response = self.response
-        status = int(response.status[:3])
-        reason = response.status[4:]
-
-        headers = response.headers.items()
-        headers.sort()
-        headers.insert(0, ('Status', response.status))
-        headers = '\r\n'.join('%s: %s' % h for h in headers)
-        content = response.body
-        return zope.testbrowser.connection.Response(content, headers, status, reason)
-
-
-class WSGIHTTPHandler(zope.testbrowser.connection.HTTPHandler):
-
-    def __init__(self, test_app, *args, **kw):
-        self._test_app = test_app
-        zope.testbrowser.connection.HTTPHandler.__init__(self, *args, **kw)
-
-    def _connect(self, *args, **kw):
-        return WSGIConnection(self._test_app, *args, **kw)
-
-    def https_request(self, req):
-        req.add_unredirected_header('X-Zope-Scheme', 'https')
-        return self.http_request(req)
-
-
-class WSGIMechanizeBrowser(zope.testbrowser.connection.MechanizeBrowser):
-    """Special ``mechanize`` browser using the WSGI HTTP handler."""
-
-    def __init__(self, test_app, *args, **kw):
-        self._test_app = test_app
-        zope.testbrowser.connection.MechanizeBrowser.__init__(self, *args, **kw)
-
-    def _http_handler(self, *args, **kw):
-        return WSGIHTTPHandler(self._test_app, *args, **kw)
-
-
-class Browser(zope.testbrowser.browser.Browser):
-    """A WSGI `testbrowser` Browser that uses a WebTest wrapped WSGI app."""
-
-    def __init__(self, test_app, url=None):
-        mech_browser = WSGIMechanizeBrowser(test_app)
-        super(Browser, self).__init__(url=url, mech_browser=mech_browser)

Deleted: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py
===================================================================
--- zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py	2011-02-02 15:30:40 UTC (rev 120054)
+++ zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py	2011-02-02 18:02:30 UTC (rev 120055)
@@ -1,129 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2010-2011 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-import base64
-import re
-import wsgi_intercept
-import wsgi_intercept.mechanize_intercept
-import zope.testbrowser.browser
-
-
-# List of hostname where the test browser/http function replies to
-TEST_HOSTS = ['localhost', '127.0.0.1']
-
-
-class InterceptBrowser(wsgi_intercept.mechanize_intercept.Browser):
-
-    default_schemes = ['http']
-    default_others = ['_http_error',
-                      '_http_default_error']
-    default_features = ['_redirect', '_cookies', '_referer', '_refresh',
-                        '_equiv', '_basicauth', '_digestauth']
-
-
-class Browser(zope.testbrowser.browser.Browser):
-    """Override the zope.testbrowser.browser.Browser interface so that it
-    uses InterceptBrowser.
-    """
-
-    def __init__(self, *args, **kw):
-        kw['mech_browser'] = InterceptBrowser()
-        super(Browser, self).__init__(*args, **kw)
-
-
-# Compatibility helpers to behave like zope.app.testing
-
-basicre = re.compile('Basic (.+)?:(.+)?$')
-
-
-def auth_header(header):
-    """This function takes an authorization HTTP header and encode the
-    couple user, password into base 64 like the HTTP protocol wants
-    it.
-    """
-    match = basicre.match(header)
-    if match:
-        u, p = match.group(1, 2)
-        if u is None:
-            u = ''
-        if p is None:
-            p = ''
-        auth = base64.encodestring('%s:%s' % (u, p))
-        return 'Basic %s' % auth[:-1]
-    return header
-
-
-def is_wanted_header(header):
-    """Return True if the given HTTP header key is wanted.
-    """
-    key, value = header
-    return key.lower() not in ('x-content-type-warning', 'x-powered-by')
-
-
-class AuthorizationMiddleware(object):
-    """This middleware makes the WSGI application compatible with the
-    HTTPCaller behavior defined in zope.app.testing.functional:
-    - It modifies the HTTP Authorization header to encode user and
-      password into base64 if it is Basic authentication.
-    """
-
-    def __init__(self, wsgi_stack):
-        self.wsgi_stack = wsgi_stack
-
-    def __call__(self, environ, start_response):
-        # Handle authorization
-        auth_key = 'HTTP_AUTHORIZATION'
-        if auth_key in environ:
-            environ[auth_key] = auth_header(environ[auth_key])
-
-        # Remove unwanted headers
-        def application_start_response(status, headers, exc_info=None):
-            headers = filter(is_wanted_header, headers)
-            start_response(status, headers)
-
-        for entry in self.wsgi_stack(environ, application_start_response):
-            yield entry
-
-
-class Layer(object):
-    """Test layer which sets up WSGI application for use with
-    wsgi_intercept/testbrowser.
-
-    """
-
-    __bases__ = ()
-    __name__ = 'Layer'
-
-    def make_wsgi_app(self):
-        # Override this method in subclasses of this layer in order to set up
-        # the WSGI application.
-        raise NotImplementedError
-
-    def cooperative_super(self, method_name):
-        # Calling `super` for multiple inheritance:
-        method = getattr(super(Layer, self), method_name, None)
-        if method is not None:
-            method()
-
-    def setUp(self):
-        self.cooperative_super('setUp')
-        self.app = self.make_wsgi_app()
-        factory = lambda: AuthorizationMiddleware(self.app)
-
-        for host in TEST_HOSTS:
-            wsgi_intercept.add_wsgi_intercept(host, 80, factory)
-
-    def tearDown(self):
-        for host in TEST_HOSTS:
-            wsgi_intercept.remove_wsgi_intercept(host, 80)
-        self.cooperative_super('tearDown')

Copied: zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py (from rev 120009, zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/webtest.py)
===================================================================
--- zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py	                        (rev 0)
+++ zope.testbrowser/branches/jinty-webtest3/src/zope/testbrowser/wsgi.py	2011-02-02 18:02:30 UTC (rev 120055)
@@ -0,0 +1,183 @@
+##############################################################################
+#
+# Copyright (c) 2010 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""WSGI-specific testing code
+"""
+
+from __future__ import absolute_import
+
+import cStringIO
+import Cookie
+import httplib
+import socket
+import sys
+
+import mechanize
+from webtest import TestApp
+
+import zope.testbrowser.browser
+import zope.testbrowser.connection
+
+class WSGIConnection(object):
+    """A ``mechanize`` compatible connection object."""
+
+    def __init__(self, test_app, host, timeout=None):
+        self._test_app = TestApp(test_app)
+        self.host = host
+
+    def set_debuglevel(self, level):
+        pass
+
+    def _quote(self, url):
+        # XXX: is this necessary with WebTest? Was cargeo-culted from the 
+        # Zope Publisher Connection
+        return url.replace(' ', '%20')
+
+    def request(self, method, url, body=None, headers=None):
+        """Send a request to the publisher.
+
+        The response will be stored in ``self.response``.
+        """
+        if body is None:
+            body = ''
+
+        if url == '':
+            url = '/'
+
+        url = self._quote(url)
+
+        # Extract the handle_error option header
+        if sys.version_info >= (2,5):
+            handle_errors_key = 'X-Zope-Handle-Errors'
+        else:
+            handle_errors_key = 'X-zope-handle-errors'
+        handle_errors_header = headers.get(handle_errors_key, True)
+        if handle_errors_key in headers:
+            del headers[handle_errors_key]
+
+        # Translate string to boolean.
+        handle_errors = {'False': False}.get(handle_errors_header, True)
+        extra_environ = {}
+        if not handle_errors:
+            # There doesn't seem to be a "Right Way" to do this
+            extra_environ['wsgi.handleErrors'] = False # zope.app.wsgi does this
+            extra_environ['paste.throw_errors'] = True # the paste way of doing this
+
+        scheme_key = 'X-Zope-Scheme'
+        extra_environ['wsgi.url_scheme'] = headers.get(scheme_key, 'http')
+        if scheme_key in headers:
+            del headers[scheme_key]
+
+        app = self._test_app
+
+        # clear our app cookies so that our testbrowser cookie headers don't
+        # get stomped
+        app.cookies.clear()
+
+        # pass the request to webtest
+        if method == 'GET':
+            assert not body, body
+            response = app.get(url, headers=headers, expect_errors=True, extra_environ=extra_environ)
+        elif method == 'POST':
+            response = app.post(url, body, headers=headers, expect_errors=True, extra_environ=extra_environ)
+        else:
+            raise Exception('Couldnt handle method %s' % method)
+
+        self.response = response
+
+    def getresponse(self):
+        """Return a ``mechanize`` compatible response.
+
+        The goal of ths method is to convert the WebTest's reseponse to
+        a ``mechanize`` compatible response, which is also understood by
+        mechanize.
+        """
+        response = self.response
+        status = int(response.status[:3])
+        reason = response.status[4:]
+
+        headers = response.headers.items()
+        headers.sort()
+        headers.insert(0, ('Status', response.status))
+        headers = '\r\n'.join('%s: %s' % h for h in headers)
+        content = response.body
+        return zope.testbrowser.connection.Response(content, headers, status, reason)
+
+
+class WSGIHTTPHandler(zope.testbrowser.connection.HTTPHandler):
+
+    def __init__(self, test_app, *args, **kw):
+        self._test_app = test_app
+        zope.testbrowser.connection.HTTPHandler.__init__(self, *args, **kw)
+
+    def _connect(self, *args, **kw):
+        return WSGIConnection(self._test_app, *args, **kw)
+
+    def https_request(self, req):
+        req.add_unredirected_header('X-Zope-Scheme', 'https')
+        return self.http_request(req)
+
+
+class WSGIMechanizeBrowser(zope.testbrowser.connection.MechanizeBrowser):
+    """Special ``mechanize`` browser using the WSGI HTTP handler."""
+
+    def __init__(self, test_app, *args, **kw):
+        self._test_app = test_app
+        zope.testbrowser.connection.MechanizeBrowser.__init__(self, *args, **kw)
+
+    def _http_handler(self, *args, **kw):
+        return WSGIHTTPHandler(self._test_app, *args, **kw)
+
+
+class Browser(zope.testbrowser.browser.Browser):
+    """A WSGI `testbrowser` Browser that uses a WebTest wrapped WSGI app."""
+
+    def __init__(self, url=None, wsgi_app=None):
+        if wsgi_app is None:
+            wsgi_app = _APP_UNDER_TEST
+        if wsgi_app is None:
+            raise AssertionError("wsgi_app not provided or zope.testbrowser.wsgi.Layer not setup")
+        mech_browser = WSGIMechanizeBrowser(wsgi_app)
+        super(Browser, self).__init__(url=url, mech_browser=mech_browser)
+
+_APP_UNDER_TEST = None # setup and torn down by the Layer class
+
+class Layer(object):
+    """Test layer which sets up WSGI application for use with
+    WebTest/testbrowser.
+
+    """
+
+    __bases__ = ()
+    __name__ = 'Layer'
+
+    def make_wsgi_app(self):
+        # Override this method in subclasses of this layer in order to set up
+        # the WSGI application.
+        raise NotImplementedError
+
+    def cooperative_super(self, method_name):
+        # Calling `super` for multiple inheritance:
+        method = getattr(super(Layer, self), method_name, None)
+        if method is not None:
+            method()
+
+    def setUp(self):
+        self.cooperative_super('setUp')
+        global _APP_UNDER_TEST
+        _APP_UNDER_TEST = self.make_wsgi_app()
+
+    def tearDown(self):
+        global _APP_UNDER_TEST
+        _APP_UNDER_TEST = None
+        self.cooperative_super('tearDown')



More information about the checkins mailing list