[Checkins] SVN: zope.app.testing/trunk/ merge webtest branch:
Brian Sutherland
jinty at web.de
Mon Mar 14 05:10:26 EDT 2011
Log message for revision 120905:
merge webtest branch:
- Move zope.app.testing testbrowser functionality into zope.app.testing. This
requires zope.testbrowser version 4.0.0 or above.
Changed:
U zope.app.testing/trunk/CHANGES.txt
U zope.app.testing/trunk/setup.py
A zope.app.testing/trunk/src/zope/app/testing/testbrowser.py
A zope.app.testing/trunk/src/zope/app/testing/testbrowser.txt
U zope.app.testing/trunk/src/zope/app/testing/tests.py
-=-
Modified: zope.app.testing/trunk/CHANGES.txt
===================================================================
--- zope.app.testing/trunk/CHANGES.txt 2011-03-14 08:59:18 UTC (rev 120904)
+++ zope.app.testing/trunk/CHANGES.txt 2011-03-14 09:10:25 UTC (rev 120905)
@@ -2,12 +2,12 @@
CHANGES
=======
-3.8.2 (unreleased)
+3.9.0 (2011-03-14)
------------------
-- Nothing changed yet.
+- Move zope.app.testing testbrowser functionality into zope.app.testing. This
+ requires zope.testbrowser version 4.0.0 or above.
-
3.8.1 (2011-01-07)
------------------
Modified: zope.app.testing/trunk/setup.py
===================================================================
--- zope.app.testing/trunk/setup.py 2011-03-14 08:59:18 UTC (rev 120904)
+++ zope.app.testing/trunk/setup.py 2011-03-14 09:10:25 UTC (rev 120905)
@@ -27,7 +27,7 @@
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='zope.app.testing',
- version = '3.8.2dev',
+ version = '3.9.0',
author='Zope Foundation and Contributors',
author_email='zope-dev at zope.org',
description='Zope Application Testing Support',
@@ -81,6 +81,7 @@
'zope.security',
'zope.site',
'zope.testing',
+ 'zope.testbrowser >= 4.0.0dev',
'zope.traversing',
],
include_package_data = True,
Copied: zope.app.testing/trunk/src/zope/app/testing/testbrowser.py (from rev 120904, zope.app.testing/branches/jinty-testbrowser/src/zope/app/testing/testbrowser.py)
===================================================================
--- zope.app.testing/trunk/src/zope/app/testing/testbrowser.py (rev 0)
+++ zope.app.testing/trunk/src/zope/app/testing/testbrowser.py 2011-03-14 09:10:25 UTC (rev 120905)
@@ -0,0 +1,99 @@
+import sys
+
+from zope.app.testing.functional import HTTPCaller
+
+import zope.testbrowser.connection
+
+class PublisherConnection(object):
+ """A ``mechanize`` compatible connection object."""
+
+ def __init__(self, host, timeout=None):
+ self.caller = HTTPCaller()
+ 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
+ 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)
+
+ # 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 ``mechanize`` compatible response.
+
+ The goal of ths method is to convert the Zope Publisher's reseponse to
+ a ``mechanize`` compatible response, which is also understood by
+ mechanize.
+ """
+ real_response = self.response._response
+ status = real_response.getStatus()
+ reason = real_response._reason # XXX add a getReason method
+
+ headers = real_response.getHeaders()
+ headers.sort()
+ headers.insert(0, ('Status', real_response.getStatusString()))
+ headers = '\r\n'.join('%s: %s' % h for h in headers)
+ content = real_response.consumeBody()
+ return zope.testbrowser.connection.Response(content, headers, status, reason)
+
+
+class PublisherHTTPHandler(zope.testbrowser.connection.HTTPHandler):
+ """Special HTTP handler to use the Zope Publisher."""
+
+ def _connect(self, *args, **kw):
+ return PublisherConnection(*args, **kw)
+
+
+class PublisherMechanizeBrowser(zope.testbrowser.connection.MechanizeBrowser):
+ """Special ``mechanize`` browser using the Zope Publisher HTTP handler."""
+
+ def _http_handler(self, *args, **kw):
+ return PublisherHTTPHandler(*args, **kw)
+
+
+class Browser(zope.testbrowser.browser.Browser):
+ """A Zope `testbrowser` Browser that uses the Zope Publisher."""
+
+ def __init__(self, url=None):
+ mech_browser = PublisherMechanizeBrowser()
+ super(Browser, self).__init__(url=url, mech_browser=mech_browser)
+
Copied: zope.app.testing/trunk/src/zope/app/testing/testbrowser.txt (from rev 120904, zope.app.testing/branches/jinty-testbrowser/src/zope/app/testing/testbrowser.txt)
===================================================================
--- zope.app.testing/trunk/src/zope/app/testing/testbrowser.txt (rev 0)
+++ zope.app.testing/trunk/src/zope/app/testing/testbrowser.txt 2011-03-14 09:10:25 UTC (rev 120905)
@@ -0,0 +1,85 @@
+Zope Publisher Browser Tests
+============================
+
+These tests specifically test the implementation of Browser which uses the Zope
+Publisher via zope.app.testing as the application to test.
+
+ >>> from zope.app.testing.testbrowser import Browser
+ >>> browser = Browser()
+
+Error Handling
+--------------
+
+handleErrors works as advertised:
+
+ >>> browser.handleErrors
+ True
+ >>> browser.open('http://localhost/invalid')
+ Traceback (most recent call last):
+ ...
+ HTTPError: HTTP Error 404: Not Found
+
+So when we tell the publisher not to handle the errors,
+
+ >>> browser.handleErrors = False
+ >>> browser.open('http://localhost/invalid')
+ Traceback (most recent call last):
+ ...
+ NotFound: Object: <zope.site.folder.Folder object at ...>,
+ name: u'invalid'
+
+Spaces in URLs
+--------------
+
+Spaces in URLs don't cause blowups:
+
+ >>> browser.open('http://localhost/space here')
+ Traceback (most recent call last):
+ ...
+ NotFound: Object: <zope.site.folder.Folder object at ...>,
+ name: u'space here'
+
+Headers
+-------
+
+Sending arbitrary headers works:
+
+ >>> browser.addHeader('Accept-Language', 'en-US')
+ >>> browser.open('http://localhost/echo_one?var=HTTP_ACCEPT_LANGUAGE')
+ >>> print browser.contents
+ 'en-US'
+
+POST
+----
+
+HTTP posts are correctly sent:
+
+ >>> browser.post('http://localhost/echo', 'x=1&y=2')
+ >>> print browser.contents
+ CONTENT_LENGTH: 7
+ CONTENT_TYPE: application/x-www-form-urlencoded
+ HTTP_ACCEPT_LANGUAGE: en-US
+ HTTP_CONNECTION: close
+ HTTP_COOKIE:
+ HTTP_HOST: localhost
+ HTTP_USER_AGENT: Python-urllib/2.4
+ PATH_INFO: /echo
+ QUERY_STRING:
+ REMOTE_ADDR: 127.0.0.1
+ REQUEST_METHOD: POST
+ SERVER_PROTOCOL: HTTP/1.1
+ x: 1
+ y: 2
+ Body: ''
+
+Returned headers
+----------------
+
+Server headers are correctly returned:
+
+ >>> print browser.headers
+ Status: 200 OK
+ Content-Length: 123
+ Content-Type: text/plain;charset=utf-8
+ X-Content-Type-Warning: guessed from content
+ X-Powered-By: Zope (www.zope.org), Python (www.python.org)
Modified: zope.app.testing/trunk/src/zope/app/testing/tests.py
===================================================================
--- zope.app.testing/trunk/src/zope/app/testing/tests.py 2011-03-14 08:59:18 UTC (rev 120904)
+++ zope.app.testing/trunk/src/zope/app/testing/tests.py 2011-03-14 09:10:25 UTC (rev 120905)
@@ -298,6 +298,28 @@
self.request.response.setCookie('bid', 'bval')
+class Echo(object):
+ """Simply echo the interesting parts of the request"""
+
+ def __call__(self):
+ items = []
+ for k in sorted(self.request.keys()):
+ if k in self.request.form:
+ continue
+ v = self.request.get(k, None)
+ items.append('%s: %s' % (k, v))
+ items.extend('%s: %s' % x for x in sorted(self.request.form.items()))
+ items.append('Body: %r' % self.request.bodyStream.read())
+ return '\n'.join(items)
+
+
+class EchoOne(object):
+ """Echo one variable from the request"""
+
+ def __call__(self):
+ return repr(self.request.get(self.request.form['var']))
+
+
class CookieFunctionalTest(BrowserTestCase):
"""Functional tests should handle cookies like a web browser
@@ -644,7 +666,30 @@
"""
+def testbrowserSetUp(test):
+ import zope.configuration.xmlconfig
+ zope.configuration.xmlconfig.string(r'''
+ <configure xmlns="http://namespaces.zope.org/browser">
+
+ <include package="zope.browserpage" file="meta.zcml" />
+
+ <page
+ name="echo"
+ for="*"
+ permission="zope.Public"
+ class="zope.app.testing.tests.Echo" />
+
+ <page
+ name="echo_one"
+ for="*"
+ permission="zope.Public"
+ class="zope.app.testing.tests.EchoOne" />
+
+ </configure>
+ ''')
+
+
def test_suite():
checker = RENormalizing([
(re.compile(r'^HTTP/1.1 (\d{3}) .*?\n'), 'HTTP/1.1 \\1\n')])
@@ -657,6 +702,17 @@
BrowserFunctionalTest.layer = AppTestingLayer
HTTPCallerFunctionalTest.layer = AppTestingLayer
+ testbrowser_checker = RENormalizing([
+ (re.compile(r'Status: 200.*'), 'Status: 200 OK'),
+ (re.compile(r'HTTP_USER_AGENT:\s+\S+'),
+ 'HTTP_USER_AGENT: Python-urllib/2.4'),
+ (re.compile(r'Content-[Ll]ength:.*'), 'Content-Length: 123'),
+ ])
+ testbrowser_test = FunctionalDocFileSuite('testbrowser.txt',
+ setUp=testbrowserSetUp,
+ checker=testbrowser_checker)
+ testbrowser_test.layer = AppTestingLayer
+
doc_test = FunctionalDocFileSuite('doctest.txt', 'cookieTestOne.txt',
'cookieTestTwo.txt', checker=checker)
doc_test.layer = AppTestingLayer
@@ -676,6 +732,7 @@
unittest.makeSuite(RetryProblemFunctional),
unittest.makeSuite(RetryProblemBrowser),
doc_test,
+ testbrowser_test,
))
if __name__ == '__main__':
More information about the checkins
mailing list