[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