Skip to content

Commit

Permalink
- tox now uses the Zope test runner to execute tests, since setup tes…
Browse files Browse the repository at this point in the history
…ts cannot

  deal with layers, especially when they need to spawn sub-proceeses.

- Switched all functional tests to use `WebTest` instead of
  ``zope.testbrowser``. Setup proper layering.

- Do not rely on ``zope.testbrowser.wsgi`` WSGI layer support. It was not
  needed anyways.

- Minimized the ``ftesting.zcml`` setup.
  • Loading branch information
strichter committed Mar 3, 2013
1 parent edf871e commit 262d9b9
Show file tree
Hide file tree
Showing 14 changed files with 160 additions and 145 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*.pyc
*.so
*.dll
*.egg
__pycache__
src/*.egg-info

Expand Down
11 changes: 10 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ CHANGES
4.0.0a3 (unreleased)
--------------------

- Nothing changed yet.
- tox now uses the Zope test runner to execute tests, since setup tests cannot
deal with layers, especially when they need to spawn sub-proceeses.

- Switched all functional tests to use `WebTest` instead of
``zope.testbrowser``. Setup proper layering.

- Do not rely on ``zope.testbrowser.wsgi`` WSGI layer support. It was not
needed anyways.

- Minimized the ``ftesting.zcml`` setup.


4.0.0a2 (2013-03-02)
Expand Down
6 changes: 3 additions & 3 deletions buildout.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[buildout]
develop = .
../zope.app.appsetup
../zope.component
find-links =
${buildout:directory}/zope.testbrowser-4.0.3dev.tar.gz
${buildout:directory}/ZODB-4.0.0dev.tar.gz
parts = test
versions=versions
Expand All @@ -14,8 +15,7 @@ eggs = zope.app.wsgi [test]
zope.i18n = 4.0.0a4
zope.tal=4.0.0a1
ZODB=4.0.0dev
zope.app.appsetup=4.0.0a1.dev
zope.app.appsetup=
zope.app.publication=4.0.0a1.dev
zope.testbrowser=4.0.3dev
zope.securitypolicy= >=4.0.0a1
zope.session = >=4.0.0a1
11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from setuptools import setup, find_packages

TESTS_REQUIRE = [
'zope.testbrowser[wsgi] >= 4.0.0',
'WebTest',
'zope.annotation',
'zope.authentication',
'zope.browserpage',
Expand All @@ -30,6 +30,7 @@
'zope.principalregistry >=4.0.0a1',
'zope.securitypolicy >=4.0.0a1',
'zope.testing',
'zope.testrunner',
]

setup(name='zope.app.wsgi',
Expand Down Expand Up @@ -60,7 +61,12 @@
namespace_packages=['zope', 'zope.app'],
extras_require = dict(
test=TESTS_REQUIRE,
testbrowser=['zope.testbrowser[wsgi] >= 4.0.0']),
testlayer=['WebTest'],
),
setup_requires=[
'eggtestinfo',
'zope.testrunner',
],
install_requires=[
'setuptools',
'ZConfig',
Expand All @@ -84,6 +90,7 @@
],
tests_require=TESTS_REQUIRE,
test_suite='zope.app.wsgi.tests.test_suite',
test_loader='zope.testrunner.eggsupport:SkipLayers',
entry_points={
'paste.app_factory': [
'main = zope.app.wsgi.paste:ZopeApplication'
Expand Down
6 changes: 3 additions & 3 deletions src/zope/app/wsgi/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ adapter that returns the principal id as value::
>>> from pprint import pprint
>>> pprint(environ)
{'PATH_INFO': '/',
'REMOTE_USER': 'zope.anybody',
'REMOTE_USER': '...fallback_unauthenticated_principal',
'wsgi.input': <...BytesIO object at ...>,
'wsgi.logging_info': 'zope.anybody'}
'wsgi.logging_info': '...fallback_unauthenticated_principal'}

.. edge case

Expand All @@ -125,7 +125,7 @@ adapter that returns the principal id as value::
{'PATH_INFO': '/',
'REMOTE_USER': 'someoneelse',
'wsgi.input': <...BytesIO object at ...>,
'wsgi.logging_info': 'zope.anybody'}
'wsgi.logging_info': '...fallback_unauthenticated_principal'}


Creating A WSGI Application
Expand Down
7 changes: 3 additions & 4 deletions src/zope/app/wsgi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#
##############################################################################
"""A WSGI Application wrapper for zope
$Id$
"""
from __future__ import print_function

Expand Down Expand Up @@ -53,8 +51,9 @@ def __init__(self, db=None, factory=HTTPPublicationRequestFactory,
self.requestFactory = None
self.handleErrors = handle_errors

if db is not None:
self.requestFactory = factory(db)
if db is None:
db = object()
self.requestFactory = factory(db)

def __call__(self, environ, start_response):
"""See zope.app.wsgi.interfaces.IWSGIApplication"""
Expand Down
13 changes: 6 additions & 7 deletions src/zope/app/wsgi/filereturns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ nothing bad happens. :)
... checker.NamesChecker(['browserDefault', '__call__']),
... )

>>> from zope.testbrowser.wsgi import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.open('http://localhost/@@test-file-view.html')
>>> browser.headers['content-type']
>>> from webtest.app import TestApp
>>> browser = TestApp(wsgi_app, extra_environ={'wsgi.handleErrors': False})
>>> res = browser.get('http://localhost/@@test-file-view.html')
>>> res.headers['content-type']
'text/plain'

>>> browser.headers['content-length']
>>> res.headers['content-length']
'13'

>>> print browser.contents
>>> print(res.body.decode())
Hello
World!
<BLANKLINE>
Expand Down
37 changes: 8 additions & 29 deletions src/zope/app/wsgi/ftesting.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,22 @@
<include package="zope.app.publication" file="meta.zcml" />
<include package="zope.securitypolicy" file="meta.zcml" />

<include package="zope.login" />
<include package="zope.security" />
<include package="zope.app.appsetup" />
<include package="zope.app.publication" />
<include package="zope.authentication" />
<include package="zope.securitypolicy" />
<include package="zope.container" />
<include package="zope.location" />
<include package="zope.principalregistry" />
<include package="zope.app.publication" />
<include package="zope.app.appsetup" file="ftesting.zcml"/>
<include package="zope.annotation" />
<include package="zope.publisher" />
<include package="zope.password" />
<include package="zope.security" />
<include package="zope.site" />
<include package="zope.traversing" />
<include package="zope.traversing.browser" />

<include package="zope.app.wsgi" />

<securityPolicy
component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />

<role id="zope.Manager" title="Site Manager" />

<grantAll role="zope.Manager" />

<!-- Principals -->
<unauthenticatedPrincipal
id="zope.anybody"
title="Unauthenticated User" />
<grant
permission="zope.View"
principal="zope.anybody" />

<!-- Principal that tests generally run as -->
<principal
id="zope.mgr"
title="Manager"
login="mgr"
password="mgrpw" />

<grant role="zope.Manager" principal="zope.mgr" />
component="zope.security.simplepolicies.PermissiveSecurityPolicy" />

</configure>

18 changes: 9 additions & 9 deletions src/zope/app/wsgi/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
#
##############################################################################
"""zope.app.wsgi common test related classes/functions/objects.
$Id$
"""
import tempfile

Expand All @@ -24,9 +22,9 @@


@interface.implementer(zope.publisher.interfaces.browser.IBrowserPublisher)
@component.adapter(
interface.Interface, zope.publisher.interfaces.browser.IBrowserRequest)
class FileView:
component.adapts(interface.Interface,
zope.publisher.interfaces.browser.IBrowserRequest)

def __init__(self, _, request):
self.request = request
Expand All @@ -36,9 +34,11 @@ def browserDefault(self, *_):

def __call__(self):
self.request.response.setHeader('content-type', 'text/plain')
f = tempfile.TemporaryFile()
f.write("Hello\nWorld!\n")
return f
fn = tempfile.mktemp()
with open(fn, 'wb') as file:
file.write(b"Hello\nWorld!\n")
file = open(fn, 'rb')
return file


class IndexView(FileView):
Expand Down Expand Up @@ -71,9 +71,9 @@ def drop_content_length_response(status, headers, exc_info=None):
app_iter = self.application(environ, drop_content_length_response)

# Very silly indeed:
result = ''.join(app_iter)
result = b''.join(app_iter)
return [result.replace(
'<body>', '<body><h1>Hello from the silly middleware</h1>')]
b'<body>', b'<body><h1>Hello from the silly middleware</h1>')]


class SillyMiddleWareBrowserLayer(BrowserLayer):
Expand Down
87 changes: 66 additions & 21 deletions src/zope/app/wsgi/testlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,41 @@
##############################################################################
from io import BytesIO

import base64
import re
import transaction
from zope.app.appsetup.testlayer import ZODBLayer
from zope.app.wsgi import WSGIPublisherApplication
from zope.testbrowser.wsgi import Layer as WSGILayer, AuthorizationMiddleware
from webtest import TestRequest

# BBB
#from zope.testbrowser.wsgi import Browser

from zope.app.wsgi._compat import httpclient, xmlrpcclient

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 = ''
plain = '%s:%s' % (u, p)
auth = base64.encodestring(plain.encode('utf-8'))
return 'Basic %s' % str(auth.rstrip().decode('latin1'))
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 TransactionMiddleware(object):
"""This middleware makes the WSGI application compatible with the
Expand All @@ -46,36 +70,61 @@ def __call__(self, environ, start_response):
self.root_factory()._p_jar.sync()


class BrowserLayer(WSGILayer, ZODBLayer):
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 = list(filter(is_wanted_header, headers))
start_response(status, headers)

for entry in self.wsgi_stack(environ, application_start_response):
yield entry


class BrowserLayer(ZODBLayer):
"""This create a test layer with a test database and register a wsgi
application to use that test database.
You can use a WSGI version of zope.testbrowser Browser instance to access
the application.
"""
allowTearDown = False

def __init__(self, package, zcml_file='ftesting.zcml',
name=None, features=None, allowTearDown=False):
super(BrowserLayer, self).__init__(package, zcml_file, name, features)
self.allowTearDown = allowTearDown

def setup_middleware(self, app):
# Override this method in subclasses of this layer in order to set up
# WSGI middleware.
return app

def make_wsgi_app(self):
# Since the request factory class is only a parameter default of
# WSGIPublisherApplication and not easily accessible otherwise, we fake
# it into creating a requestFactory instance, so we can read the class
# off of that in testSetUp()
self._application = WSGIPublisherApplication(self.db)
return AuthorizationMiddleware(
TransactionMiddleware(
self.getRootFolder,
self.setup_middleware(self._application)))

def testSetUp(self):
super(BrowserLayer, self).testSetUp()
# Tell the publisher to use ZODBLayer's current database
factory = type(self._application.requestFactory)
self._application.requestFactory = factory(self.db)

def tearDown(self):
if self.allowTearDown:
super(BrowserLayer, self).tearDown()
raise NotImplementedError

class NotInBrowserLayer(Exception):
"""The current test is not running in a layer inheriting from
Expand Down Expand Up @@ -121,14 +170,10 @@ def __str__(self):
out = self.getOutput()
return out.decode('latin1')

def http(string, handle_errors=True):
app = WSGILayer.get_app()
if app is None:
raise NotInBrowserLayer(NotInBrowserLayer.__doc__)

def http(wsgi_app, string, handle_errors=True):
request = TestRequest.from_file(BytesIO(string))
request.environ['wsgi.handleErrors'] = handle_errors
response = request.get_response(app)
response = request.get_response(wsgi_app)
return FakeResponse(response)


Expand Down
Loading

0 comments on commit 262d9b9

Please sign in to comment.