[Zope3-checkins] SVN: Zope3/branches/ZopeX3-3.0/src/zope/app/ Merged from trunk:

Jim Fulton jim at zope.com
Tue Aug 24 16:30:05 EDT 2004


Log message for revision 27253:
  Merged from trunk:
  
    r27252 | jim | 2004-08-24 16:21:30 -0400 (Tue, 24 Aug 2004) | 10 lines
  
    Changed the addMenuItem directive so that it no-longer generates
    unpredictable factory names.  It used to generate factory ids of the
    form "zope.app.browser.add." + class_name + ".f" + number, where
    number was alocated sequentially from one factory to another.
    This was a disaster for testability.
  
    Now factory ids are of the form:
  
    "zope.app.browser.add." + class_module_name + "." + class_name
    ------------------------------------------------------------------------
    r27222 | jim | 2004-08-22 22:58:28 -0400 (Sun, 22 Aug 2004) | 11 lines
  
    Added the ability to:
  
    - Get access to teh object system from functional doctests
  
    - Make changes in regular Python code and have the changes
      reflected sanely.
  
    In particular, making an http request implicitly commits any
      Python changes and syncs the connection used for objects accessed
      via Python.
  
    ------------------------------------------------------------------------
    r27221 | jim | 2004-08-22 15:12:27 -0400 (Sun, 22 Aug 2004) | 10 lines
  
    Added (first cut at) functional HTTP doctests.  These are doctests
    expressed as http request inputs and expected http response outputs.
  
    To do:
  
    - Support for binary inputs (e.g.file uploads)
  
    - Support for accessing the object system, so that assertions
        can be made about the state of the system.
  
    ------------------------------------------------------------------------
    r27220 | jim | 2004-08-22 15:12:14 -0400 (Sun, 22 Aug 2004) | 3 lines
  
    Created a script for converting http sesssions recorded by tcpwatch
    into functional doctests.
  
  


Changed:
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/doctest.txt
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/metaconfigure.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/tests/test_addMenuItem.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/site/browser/ftests/test_utilitytools.py
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/tests/dochttp.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/tests/recorded/
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/tests/test_dochttp.py


-=-
Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/doctest.txt (from rev 27222, Zope3/trunk/src/zope/app/ftests/doctest.txt)
===================================================================
--- Zope3/trunk/src/zope/app/ftests/doctest.txt	2004-08-23 02:58:28 UTC (rev 27222)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/doctest.txt	2004-08-24 20:30:04 UTC (rev 27253)
@@ -0,0 +1,142 @@
+DocTest Functional Tests
+========================
+
+This file documents and tests doctest-based functional tests and basic
+Zope web-application functionality.
+
+Request/Response Functional Tests
+---------------------------------
+
+You can create Functional tests as doctests.  Typically, this is done
+by using a script such as zope.app.tests.dochttp.py to convert
+tcpwatch recorded output to a doctest, which is then edited to provide
+explanation and to remove uninyeresting details.  That is how this
+file was created.
+
+Here we'll test some of the most basic types of access.
+
+First, we'll test accessing a protected page without credentials:
+
+  >>> print http(r"""
+  ... GET /@@contents.html HTTP/1.1
+  ... """)
+  HTTP/1.1 401 Unauthorized
+  Content-Length: ...
+  Content-Type: text/html;charset=utf-8
+  Www-Authenticate: basic realm=zope
+  <BLANKLINE>
+  <!DOCTYPE html PUBLIC ...
+
+Here we see that we got:
+
+  - A 404 response,
+  - A Www-Authenticate header, and
+  - An html body with an error message
+
+Note that we used ellipeses to indicate ininteresting details.
+
+Next, we'll access the same page with credentials: 
+
+  >>> print http(r"""
+  ... GET /@@contents.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... """)
+  HTTP/1.1 200 Ok
+  Content-Length: ...
+  Content-Type: text/html;charset=utf-8
+  <BLANKLINE>
+  <!DOCTYPE html PUBLIC ...
+
+Important note: you must use the user named "mgr" with a password
+"mgrpw". This means that when you record requests with tcpwatch, you
+need to use that user login name and password.
+
+And we get a normal output.
+
+Next we'll try accessing site management. Since we used "/manage", 
+we got redirected:
+
+  >>> print http(r"""
+  ... GET /++etc++site/@@manage HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Referer: http://localhost:8081/
+  ... """)
+  HTTP/1.1 303 See Other
+  Content-Length: 0
+  Content-Type: text/plain;charset=utf-8
+  Location: @@tasks.html
+  <BLANKLINE>
+
+Note that, in this case, we got a 303 response.  A 303 response is the
+prefered response for this sort of redirect with HTTP 1.1.  If we used
+HTTP 1.0, we'd get a 302 response:
+
+  >>> print http(r"""
+  ... GET /++etc++site/@@manage HTTP/1.0
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Referer: http://localhost:8081/
+  ... """)
+  HTTP/1.0 302 Moved Temporarily
+  Content-Length: 0
+  Content-Type: text/plain;charset=utf-8
+  Location: @@tasks.html
+  <BLANKLINE>
+
+Lets visit the page we were rediected to:
+
+  >>> print http(r"""
+  ... GET /++etc++site/@@tasks.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Referer: http://localhost:8081/
+  ... """)
+  HTTP/1.1 200 Ok
+  Content-Length: ...
+  Content-Type: text/html;charset=utf-8
+  <BLANKLINE>
+  <!DOCTYPE html PUBLIC ...
+
+Finally, lets access the default page for the site:
+
+  >>> print http(r"""
+  ... GET / HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... """)
+  HTTP/1.1 200 Ok
+  Content-Length: ...
+  Content-Type: text/html;charset=utf-8
+  <BLANKLINE>
+  <!DOCTYPE html PUBLIC ...
+
+Access to the object system
+---------------------------
+
+You can use the getRootFolder function:
+
+  >>> root = getRootFolder()
+  >>> root
+  <zope.app.folder.folder.Folder object at ...>
+
+You can intermic http requests with regular Python calls.  Note,
+however, that making an http call implied a transaction commit.
+If you want to throw away changes made in Python code, abort the
+transaction before the http request.
+
+  >>> print http(r"""
+  ... POST /@@contents.html HTTP/1.1
+  ... Authorization: Basic bWdyOm1ncnB3
+  ... Content-Length: 73
+  ... Content-Type: application/x-www-form-urlencoded
+  ... 
+  ... type_name=zope.app.browser.add.zope.app.folder.folder.Folder&new_value=f1""")
+  HTTP/1.1 303 See Other
+  Content-Length: ...
+  Content-Type: text/html;charset=utf-8
+  Location: http://localhost/@@contents.html
+  <BLANKLINE>
+  <!DOCTYPE html ...
+
+Now we can see that the new folder was added:
+
+  >>> list(root.keys())
+  [u'f1']
+

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py	2004-08-24 20:21:30 UTC (rev 27252)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/ftests/test_functional.py	2004-08-24 20:30:04 UTC (rev 27253)
@@ -18,6 +18,7 @@
 
 import unittest
 from zope.app.tests.functional import SampleFunctionalTest, BrowserTestCase
+from zope.app.tests.functional import FunctionalDocFileSuite
 
 class CookieFunctionalTest(BrowserTestCase):
 
@@ -118,6 +119,7 @@
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(SampleFunctionalTest))
     suite.addTest(unittest.makeSuite(CookieFunctionalTest))
+    suite.addTest(FunctionalDocFileSuite('doctest.txt'))
     return suite
 
 

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/metaconfigure.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/metaconfigure.py	2004-08-24 20:21:30 UTC (rev 27252)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/metaconfigure.py	2004-08-24 20:30:04 UTC (rev 27253)
@@ -75,14 +75,16 @@
             )
 
 
-_next_id = 0
 def addMenuItem(_context, title, class_=None, factory=None, description='',
                 permission=None, filter=None, view=None):
     """Create an add menu item for a given class or factory
 
-    As a convenience, a class can be provided, in which case, a factory is
-    automatically defined based on the class.
+    As a convenience, a class can be provided, in which case, a
+    factory is automatically defined based on the class.  In this
+    case, the factory id is based on the class name.
+
     """
+    
     if class_ is None:
         if factory is None:
             raise ValueError("Must specify either class or factory")
@@ -92,10 +94,8 @@
         if permission is None:
             raise ValueError(
                 "A permission must be specified when a class is used")
-        global _next_id
-        _next_id += 1
-        factory = "zope.app.browser.add.%s.f%s" % (
-            class_.__name__, _next_id) 
+        factory = "zope.app.browser.add.%s.%s" % (
+            class_.__module__, class_.__name__) 
         ContentDirective(_context, class_).factory(
             _context,
             id = factory)
@@ -110,11 +110,3 @@
     menuItemDirective(_context, 'zope.app.container.add', IAdding,
                       action, title, description, filter,
                       permission, extra)
-
-
-def test_reset():
-    global _next_id
-    _next_id = 0
-    
-from zope.testing.cleanup import addCleanUp
-addCleanUp(test_reset)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/tests/test_addMenuItem.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2004-08-24 20:21:30 UTC (rev 27252)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2004-08-24 20:30:04 UTC (rev 27253)
@@ -13,20 +13,20 @@
 ##############################################################################
 """Test the addMenuItem directive
 
->>> test_reset()
 >>> context = Context()
 >>> addMenuItem(context, class_=X, title="Add an X",
 ...             permission="zope.ManageContent")
->>> context
+>>> context # doctest: +CONTEXT_DIFF
 ((('utility',
    <InterfaceClass zope.component.interfaces.IFactory>,
-   'zope.app.browser.add.X.f1'),
+   'zope.app.browser.add.zope.app.publisher.browser.tests.test_addMenuItem.X'),
   <function handler>,
   ('Utilities',
    'provideUtility',
    <InterfaceClass zope.component.interfaces.IFactory>,
    <zope.component.factory.Factory object>,
-   'zope.app.browser.add.X.f1')),
+   'zope.app.browser.add.""" \
+         """zope.app.publisher.browser.tests.test_addMenuItem.X')),
  (None,
   <function provideInterface>,
   ('zope.component.interfaces.IFactory',
@@ -35,15 +35,18 @@
    'zope.app.container.add',
    <InterfaceClass zope.app.container.interfaces.IAdding>,
    'Add an X'),
-  <bound method GlobalBrowserMenuService.menuItem of <zope.app.publisher.browser.globalbrowsermenuservice.GlobalBrowserMenuService object>>,
+  <bound method GlobalBrowserMenuService.menuItem of """ \
+     """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
+     """GlobalBrowserMenuService object>>,
   ('zope.app.container.add',
    <InterfaceClass zope.app.container.interfaces.IAdding>,
-   'zope.app.browser.add.X.f1',
+   'zope.app.browser.add.zope.app.publisher.browser.tests.test_addMenuItem.X',
    'Add an X',
    '',
    None,
    'zope.ManageContent',
-   {'factory': 'zope.app.browser.add.X.f1'})))
+   {'factory': 'zope.app.browser.add.""" \
+         """zope.app.publisher.browser.tests.test_addMenuItem.X'})))
 
 $Id$
 """
@@ -53,7 +56,7 @@
 import re
 import pprint
 import cStringIO
-from zope.app.publisher.browser.metaconfigure import addMenuItem, test_reset
+from zope.app.publisher.browser.metaconfigure import addMenuItem
 
 atre = re.compile(' at [0-9a-fA-Fx]+')
 
@@ -124,21 +127,22 @@
 
 def test_w_factory_class_view():
     """
-    >>> test_reset()
     >>> context = Context()
     >>> addMenuItem(context, class_=X, title="Add an X",
     ...             permission="zope.ManageContent", description="blah blah",
     ...             filter="context/foo", view="AddX")
-    >>> context
+    >>> context # doctest: +CONTEXT_DIFF
     ((('utility',
        <InterfaceClass zope.component.interfaces.IFactory>,
-       'zope.app.browser.add.X.f1'),
+       'zope.app.browser.add.""" \
+         """zope.app.publisher.browser.tests.test_addMenuItem.X'),
       <function handler>,
       ('Utilities',
        'provideUtility',
        <InterfaceClass zope.component.interfaces.IFactory>,
        <zope.component.factory.Factory object>,
-       'zope.app.browser.add.X.f1')),
+       'zope.app.browser.add.""" \
+         """zope.app.publisher.browser.tests.test_addMenuItem.X')),
      (None,
       <function provideInterface>,
       ('zope.component.interfaces.IFactory',
@@ -147,7 +151,9 @@
        'zope.app.container.add',
        <InterfaceClass zope.app.container.interfaces.IAdding>,
        'Add an X'),
-      <bound method GlobalBrowserMenuService.menuItem of <zope.app.publisher.browser.globalbrowsermenuservice.GlobalBrowserMenuService object>>,
+      <bound method GlobalBrowserMenuService.menuItem of """ \
+         """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
+         """GlobalBrowserMenuService object>>,
       ('zope.app.container.add',
        <InterfaceClass zope.app.container.interfaces.IAdding>,
        'AddX',
@@ -155,7 +161,8 @@
        'blah blah',
        'context/foo',
        'zope.ManageContent',
-       {'factory': 'zope.app.browser.add.X.f1'})))
+       {'factory': 'zope.app.browser.add.""" \
+         """zope.app.publisher.browser.tests.test_addMenuItem.X'})))
 """
 
 

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/site/browser/ftests/test_utilitytools.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/site/browser/ftests/test_utilitytools.py	2004-08-24 20:21:30 UTC (rev 27252)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/site/browser/ftests/test_utilitytools.py	2004-08-24 20:30:04 UTC (rev 27253)
@@ -52,7 +52,9 @@
         # attempt to add something
         response = self.publish(
             path+'/action.html', basic='mgr:mgrpw',
-            form={'type_name': 'zope.app.browser.add.TranslationDomain.f1',
+            form={'type_name':
+                  'zope.app.browser.add.'
+                  'zope.app.i18n.translationdomain.TranslationDomain',
                   'id': 'zope',
                   'add': 'Add'})
 
@@ -74,7 +76,9 @@
         self.publish(
             path + '/action.html',
             basic='mgr:mgrpw',
-            form={'type_name': 'zope.app.browser.add.TranslationDomain.f1',
+            form={'type_name':
+                  'zope.app.browser.add.'
+                  'zope.app.i18n.translationdomain.TranslationDomain',
                   'id': 'zope',
                   'add': 'Add'})
 
@@ -96,7 +100,9 @@
         self.publish(
             path + '/action.html',
             basic='mgr:mgrpw',
-            form={'type_name': 'zope.app.browser.add.TranslationDomain.f1',
+            form={'type_name':
+                  'zope.app.browser.add.'
+                  'zope.app.i18n.translationdomain.TranslationDomain',
                   'id': 'zope',
                   'add': 'Add'})
 
@@ -120,7 +126,9 @@
         self.publish(
             path + '/action.html',
             basic='mgr:mgrpw',
-            form={'type_name': 'zope.app.browser.add.TranslationDomain.f1',
+            form={'type_name':
+                  'zope.app.browser.add.'
+                  'zope.app.i18n.translationdomain.TranslationDomain',
                   'id': 'zope',
                   'add': 'Add'})
 
@@ -145,7 +153,9 @@
         self.publish(
             path + '/action.html',
             basic='mgr:mgrpw',
-            form={'type_name': 'zope.app.browser.add.TranslationDomain.f1',
+            form={'type_name':
+                  'zope.app.browser.add.'
+                  'zope.app.i18n.translationdomain.TranslationDomain',
                   'id': 'zope',
                   'add': 'Add'})
 

Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/tests/dochttp.py (from rev 27222, Zope3/trunk/src/zope/app/tests/dochttp.py)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py	2004-08-24 20:21:30 UTC (rev 27252)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/tests/functional.py	2004-08-24 20:30:04 UTC (rev 27253)
@@ -18,6 +18,8 @@
 $Id$
 """
 import logging
+import re
+import rfc822
 import sys
 import traceback
 import unittest
@@ -25,26 +27,30 @@
 from StringIO import StringIO
 from Cookie import SimpleCookie
 
-from transaction import get_transaction
+from transaction import abort, commit
 from ZODB.DB import DB
 from ZODB.DemoStorage import DemoStorage
+import zope.interface
 from zope.publisher.browser import BrowserRequest
 from zope.publisher.http import HTTPRequest
 from zope.publisher.publish import publish
+from zope.publisher.xmlrpc import XMLRPCRequest
 from zope.security.interfaces import Forbidden, Unauthorized
 from zope.security.management import endInteraction
+import zope.server.interfaces
+from zope.testing import doctest
 
 from zope.app.debug import Debugger
+from zope.app.publication.http import HTTPPublication
+from zope.app.publication.browser import BrowserPublication
+from zope.app.publication.xmlrpc import XMLRPCPublication
 from zope.app.publication.zopepublication import ZopePublication
 from zope.app.publication.http import HTTPPublication
 import zope.app.tests.setup
 from zope.app.component.hooks import setSite, getSite
 
+HTTPTaskStub = StringIO
 
-class HTTPTaskStub(StringIO):
-    pass
-
-
 class ResponseWrapper(object):
     """A wrapper that adds several introspective methods to a response."""
 
@@ -73,7 +79,6 @@
     def __getattr__(self, attr):
         return getattr(self._response, attr)
 
-
 class FunctionalTestSetup(object):
     """Keeps shared state across several functional test cases."""
 
@@ -118,7 +123,7 @@
 
     def tearDown(self):
         """Cleans up after a functional test case."""
-        get_transaction().abort()
+        abort()
         if self.connection:
             self.connection.close()
             self.connection = None
@@ -146,6 +151,7 @@
 
     def tearDown(self):
         """Cleans up after a functional test case."""
+
         FunctionalTestSetup().tearDown()
         super(FunctionalTestCase, self).tearDown()
 
@@ -154,10 +160,10 @@
         return FunctionalTestSetup().getRootFolder()
 
     def commit(self):
-        get_transaction().commit()
+        commit()
 
     def abort(self):
-        get_transaction().abort()
+        abort()
 
 class BrowserTestCase(FunctionalTestCase):
     """Functional test case for Browser requests."""
@@ -169,6 +175,7 @@
 
     def tearDown(self):
         del self.cookies
+
         self.setSite(None)
         super(BrowserTestCase, self).tearDown()
 
@@ -373,6 +380,134 @@
         publish(request, handle_errors=handle_errors)
         return response
 
+
+class HTTPHeaderOutput:
+
+    zope.interface.implements(zope.server.interfaces.IHeaderOutput)
+
+    def __init__(self, protocol, omit):
+        self.headers = {}
+        self.headersl = []
+        self.protocol = protocol
+        self.omit = omit
+    
+    def setResponseStatus(self, status, reason):
+        self.status, self.reason = status, reason
+
+    def setResponseHeaders(self, mapping):
+        self.headers.update(dict(
+            [('-'.join([s.capitalize() for s in name.split('-')]), v)
+             for name, v in mapping.items()
+             if name.lower() not in self.omit]
+        ))
+
+    def appendResponseHeaders(self, lst):
+        headers = [split_header(header) for header in lst]
+        self.headersl.extend(
+            [('-'.join([s.capitalize() for s in name.split('-')]), v)
+             for name, v in headers
+             if name.lower() not in self.omit]
+        )
+
+    def __str__(self):
+        out = ["%s: %s" % header for header in self.headers.items()]
+        out.extend(["%s: %s" % header for header in self.headersl])
+        out.sort()
+        out.insert(0, "%s %s %s" % (self.protocol, self.status, self.reason))
+        return '\n'.join(out)
+
+class DocResponseWrapper(ResponseWrapper):
+    """Response Wrapper for use in doc tests
+    """
+
+    def __init__(self, response, outstream, path, header_output):
+        ResponseWrapper.__init__(self, response, outstream, path)
+        self.header_output = header_output
+
+    def __str__(self):
+        body = self.getOutput()
+        if body:
+            return "%s\n\n%s" % (self.header_output, body)
+        return "%s\n" % (self.header_output)
+
+def http(request_string):
+    """Execute an HTTP request string via the publisher
+
+    This is used for HTTP doc tests.
+    """
+    # Commit work done by previous python code.
+    commit()
+
+    # Discard leading white space to make call layout simpler
+    request_string = request_string.lstrip()
+
+    # split off and parse the command line
+    l = request_string.find('\n')
+    command_line = request_string[:l].rstrip()
+    request_string = request_string[l+1:]
+    method, path, protocol = command_line.split()
+    
+
+    instream = StringIO(request_string)
+    environment = {"HTTP_HOST": 'localhost',
+                   "HTTP_REFERER": 'localhost',
+                   "REQUEST_METHOD": method,
+                   "SERVER_PROTOCOL": protocol,
+                   }
+
+    headers = [split_header(header)
+               for header in rfc822.Message(instream).headers]
+    for name, value in headers:
+        name = 'HTTP_' + ('_'.join(name.upper().split('-')))
+        environment[name] = value.rstrip()
+
+    outstream = HTTPTaskStub()
+
+
+    old_site = getSite()
+    setSite(None)
+    app = FunctionalTestSetup().getApplication()
+    header_output = HTTPHeaderOutput(
+        protocol, ('x-content-type-warning', 'x-powered-by'))
+
+    if method in ('GET', 'POST', 'HEAD'):
+        if (method == 'POST' and
+            environment.get('CONTENT_TYPE', '').startswith('text/xml')
+            ):
+            request_cls = XMLRPCRequest
+            publication_cls = XMLRPCPublication
+        else:
+            request_cls = BrowserRequest
+            publication_cls = BrowserPublication
+    else:
+        request_cls = HTTPRequest
+        publication_cls = HTTPPublication
+    
+    request = app._request(path, instream, outstream,
+                           environment=environment,
+                           request=request_cls, publication=publication_cls)
+    request.response.setHeaderOutput(header_output)
+    response = DocResponseWrapper(request.response, outstream, path,
+                                  header_output)
+    
+    publish(request)
+    setSite(old_site)
+
+    # sync Python connection:
+    getRootFolder()._p_jar.sync()
+    
+    return response
+
+headerre = re.compile('(\S+): (.+)$')
+def split_header(header):
+    return headerre.match(header).group(1, 2)
+
+def getRootFolder():
+    return FunctionalTestSetup().getRootFolder()
+
+def sync():
+    getRootFolder()._p_jar.sync()
+
 #
 # Sample functional test case
 #
@@ -402,6 +537,32 @@
     suite.addTest(unittest.makeSuite(SampleFunctionalTest))
     return suite
 
+def FunctionalDocFileSuite(*paths, **kw):
+    globs = kw.setdefault('globs', {})
+    globs['http'] = http
+    globs['getRootFolder'] = getRootFolder
+    globs['sync'] = sync
 
+    kw['package'] = doctest._normalize_module(kw.get('package'))
+
+    kwsetUp = kw.get('setUp')
+    def setUp():
+        FunctionalTestSetup().setUp()
+        
+        if kwsetUp is not None:
+            kwsetUp()
+    kw['setUp'] = setUp
+
+    kwtearDown = kw.get('tearDown')
+    def tearDown():
+        if kwtearDown is not None:
+            kwtearDown()
+        FunctionalTestSetup().tearDown()
+    kw['tearDown'] = tearDown
+
+    kw['optionflags'] = doctest.ELLIPSIS | doctest.CONTEXT_DIFF
+
+    return doctest.DocFileSuite(*paths, **kw)
+
 if __name__ == '__main__':
     unittest.main()

Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/tests/recorded (from rev 27222, Zope3/trunk/src/zope/app/tests/recorded)

Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/tests/test_dochttp.py (from rev 27222, Zope3/trunk/src/zope/app/tests/test_dochttp.py)



More information about the Zope3-Checkins mailing list