[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