diff --git a/CHANGES.rst b/CHANGES.rst index 055c1db..cc90a13 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,8 @@ Changelog 4.0a2 (unreleased) ------------------ +- Remove mechanize based testbrowser support. + - Use `@implementer` class decorator. 4.0a1 (2016-09-09) diff --git a/src/ZServer/Testing/testbrowser.py b/src/ZServer/Testing/testbrowser.py deleted file mode 100644 index 243837f..0000000 --- a/src/ZServer/Testing/testbrowser.py +++ /dev/null @@ -1,167 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 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. -# -############################################################################## -"""Support for using zope.testbrowser from Zope2. - -Mostly just copy and paste from zope.testbrowser.testing. -""" - -import cStringIO -import httplib -import urllib2 - -import mechanize -from zExceptions import status_reasons -from zope.testbrowser import browser - -from ZServer.Testing.doctest_functional import http - - -class PublisherConnection(object): - - def __init__(self, host, timeout=None): - self.caller = http - self.host = host - - def set_debuglevel(self, level): - pass - - def _quote(self, url): - # the publisher expects to be able to split on whitespace, so we have - # to make sure there is none in the URL - 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 - 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) - - # Construct the headers. - header_chunks = [] - if headers is not None: - for header in headers.items(): - header_chunks.append('%s: %s' % header) - headers = '\n'.join(header_chunks) + '\n' - else: - headers = '' - - # Construct the full HTTP request string, since that is what the - # ``HTTPCaller`` wants. - request_string = (method + ' ' + url + ' HTTP/1.1\n' + - headers + '\n' + body) - self.response = self.caller(request_string, handle_errors) - - def getresponse(self): - """Return a ``urllib2`` compatible response. - - The goal of ths method is to convert the Zope Publisher's reseponse to - a ``urllib2`` compatible response, which is also understood by - mechanize. - """ - real_response = self.response._response - status = real_response.getStatus() - reason = status_reasons[real_response.status] - headers = [] - # Convert header keys to camel case. This is basically a copy - # paste from ZPublisher.HTTPResponse - for key, val in real_response.headers.items(): - if key.lower() == key: - # only change non-literal header names - key = "%s%s" % (key[:1].upper(), key[1:]) - start = 0 - l = key.find('-', start) - while l >= start: - key = "%s-%s%s" % ( - key[:l], key[l + 1:l + 2].upper(), key[l + 2:]) - start = l + 1 - l = key.find('-', start) - headers.append((key, val)) - # get the cookies, breaking them into tuples for sorting - cookies = real_response._cookie_list() - headers.extend(cookies) - headers.sort() - headers.insert(0, ('Status', "%s %s" % (status, reason))) - headers = '\r\n'.join('%s: %s' % h for h in headers) - content = real_response.body - return PublisherResponse(content, headers, status, reason) - - -class PublisherResponse(object): - """``mechanize`` compatible response object.""" - - def __init__(self, content, headers, status, reason): - self.content = content - self.status = status - self.reason = reason - self.msg = httplib.HTTPMessage(cStringIO.StringIO(headers), 0) - self.content_as_file = cStringIO.StringIO(self.content) - - def read(self, amt=None): - return self.content_as_file.read(amt) - - def close(self): - """To overcome changes in mechanize and socket in python2.5""" - pass - - -class PublisherHTTPHandler(urllib2.HTTPHandler): - """Special HTTP handler to use the Zope Publisher.""" - - http_request = urllib2.AbstractHTTPHandler.do_request_ - - def http_open(self, req): - """Open an HTTP connection having a ``urllib2`` request.""" - # Here we connect to the publisher. - return self.do_open(PublisherConnection, req) - - -class PublisherMechanizeBrowser(mechanize.Browser): - """Special ``mechanize`` browser using the Zope Publisher HTTP handler.""" - - default_schemes = ['http'] - default_others = ['_http_error', '_http_request_upgrade', - '_http_default_error'] - default_features = ['_redirect', '_cookies', '_referer', '_refresh', - '_equiv', '_basicauth', '_digestauth'] - - def __init__(self, *args, **kws): - self.handler_classes = mechanize.Browser.handler_classes.copy() - self.handler_classes["http"] = PublisherHTTPHandler - self.default_others = [cls for cls in self.default_others - if cls in mechanize.Browser.handler_classes] - mechanize.Browser.__init__(self, *args, **kws) - - -class Browser(browser.Browser): - """A Zope ``testbrowser` Browser that uses the Zope Publisher.""" - - def __init__(self, url=None): - mech_browser = PublisherMechanizeBrowser() - # override the http handler class - mech_browser.handler_classes["http"] = PublisherHTTPHandler - super(Browser, self).__init__(url=url, mech_browser=mech_browser) diff --git a/src/ZServer/ZPublisher/tests/exception_handling.txt b/src/ZServer/ZPublisher/tests/exception_handling.txt deleted file mode 100644 index fe2c8e9..0000000 --- a/src/ZServer/ZPublisher/tests/exception_handling.txt +++ /dev/null @@ -1,202 +0,0 @@ -Exception handling ------------------- - -These tests capture the current behavior. Maybe some of that behavior should -be changed. The behavior caused by handleErrors=False shows only up in tests. - -Set exception hook: - - >>> from ZServer.ZPublisher.exceptionhook import EXCEPTION_HOOK - >>> import Zope2 - >>> setattr(Zope2, 'zpublisher_exception_hook', EXCEPTION_HOOK) - -Create the browser object we'll be using. - - >>> from ZServer.Testing.testbrowser import Browser - >>> browser = Browser() - >>> # XXX: browser has no API for disabling redirects - >>> browser.mech_browser.set_handle_redirect(False) - -Create the objects that are raising exceptions. - - >>> dummy = app.test_folder_1_._setObject('foo', ExceptionRaiser1()) - >>> dummy = app.test_folder_1_._setObject('bar', ExceptionRaiser2()) - >>> dummy = app.test_folder_1_._setObject('baz', ExceptionRaiser3()) - -Handle AttributeError. - - >>> app.test_folder_1_.foo.exception = AttributeError('ERROR VALUE') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 500: Internal Server Error - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - AttributeError: ERROR VALUE - >>> browser.contents - -Handle ImportError. - - >>> app.test_folder_1_.foo.exception = ImportError('ERROR VALUE') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 500: Internal Server Error - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - ImportError: ERROR VALUE - >>> browser.contents - -Handle zope.publisher.interfaces.NotFound. - - >>> from zope.publisher.interfaces import NotFound - >>> app.test_folder_1_.foo.exception = NotFound('OBJECT','NAME') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 404: Not Found - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - NotFound: Object: 'OBJECT', name: 'NAME' - >>> browser.contents - -Don't handle SystemExit, even if handleErrors is True. - - >>> app.test_folder_1_.foo.exception = SystemExit('ERROR VALUE') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - SystemExit: ERROR VALUE - >>> browser.contents - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - SystemExit: ERROR VALUE - >>> browser.contents - -Handle zExceptions.Redirect. - - >>> from zExceptions import Redirect - >>> app.test_folder_1_.foo.exception = Redirect('LOCATION') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 302: Found - >>> browser.contents - '' - >>> browser.headers['Location'] - 'LOCATION' - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - Redirect: LOCATION - >>> browser.contents - -Handle zExceptions.Unauthorized raised by the object. We take the -'WWW-Authenticate' header as a sign that HTTPResponse._unauthorized was called. - - >>> from zExceptions import Unauthorized - >>> app.test_folder_1_.foo.exception = Unauthorized('ERROR VALUE') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 401: Unauthorized - >>> browser.headers['WWW-Authenticate'] - 'basic realm="Zope2"' - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - Unauthorized: ERROR VALUE - >>> browser.contents - -And the same with unicode error value. - - >>> app.test_folder_1_.foo.exception = Unauthorized(u'ERROR VALUE \u03A9') - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/foo') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 401: Unauthorized - >>> browser.headers['WWW-Authenticate'] - 'basic realm="Zope2"' - - >>> browser.handleErrors = False - >>> try: - ... browser.open('http://localhost/test_folder_1_/foo') - ... except Unauthorized, e: - ... e._message == u'ERROR VALUE \u03A9' - ... else: - ... print "Unauthorized not raised" - True - >>> browser.contents - -Handle zExceptions.Unauthorized raised by BaseRequest.traverse. We take the -'WWW-Authenticate' header as a sign that HTTPResponse._unauthorized was called. - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/bar') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 401: Unauthorized - >>> 'You are not authorized to access this resource.' in browser.contents - True - >>> browser.headers['WWW-Authenticate'] - 'basic realm="Zope2"' - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/bar') - Traceback (most recent call last): - ... - Unauthorized: You are not authorized to access this resource... - >>> browser.contents - -Handle zExceptions.Forbidden raised by BaseRequest.traverse. 'traverse' -converts it into zExceptions.NotFound if we are not in debug mode. - - >>> browser.handleErrors = True - >>> browser.open('http://localhost/test_folder_1_/baz') - Traceback (most recent call last): - ... - HTTPError: HTTP Error 404: Not Found - >>> '

Resource not found

' in browser.contents - True - >>> '

Resource: index_html

' in browser.contents - True - - >>> browser.handleErrors = False - >>> browser.open('http://localhost/test_folder_1_/baz') - Traceback (most recent call last): - ... - NotFound: ... - ...

Site Error

- ...

Resource not found

... - ...

Resource: index_html

... - >>> browser.contents diff --git a/src/ZServer/ZPublisher/tests/test_exception_handling.py b/src/ZServer/ZPublisher/tests/test_exception_handling.py deleted file mode 100644 index 9ede712..0000000 --- a/src/ZServer/ZPublisher/tests/test_exception_handling.py +++ /dev/null @@ -1,51 +0,0 @@ -############################################################################## -# -# Copyright (c) 2010 Zope Foundation and Contributors. -# -# 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. -# -############################################################################## -""" Functional tests for exception handling. -""" - -import unittest - -from OFS.SimpleItem import SimpleItem - -from ZServer.Testing.doctest_functional import FunctionalDocFileSuite - - -class ExceptionRaiser1(SimpleItem): - - def index_html(self): - """DOCSTRING - """ - raise self.exception - - -class ExceptionRaiser2(ExceptionRaiser1): - - __roles__ = () - - -class ExceptionRaiser3(SimpleItem): - - def index_html(self): - return 'NO DOCSTRING' - - -def test_suite(): - return unittest.TestSuite([ - FunctionalDocFileSuite( - 'exception_handling.txt', - globs={ - 'ExceptionRaiser1': ExceptionRaiser1, - 'ExceptionRaiser2': ExceptionRaiser2, - 'ExceptionRaiser3': ExceptionRaiser3, - }), - ])