[Zope-Checkins] SVN: Zope/trunk/lib/python/Testing/ZopeTestCase/ added ZopeTestCase 0.9

Andreas Jung andreas at andreas-jung.com
Mon Sep 6 15:04:42 EDT 2004


Log message for revision 27453:
  added ZopeTestCase 0.9
  


Changed:
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/PortalTestCase.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeTestCase.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/API.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/CHANGES.txt
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/ENVIRONMENT.txt
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/FunctionalTesting.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/HOWTO.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/INSTALL.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/IZopeTestCase.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PROFILER.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PortalTestCase.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/README.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/SECURITY.stx
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/TIMELINES.txt
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/doc/VERSION.txt
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/framework.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/functional.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/profiler.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/runalltests.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/sandbox.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testFunctional.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testPortalTestCase.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testPythonScript.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testSkeleton.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/testZopeTestCase.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/threadutils.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py
  A   Zope/trunk/lib/python/Testing/ZopeTestCase/ztc_common.py


-=-
Added: Zope/trunk/lib/python/Testing/ZopeTestCase/PortalTestCase.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/PortalTestCase.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/PortalTestCase.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,135 @@
+#
+# Abstract base test case for working with CMF-style portals
+#
+# This base class maintains a fixture consisting of:
+#
+#   - a portal object (self.portal)
+#   - a user folder inside the portal
+#   - a default user with role 'Member' inside the user folder
+#   - the default user's memberarea (self.folder)
+#   - the default user is logged in
+#
+# The twist is that the portal object itself is *not* created
+# by the PortalTestCase class! Subclasses must make sure
+# getPortal() returns a usable portal object to the setup code.
+#
+
+# $Id: PortalTestCase.py,v 1.24 2004/03/29 01:14:14 shh42 Exp $
+
+import ZopeTestCase
+
+from AccessControl import getSecurityManager
+from AccessControl.SecurityManagement import newSecurityManager
+from Acquisition import aq_base
+
+portal_name = 'portal'
+user_name = ZopeTestCase.user_name
+
+
+class PortalTestCase(ZopeTestCase.ZopeTestCase):
+    '''Base test case for testing CMF-style portals
+
+       __implements__ = (IPortalTestCase, ISimpleSecurity, IExtensibleSecurity)
+
+       See doc/IZopeTestCase.py for more.
+    '''
+
+    _configure_portal = 1
+
+    def getPortal(self):
+        '''Returns the portal object for use by the setup
+           code. Will typically be overridden by subclasses
+           to return the object serving as the portal.
+        '''
+        return self.app[portal_name]
+
+    def createMemberarea(self, member_id):
+        '''Creates a memberarea for the specified member. 
+           Subclasses may override to provide a customized
+           or more lightweight version of the memberarea.
+        '''
+        pm = self.portal.portal_membership
+        pm.createMemberarea(member_id)
+
+    def setUp(self):
+        '''Sets up the fixture. Do not override,
+           use the hooks instead.
+        '''
+        try:
+            self.beforeSetUp()
+            self.app = self._app()
+            self.portal = self.getPortal()
+            self._setup()
+            self._refreshSkinData()
+            self.afterSetUp()
+        except:
+            self._clear()
+            raise
+
+    def _setup(self):
+        '''Configures the portal. Framework authors may override.'''
+        if self._configure_portal:
+            self._setupUserFolder()
+            self._setupUser()
+            self.login()
+            self._setupHomeFolder()
+
+    def _setupUserFolder(self):
+        '''Creates the user folder if missing.'''
+        if not hasattr(aq_base(self.portal), 'acl_users'):
+            self.portal.manage_addUserFolder()
+
+    def _setupUser(self):
+        '''Creates the default user.'''
+        uf = self.portal.acl_users
+        uf._doAddUser(user_name, 'secret', ['Member'], [])
+
+    def _setupHomeFolder(self):
+        '''Creates the default user's home folder.'''
+        self.createMemberarea(user_name)
+        pm = self.portal.portal_membership
+        self.folder = pm.getHomeFolder(user_name)
+
+    def _refreshSkinData(self):
+        '''Refreshes the magic _v_skindata attribute.'''
+        if hasattr(self.portal, '_v_skindata'):
+            self.portal._v_skindata = None
+        if hasattr(self.portal, 'setupCurrentSkin'):
+            self.portal.setupCurrentSkin()
+
+    def _clear(self, call_close_hook=0):
+        '''Clears the fixture.'''
+        # No automagic cleanups here. We rely on
+        # transaction abort. Those who commit are
+        # required to clean up their own mess.
+        if call_close_hook:
+            self.beforeClose()
+        self._close()
+        self.logout()
+        self.afterClear()
+
+    # Security interfaces
+
+    def setRoles(self, roles, name=user_name):
+        '''Changes the user's roles.'''
+        uf = self.portal.acl_users
+        uf._doChangeUser(name, None, roles, [])
+        if name == getSecurityManager().getUser().getId():
+            self.login(name)
+
+    def setPermissions(self, permissions, role='Member'):
+        '''Changes the user's permissions.'''
+        self.portal.manage_role(role, permissions)
+
+    def login(self, name=user_name):
+        '''Logs in.'''
+        uf = self.portal.acl_users
+        user = uf.getUserById(name)
+        if not hasattr(user, 'aq_base'):
+            user = user.__of__(uf)
+        newSecurityManager(None, user)
+
+
+# b/w compatibility names
+_portal_name = portal_name
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,172 @@
+#
+# Lightweight Zope startup
+#
+# Fast Zope startup is achieved by not installing (m)any
+# products. If your tests require a product you must
+# install it yourself using installProduct().
+#
+# Typically used as in
+#
+#   import ZopeLite as Zope
+#   Zope.installProduct('SomeProduct')
+#   app = Zope.app()
+#
+
+# $Id: ZopeLite.py,v 1.19 2004/03/19 13:51:32 shh42 Exp $
+
+import os, sys, time
+
+# Increase performance on MP hardware
+sys.setcheckinterval(2500)
+
+# Shut up if we are not in control of the import process
+_quiet = 'Zope' in sys.modules.keys()
+
+def _print(msg):
+    '''Writes 'msg' to stderr and flushes the stream.'''
+    sys.stderr.write(msg)
+    sys.stderr.flush()
+
+def _write(msg):
+    '''Writes 'msg' to stderr if not _quiet.'''
+    if not _quiet:
+        _print(msg)
+
+def _exec(cmd):
+    '''Prints the time it takes to execute 'cmd'.'''
+    if os.environ.get('X', None):
+        start = time.time()
+        exec cmd
+        _print('(%.3fs)' % (time.time() - start))
+
+_write('Loading Zope, please stand by ')
+_start = time.time()
+
+# Zope 2.7 specifics
+try: 
+    import App.config
+except ImportError:
+    pass # Zope < 2.7
+else:
+    # Need to import Zope early on as the 
+    # ZTUtils package relies on it
+    config = App.config.getConfiguration()
+    config.debug_mode = 0
+    App.config.setConfiguration(config)
+    _exec('import Zope')
+    import Zope
+
+_exec('import ZODB')
+import ZODB
+_write('.')
+
+_exec('import Globals')
+import Globals
+# Work around a bug in Zope 2.7.0
+Globals.DevelopmentMode = 0
+_exec('import OFS.SimpleItem')
+import OFS.SimpleItem
+_exec('import OFS.ObjectManager')
+import OFS.ObjectManager
+_write('.')
+
+_exec('import OFS.Application')
+import OFS.Application
+import App.ProductContext
+
+# Avoid expensive product import
+def _null_import_products(): pass
+OFS.Application.import_products = _null_import_products
+
+# Avoid expensive product installation
+def _null_initialize(app): pass
+OFS.Application.initialize = _null_initialize
+
+# Avoid expensive help registration
+def _null_register_topic(self,id,topic): pass
+App.ProductContext.ProductContext.registerHelpTopic = _null_register_topic
+def _null_register_title(self,title): pass
+App.ProductContext.ProductContext.registerHelpTitle = _null_register_title
+def _null_register_help(self,directory='',clear=1,title_re=None): pass
+App.ProductContext.ProductContext.registerHelp = _null_register_help
+
+# Make sure to use a temporary client cache
+if os.environ.get('ZEO_CLIENT'): del os.environ['ZEO_CLIENT']
+
+# Load Zope (< 2.7)
+_exec('import Zope')
+import Zope
+_write('.')
+
+from OFS.Application import get_folder_permissions, get_products, install_product
+from OFS.Folder import Folder
+import Products
+
+_theApp = Zope.app()
+_installedProducts = {}
+
+def hasProduct(name):
+    '''Tests if a product can be found along Products.__path__'''
+    return name in [n[1] for n in get_products()]
+
+def installProduct(name, quiet=0):
+    '''Installs a Zope product.'''
+    start = time.time()
+    app = _theApp
+    meta_types = []
+    if not _installedProducts.has_key(name):
+        for priority, product_name, index, product_dir in get_products():
+            if product_name == name:
+                if not quiet: _print('Installing %s ... ' % product_name)
+                # We want to fail immediately if a product throws an exception
+                # during install, so we set the raise_exc flag.
+                install_product(app, product_dir, product_name, meta_types,
+                                get_folder_permissions(), raise_exc=1)
+                _installedProducts[product_name] = 1
+                Products.meta_types = Products.meta_types + tuple(meta_types)
+                Globals.default__class_init__(Folder)
+                if not quiet: _print('done (%.3fs)\n' % (time.time() - start))
+                break
+        else:
+            if name != 'SomeProduct':   # Ignore the skeleton tests :-P
+                if not quiet: _print('Installing %s ... NOT FOUND\n' % name)
+
+# Loading the Control_Panel of an existing ZODB may take 
+# a while; print another dot if it does.
+_s = time.time(); _max = (_s - _start) / 4
+_exec('_theApp.Control_Panel')
+_cp = _theApp.Control_Panel
+if hasattr(_cp, 'initialize_cache'):
+    _cp.initialize_cache()
+if (time.time() - _s) > _max: 
+    _write('.')
+
+installProduct('PluginIndexes', 1)  # Must install first
+installProduct('OFSP', 1)
+#installProduct('ExternalMethod', 1)
+#installProduct('ZSQLMethods', 1)
+#installProduct('ZGadflyDA', 1)
+#installProduct('MIMETools', 1)
+#installProduct('MailHost', 1)
+
+# So people can use ZopeLite.app()
+app = Zope.app
+debug = Zope.debug
+DB = Zope.DB
+# startup appeared in Zope 2.6.1
+def startup(): pass
+# configure appeared in Zope 2.7
+try: configure = Zope.configure
+except AttributeError: pass 
+
+from ZODB.DemoStorage import DemoStorage
+def sandbox(base=None):
+    '''Returns what amounts to a sandbox copy of the base ZODB.'''
+    if base is None: base = Zope.DB
+    base_storage = base._storage
+    quota = getattr(base_storage, '_quota', None)
+    storage = DemoStorage(base=base_storage, quota=quota)
+    return ZODB.DB(storage)
+
+_write(' done (%.3fs)\n' % (time.time() - _start))
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeTestCase.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeTestCase.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeTestCase.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,207 @@
+#
+# Default test case & fixture for Zope testing
+#
+# The fixture consists of:
+#
+#   - a folder (self.folder)
+#   - a user folder inside that folder
+#   - a default user inside the user folder
+#
+# The default user is logged in and has the 'Access contents information'
+# and 'View' permissions given to his role.
+#
+
+# $Id: ZopeTestCase.py,v 1.15 2004/03/29 01:14:13 shh42 Exp $
+
+import ZopeLite as Zope
+
+import unittest
+import utils
+import profiler
+
+from AccessControl import getSecurityManager
+from AccessControl.SecurityManagement import newSecurityManager
+from AccessControl.SecurityManagement import noSecurityManager
+from AccessControl.Permissions import access_contents_information
+from AccessControl.Permissions import view
+
+folder_name = 'test_folder_1_'
+user_name = 'test_user_1_'
+user_role = 'test_role_1_'
+standard_permissions = [access_contents_information, view]
+
+_connections = utils.ConnectionRegistry()
+
+
+
+def app():
+    '''Opens a ZODB connection and returns the app object.'''
+    app = Zope.app()
+    _connections.register(app._p_jar)
+    return utils.makerequest(app)
+
+def close(app):
+    '''Closes the app's ZODB connection.'''
+    _connections.close(app._p_jar)
+
+def closeConnections():
+    '''Closes all registered ZODB connections.'''
+    _connections.closeAll()
+
+
+
+class ZopeTestCase(profiler.Profiled, unittest.TestCase):
+    '''Base test case for Zope testing
+
+       __implements__ = (IZopeTestCase, ISimpleSecurity, IExtensibleSecurity)
+
+       See doc/IZopeTestCase.py for more
+    '''
+
+    _setup_fixture = 1
+
+    def afterSetUp(self):
+        '''Called after setUp() has completed. This is
+           far and away the most useful hook.
+        '''
+        pass
+
+    def beforeTearDown(self):
+        '''Called before tearDown() is executed.
+           Note that tearDown() is not called if
+           setUp() fails.
+        '''
+        pass
+
+    def afterClear(self):
+        '''Called after the fixture has been cleared.
+           Note that this may occur during setUp() *and*
+           tearDown().
+        '''
+        pass
+
+    def beforeSetUp(self):
+        '''Called before the ZODB connection is opened,
+           at the start of setUp(). By default begins
+           a new transaction.
+        '''
+        get_transaction().begin()
+
+    def beforeClose(self):
+        '''Called before the ZODB connection is closed,
+           at the end of tearDown(). By default aborts
+           the transaction.
+        '''
+        get_transaction().abort()
+
+    def setUp(self):
+        '''Sets up the fixture. Do not override,
+           use the hooks instead.
+        '''
+        try:
+            self.beforeSetUp()
+            self.app = self._app()
+            self._setup()
+            self.afterSetUp()
+        except:
+            self._clear()
+            raise
+
+    def tearDown(self):
+        '''Tears down the fixture. Do not override,
+           use the hooks instead.
+        '''
+        try:
+            self.beforeTearDown()
+            self._clear(1)
+        except:
+            self._clear()
+            raise
+
+    def _app(self):
+        '''Returns the app object for a test.'''
+        return app()
+
+    def _setup(self):
+        '''Sets up the fixture. Framework authors may override.'''
+        if self._setup_fixture:
+            self._setupFolder()
+            self._setupUserFolder()
+            self._setupUser()
+            self.login()
+
+    def _setupFolder(self):
+        '''Creates and configures the folder.'''
+        self.app.manage_addFolder(folder_name)
+        self.folder = self.app._getOb(folder_name)
+        self.folder._addRole(user_role)
+        self.folder.manage_role(user_role, standard_permissions)
+
+    def _setupUserFolder(self):
+        '''Creates the user folder.'''
+        self.folder.manage_addUserFolder()
+
+    def _setupUser(self):
+        '''Creates the default user.'''
+        uf = self.folder.acl_users
+        uf._doAddUser(user_name, 'secret', [user_role], [])
+
+    def _clear(self, call_close_hook=0):
+        '''Clears the fixture.'''
+        if self._setup_fixture:
+            try: self.app._delObject(folder_name)
+            except (AttributeError, RuntimeError): pass
+        if call_close_hook:
+            self.beforeClose()
+        self._close()
+        self.logout()
+        self.afterClear()
+
+    def _close(self):
+        '''Closes the ZODB connection.'''
+        get_transaction().abort()
+        closeConnections()
+
+    # Security interfaces
+
+    def setRoles(self, roles, name=user_name):
+        '''Changes the user's roles.'''
+        uf = self.folder.acl_users
+        uf._doChangeUser(name, None, roles, [])
+        if name == getSecurityManager().getUser().getId():
+            self.login(name)
+
+    def setPermissions(self, permissions, role=user_role):
+        '''Changes the user's permissions.'''
+        self.folder.manage_role(role, permissions)
+
+    def login(self, name=user_name):
+        '''Logs in.'''
+        uf = self.folder.acl_users
+        user = uf.getUserById(name)
+        if not hasattr(user, 'aq_base'):
+            user = user.__of__(uf)
+        newSecurityManager(None, user)
+
+    def logout(self):
+        '''Logs out.'''
+        noSecurityManager()
+
+    # b/w compatibility methods
+
+    def _setRoles(self, roles, name=user_name):
+        self.setRoles(roles, name)
+    def _setPermissions(self, permissions, role=user_role):
+        self.setPermissions(permissions, role)
+    def _login(self, name=user_name):
+        self.login(name)
+    def _logout(self):
+        self.logout()
+
+
+# b/w compatibility names
+_folder_name = folder_name
+_user_name = user_name
+_user_role = user_role
+_standard_permissions = standard_permissions
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,43 @@
+#
+# Names exported by the ZopeTestCase module
+#
+
+# $Id: __init__.py,v 1.11 2004/02/06 18:00:02 shh42 Exp $
+
+import ZopeLite as Zope
+import utils
+
+from ZopeLite import installProduct
+from ZopeLite import hasProduct
+from ZopeLite import _print
+
+from ZopeTestCase import folder_name
+from ZopeTestCase import user_name
+from ZopeTestCase import user_role
+from ZopeTestCase import standard_permissions
+from ZopeTestCase import ZopeTestCase
+
+from PortalTestCase import portal_name
+from PortalTestCase import PortalTestCase
+
+from profiler import Profiled
+from sandbox import Sandboxed
+from functional import Functional
+
+from ZopeTestCase import app
+from ZopeTestCase import close
+from ZopeTestCase import closeConnections
+
+from unittest import main
+
+# Convenience class for functional unit testing
+class FunctionalTestCase(Functional, ZopeTestCase):
+    pass
+
+# b/w compatibility names
+_folder_name = folder_name
+_user_name = user_name
+_user_role = user_role
+_standard_permissions = standard_permissions
+_portal_name = portal_name
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/API.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/API.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/API.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,273 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+ZopeTestCase API Reference
+
+    A nicely rendered version of this document can be found at
+    http://zope.org/Members/shh/ZopeTestCaseWiki/ApiReference
+
+
+
+Module Testing.ZopeTestCase
+
+    Top-level package exposing names from contained modules
+
+    Constants
+
+        folder_name
+
+        user_name
+
+        user_role
+
+        standard_permissions
+
+        portal_name
+
+    Functions
+
+        hasProduct(name)
+
+        installProduct(name, quiet=0)
+
+        app()
+
+        close(app)
+
+        main()
+
+        _print(msg)
+
+    Classes
+
+        ZopeTestCase
+
+        PortalTestCase
+
+        Profiled
+
+        Sandboxed
+
+        Functional
+
+    Modules
+
+        ZopeLite as Zope
+
+        utils
+
+
+
+Module ZopeLite
+
+    Lightweight replacement for the Zope module
+
+    Constants
+
+        DB
+
+    Functions
+
+        hasProduct(name)
+
+        installProduct(name, quiet=0)
+
+        app(connection=None)
+
+        sandbox(base=None)
+
+        startup()
+
+        _print(msg)
+
+
+
+Module ZopeTestCase
+
+    Default test case and fixture for Zope testing
+
+    Constants
+
+        folder_name
+
+        user_name
+
+        user_role
+
+        standard_permissions
+
+    Functions
+
+        app()
+
+        close(app)
+
+    Classes
+
+        ZopeTestCase
+        
+        
+
+Class ZopeTestCase
+
+    Base test case for Zope testing
+
+    Attributes
+
+        _setup_fixture = 1
+
+    Methods
+
+        afterSetUp()
+
+        beforeTearDown()
+
+        afterClear()
+
+        beforeSetUp()
+
+        beforeClose()
+
+        setRoles(roles, name=user_name)
+
+        setPermissions(permissions, role=user_role)
+
+        login(name=user_name)
+
+        logout()
+
+
+
+Module PortalTestCase
+
+    Test case and fixture for testing CMF-based applications
+
+    Constants
+
+        portal_name
+
+        user_name
+
+    Classes
+
+        PortalTestCase
+
+ 
+
+Class PortalTestCase
+
+    Base test case for CMF testing
+
+    Attributes
+
+        _configure_portal = 1
+
+    Methods
+
+        getPortal()
+
+        createMemberarea(name)
+
+        afterSetUp()
+
+        beforeTearDown()
+
+        afterClear()
+
+        beforeSetUp()
+
+        beforeClose()
+
+        setRoles(roles, name=user_name)
+
+        setPermissions(permissions, role=user_role)
+
+        login(name=user_name)
+
+        logout()
+
+
+
+Module profiler
+
+    Profiling support
+
+    Functions
+
+        runcall(func, *args, **kw)
+
+        print_stats()
+
+        dump_stats(filename)
+
+    Classes
+
+        Profiled
+
+
+
+Class Profiled
+
+    Profiling support mix-in for xTestCases
+
+    Methods
+
+        runcall(func, *args, **kw)
+
+
+
+Module sandbox
+
+    ZODB sandbox support
+
+    Classes
+
+        Sandboxed
+
+
+
+Class Sandboxed
+
+    Sandbox support mix-in for xTestCases
+
+    Methods
+
+        *No public methods*
+
+
+
+Module functional
+
+    Functional testing support
+
+    Classes
+
+        Functional
+
+
+
+Class Functional
+
+    Functional testing mix-in for xTestCases
+
+    Methods
+
+        publish(path, basic=None, env=None, extra=None, request_method='GET')
+
+
+
+Module utils
+
+    Utility functions to extend the test environment
+
+    Functions
+
+        setupCoreSessions(app=None)
+
+        setupZGlobals(app=None)
+
+        setupSiteErrorLog(app=None)
+
+        startZServer(number_of_threads=1, log=None)
+
+        importObjectFromFile(container, filename, quiet=0)
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/CHANGES.txt
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/CHANGES.txt	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/CHANGES.txt	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,104 @@
+0.9.0
+- No longer support Zope 2.4 as its DemoStorage is broken.
+- Made PortalTestCase derive from ZopeTestCase (again).
+- Made all xTestCases profiler aware by default.
+- Renamed the Profiler module to profiler.py (lowercase).
+- Added support for ZODB sandboxes, sandbox.py.
+- Added support for functional unit testing, functional.py.
+- The profiler module now provides a dump_stats() method to write
+  profiler statistics to a file for manual inspection.
+- The REQUEST now fakes a published object to make the URL1
+  request variable available to tests. Thanks to Alan Runyan.
+- startZServer() now accepts a log argument, allowing to pass
+  a stream which the ZServer access log (Z2.log) will be written to.
+- The 'app' argument of utility functions is now optional.
+- Fixed custom_zodb.py support for Zope 2.7.
+- Most mercilessly refactored ztc_common.py.
+- ZopeLite now loads silently if it does not control the import process.
+
+0.8.6
+- Revised and amended much of the existing documentation. 
+- Added an API reference (skeleton), API.stx.
+- Documented what's going on when tests are run in TIMELINES.txt.
+- Fixed issues with testZODBCompat.py and Zope < 2.6.
+- setupZGlobals() now uses a new-style BTrees.OOBTree.
+- Profiling can now be activated from the command line.
+
+0.8.4
+- framework.py now flushes stdout to not mess up the output in batch mode.
+- framework.py no longer adds os.pardir to the sys.path. Thanks to 
+  Yoshinori Okuji.
+- Made sure user objects are not inadvertently wrapped twice by login().
+- Made sure "renegade" transactions are aborted if something goes wrong 
+  during the setup phase.
+- initialize_cache() is no longer called for Zope 2.7.
+
+0.8.2
+- Removed the leading underscores from all constant names. They proved 
+  non-private in "real life" anyway. The old names are still available
+  for backward compatibility, but are deprecated.
+- Removed NO_PRODUCT_LOAD for reasons of obscureness and YAGNI.
+- Added a test for ZODB behavior in ZTC, testZODBCompat.py.
+
+0.8.0
+- Added a PortalTestCase base class to aid testing of CMF-style portals.
+- Added simple profiling support using the Python profile library.
+- Got rid of the ill-conceived FX interface (don't even ask).
+- ZopeLite now supports Zope 2.7.
+
+0.7.2 (not released)
+- ZopeLite gained a do-nothing startup() method for API compliance.
+- The ZopeTestCase module now has a main() method like unittest has.
+- Made sure the test user's 'roles' attribute is a list because CMF
+  role-mapping assumes it can append to it. :-/
+
+0.7.0
+- Fixed a bug that caused setRoles() to only work with the 
+  default user folder. Refactored the fixture code in the process.
+- Reworked the connection registry and wrote tests for it.
+- Made afterClear() largely redundant because it turned out to be just that.
+- Added close() method to be able to close ZODB connections individually.
+- Added ISimpleSecurity and IExtensibleSecurity interfaces.
+
+0.6.4
+- installProduct() now immediately fails if a product throws an
+  exception during installation. Thanks to Tom Jenkins.
+- The REQUEST no longer contains the entire shell environment.
+- Moved all documentation files to the 'doc' subdirectory.
+- Added IZopeTestCase and IZopeTestCaseFX interfaces.
+
+0.6.2
+- The effects of setting INSTANCE_HOME have been changed to something 
+  less surprising. Please see ENVIRONMENT.txt for details.
+- Now uses the environment variable ZEO_INSTANCE_HOME to enable ZEO 
+  support.
+
+0.6.0
+- Use a module-level database connection registry to avoid freezing 
+  after too many errors.
+- All tests are now transactional by default.
+- Added beforeSetUp() and beforeClose() hooks to the ZopeTestCase class.
+- Added utility method importObjectFromFile()
+- Added utility method setupSiteErrorLog().
+- Added utility method startZServer().
+- Added accompanying test, testWebserver.py.
+- Added first incarnation of a How-To.
+- Revised the example tests.
+
+0.5.3
+- Zope 2.6 compatibility adjustments.
+- Hardening in the face of incomplete Zope installations.
+
+0.5.2
+- Delete ZEO_CLIENT environment variable to enforce a temporary client 
+  cache. Repair Zope 2.4 Testing package issue in the process.
+- Provide NO_PRODUCT_LOAD environment variable for completeness.
+- Added hasProduct() method to allow testing for product availability.
+- Added new utility method setupZGlobals().
+- Added a skeleton test suite, testSkeleton.py.
+- Added runalltests.py script.
+- Added CHANGES, INSTALL, and VERSION documents.
+
+0.5.0
+- Unit and regression testing framework for Zope. Initial release.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/ENVIRONMENT.txt
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/ENVIRONMENT.txt	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/ENVIRONMENT.txt	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,48 @@
+
+ZTC makes the following assumptions about its environment:
+
+a) The 'ZopeTestCase' package is installed in the Zope "trunk" inside the
+   'Testing' module, which means: SOFTWARE_HOME/Testing/ZopeTestCase.
+
+b) A 'Products' directory exists inside SOFTWARE_HOME and INSTANCE_HOME.
+
+c) The tests (the 'tests' subdirectories) are located either below a 
+   SOFTWARE_HOME or INSTANCE_HOME, typically in Products/MyCoolProduct/tests.
+
+d) The somewhat weak assumption is that ZTC can walk up the directory tree from
+   'tests', and find a 'Products' directory. This is how INSTANCE_HOME 
+   detection works. It regrettably fails on some filesystems when symbolic 
+   links are involved (a solution is detailed below, so hang on).
+
+
+The non-trivial part is that INSTANCE_HOME has two distinct purposes:
+
+    1) INSTANCE_HOME/lib/python must be added to sys.path and 
+       INSTANCE_HOME/Products to Products.__path__.
+
+    2) INSTANCE_HOME/custom_zodb.py must be used to set up a ZODB.
+
+
+ZTC attempts to resolve this by detecting an INSTANCE_HOME for 1) but leaving
+the actual environment variable untouched so 2) works by still pointing into 
+SOFTWARE_HOME/Testing.
+
+As soon as I allow you to set INSTANCE_HOME yourself, I lose the ability to 
+distinguish whether you mean 1) or 2) or both. 
+
+Before ZTC 0.6.2 the code assumed "both" and did the magic ZEO dance. This was
+clearly too surprising.
+
+The behaviour has now been changed to 1). 
+
+That way, if your setup does not fit c) or you run into the symbolic link 
+problem d), you can solve it by setting INSTANCE_HOME prior to running the 
+tests.
+
+ZEO support ("both") is handled separately, through the new environment 
+variable ZEO_INSTANCE_HOME.
+
+
+You may want to consider using a testrunner to run your tests. You can find one 
+here: http://zope.org/Members/shh/TestRunner
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/FunctionalTesting.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/FunctionalTesting.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/FunctionalTesting.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,47 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+Functional Testing Readme
+
+    The functional testing support of ZopeTestCase was inspired by
+    Marius Gedminas' work for Zope 3.
+
+    Deriving from the 'Functional' mix-in (and an xTestCase) adds a
+    'publish' method to your test case class. Tests can call
+    'self.publish(path, basic=None, env=None, extra=None, request_method='GET')', 
+    passing a path and, optionally, basic-auth info and form data. 
+    The path may contain a query string.
+
+    'publish' returns an enhanced Response object, that can be queried
+    for status, response body, headers, etc.
+
+    'publish' invokes the ZPublisher machinery just as if the request 
+    had come in through ZServer. This allows for high-level testing
+    of things like argument marshalling, form validation, and traversal.
+
+    Note that the tests have *full access to the ZODB*. This means you
+    can easily prepare a fixture for 'publish' and/or check the impact
+    of a publication on the database. This represents a major advantage 
+    over purely URL-based test environments!
+
+    Please see the 'testFunctional.py' example test for more.
+
+    While the modules are called 'functional.py' in both Zope 3 and 
+    ZopeTestCase, it is current wisdom that such tests are not truly
+    "functional tests", but rather "integration tests".
+
+    True functional tests, in their most-helpful guise as "acceptance
+    tests", must be able to test the end-user experience. For web 
+    applications this means: browser simulation.
+
+    Plone 2 comes with an 'ftests' package combining the functional
+    testing support of ZopeTestCase with the "mechanize" browser 
+    simulator library: http://wwwsearch.sourceforge.net/mechanize/
+    (For some version of 2, currently only available from the Plone
+    CVS HEAD.)
+
+Read the Source
+
+    Amen. Read 'functional.py' and 'sandbox.py' if you want to know
+    what's going on behind the scenes.
+
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/HOWTO.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/HOWTO.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/HOWTO.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,238 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+ZopeTestCase Mini How-To
+
+    (Still-terse-but-the-Wiki-has-more Version 0.3)
+
+Introduction
+
+    *ZopeTestCase* is an attempt to make writing unit and regression tests 
+    in Zope a more rewarding experience. The package deals with all the plumbing
+    required, and leaves the programmer with writing the tests - and the tests only. 
+    It is built on PyUnit and the Testing package coming with Zope.
+
+    ZopeTestCase provides a Zope sandbox to every test. This sandbox comes fully
+    equipped with ZODB connection, root application object, and REQUEST.
+    When writing tests you have complete control over Zope, the way you have from a Python 
+    product. The framework is easily customized and can be adapted to virtually every 
+    conceivable test situation.
+
+    What you can test with ZTC
+
+    - Basically everything. Including security, transactions, sessions, web access, ...
+
+    What you cannot test with ZTC
+
+    - Web forms and user interaction. This should be left to FunctionalTests.
+
+Prereqs
+
+    For an excellent introduction to unit testing read chapter 11 of Mark Pilgrim's
+    "Dive Into Python.":http://diveintopython.org/unit_testing/index.html
+
+    Read about what unit tests are and what they are not in Chris McDonough's
+    "Unit Testing Zope.":http://zope.org/Members/mcdonc/PDG/UnitTesting 
+
+    Download and install "ZopeTestCase.":http://zope.org/Members/shh/ZopeTestCase 
+    Be prepared to read the source files, especially the example tests. And the README of course.
+
+What You Get
+
+    The ZopeTestCase package consists of the following components:
+
+    framework.py
+
+          This file must be copied into the individual 'tests' directories (See the README for more). 
+          It locates the Testing and ZopeTestCase packages, detects INSTANCE_HOME installations, 
+          and determines the 'custom_zodb.py' file used to set up the test ZODB. 'framework.py' is 
+          included first thing in every test module.
+
+    ZopeLite.py
+
+          This module is designed to work like the 'Zope' module, but to load a lot faster.
+          The speed-up is achieved by not installing Zope products and help files. If your
+          tests require a product to be installed (most don't actually), you must install
+          it yourself using the 'installProduct' method. 'ZopeLite' also provides a 
+          'hasProduct' method, that allows to test whether a product can be found along the 
+          products path, i.e. whether it is available to the test instance.
+
+    ZopeTestCase.py
+
+          This module provides the 'ZopeTestCase' class, which is used as a base class for
+          all test cases. 
+          
+          A ZopeTestCase sets up a default fixture in the newly started Zope instance.
+          This fixture consist of a folder, a userfolder inside that folder, and a
+          user created in that userfolder. The user's role is configured at folder level
+          and populated with default permissions. Finally, the user is logged in.
+
+          This way, once a test runs, a complete and sane Zope "toy" environment is
+          already in place.
+
+          The fixture is destroyed during the tearDown phase and can be switched off 
+          entirely by setting '_setup_fixture=0' in your test case. 
+
+          The ZopeTestCase class provides a number of hooks that allow you to 
+          adapt the fixture to your needs:
+
+          - **'afterSetUp'** is called after the default fixture has been set up. Override this 
+            method to add the objects you intend to test. 
+            
+            *This is clearly the most useful hook.  You may ignore the remaining hooks until you 
+            really need them, if ever.*
+
+          - **'beforeTearDown'** is called at the start of the tearDown phase. The fixture
+            is still in place at this point.
+
+          - **'afterClear'** is called after the fixture has been destroyed. Note that this
+            may occur during setUp *and* tearDown. This method must not throw an exception 
+            even when called repeatedly.
+
+          Hooks for controlling transactions:
+
+          - **'beforeSetUp'** is called before the ZODB connection is opened, at the start of setUp. 
+             The default behaviour of this hook is to call 'get_transaction().begin()'. 
+             You will rarely want to override this.
+
+          - **'beforeClose'** is called before the ZODB connection is closed, at the end of
+             tearDown. By default this method calls 'get_transaction().abort()' to discard
+             any changes made by the test. In some situations you may need to override 
+             this hook and commit the transaction instead. Make sure you really know what 
+             you are doing though.
+
+    utils.py
+
+          Provides utility methods to extend the test environment:
+
+          - **'setupCoreSessions'** creates the Zope sessioning objects in the test ZODB.
+
+          - **'setupZGlobals'** creates the ZGlobals BTree required by ZClasses.
+
+          - **'setupSiteErrorLog'** creates the error_log object required by ZPublisher.
+
+          - **'startZServer'** starts a ZServer thread on the local host. Use this if the
+            objects you test require URL access to Zope.
+
+          - **'importObjectFromFile'** imports a (.zexp) file into a specified container.
+            Handy for adding "prerolled" components to the sandbox.
+
+          These methods must be run at module level. Do not call them from 'afterSetUp' or from tests!
+
+Writing Tests
+
+    Generally, writing tests with ZTC is no different from writing "ordinary" Zope code. A complete
+    Zope environment is made available to every test. The only real difference is that it is not 
+    driven by ZServer + ZPublisher but by the PyUnit TestRunner.
+    
+    Inside a ZopeTestCase the root application object can be accessed as 'self.app'. 
+    The folder serving as the fixture is available as 'self.folder'. The REQUEST is 
+    reachable as 'self.app.REQUEST'.
+
+    1. In your test module create a test case derived from 'ZopeTestCase'.
+
+    2. Override 'afterSetUp' to add your own objects to 'self.folder'.
+
+    3. Write one or more tests exercising these objects.
+
+    How to setup and run your tests is covered in detail by the 
+    "README":http://zope.org/Members/shh/ZopeTestCase/README
+
+    The easiest way to start with your own tests is to copy the skeleton test 
+    'testSkeleton.py', and take it from there.
+
+    Note that tests are written in unrestricted Python and are not affected by Zope's 
+    security policy. To test the security of individual methods or objects, you must invoke 
+    'restrictedTraverse' or 'validateValue' explicitly!
+
+    A simple test may look like::
+
+      from Testing import ZopeTestCase
+      from AccessControl import Unauthorized
+
+      class ExampleTest(ZopeTestCase.ZopeTestCase):
+
+          def afterSetUp(self):
+              # Add objects to the workarea
+              self.folder.addDTMLMethod('doc', file='foo')
+
+          def testDocument(self):
+              self.assertEqual(self.folder.doc(), 'foo')
+
+          def testEditDocument(self):
+              self.folder.doc.manage_edit('bar', '')
+              self.assertEqual(self.folder.doc(), 'bar')
+
+          def testAccessDocument(self):
+              self.folder.doc.manage_permission('View', ['Manager'])
+              self.assertRaises(Unauthorized, 
+                                self.folder.restrictedTraverse, 'doc')
+
+    Read the Examples
+
+        Please study the example tests for more:
+
+        - **'testSkeleton.py'** represents the simplest possible ZopeTestCase. The module contains all
+          the plumbing required. It is recommended that you copy this file to start your own tests.
+
+        - **'testPythonScript.py'** tests a PythonScript object in the default fixture. 
+          It demonstrates how to manipulate the test user's roles and permissions and how
+          security is validated.
+
+        - **'testShoppingCart.py'** tests the ShoppingCart example. This test
+          uses Sessions and shows how to test a TTW Zope application.
+
+        - **'testFunctional.py'** demonstrates the new functional testing features.
+          Tests may call 'self.publish()' to simulate URL calls to the ZPublisher.
+
+        - **'testWebserver.py'** starts up an HTTP ZServer thread and tests URL access to it. This 
+          test is an example for explicit transaction handling and shows how to use ZODB 
+          sandboxes to further increase isolation between individual tests.
+
+        - **'testZopeTestCase.py'** tests the ZopeTestCase class itself. May be of interest to the
+          investigative types.
+
+        - **'testPortalTestCase.py'** contains an equivalent test suite for the PortalTestCase 
+          base class.
+
+        - **'testZODBCompat.py'** tests various aspects of ZODB behavior in a ZopeTestCase environment.
+          Shows things like cut/paste, import/export, and that _v_ and _p_ variables survive 
+          transaction boundaries.
+
+    Read the Source
+
+        The source files are well documented and an overall worthy read &lt;wink&gt;:
+
+        - The ZopeTestCase class is defined in file 'ZopeTestCase.py'.
+
+        - The interfaces implemented by this class are documented in 'doc/IZopeTestCase.py'.
+
+        - All names exported by the ZopeTestCase package are listed in '__init__.py'. 
+
+Resources
+
+    A Wiki with all the documentation:
+    "ZopeTestCaseWiki":http://zope.org/Members/shh/ZopeTestCaseWiki
+
+    Slides of my talk at EuroPython 2003:
+    "UnitTestingZope":http://zope.org/Members/shh/UnitTestingZope.pdf
+
+    A testrunner with INSTANCE_HOME support: 
+    "TestRunner":http://zope.org/Members/shh/TestRunner
+
+Related
+
+    "PyUnit":http://pyunit.sf.net (Steve Purcell)
+  
+    "WebUnit":http://mechanicalcat.net/tech/webunit (Richard Jones) and 
+    "WebUnit":http://webunit.sf.net (Steve Purcell)
+  
+    "ZUnit":http://zope.org/Members/lalo/ZUnit (Lalo Martins)
+  
+    "FunctionalTests":http://zope.org/Members/tseaver/FunctionalTests (Tres Seaver)
+  
+    "WebsiteLoadTest":http://zope.org/Members/ajung/WebsiteLoadTest (Andreas Jung)
+
+Contact
+
+    Feel free to send bug reports, feature requests, etc to stefan at epy.co.at.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/INSTALL.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/INSTALL.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/INSTALL.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,27 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+Installation Instructions for ZopeTestCase
+
+    Requires Python 2.1 and Zope 2.5 or higher
+
+    Upgrade Notice: Please remove an existing installation
+    of ZopeTestCase before installing version 0.9.0!
+
+    1. Extract the tarball into the 'lib/python/Testing'
+       directory of your Zope installation.
+
+    2. Cd into the ZopeTestCase directory and execute 
+       'python runalltests.py' to test the package and to 
+       make sure all modules get compiled.
+
+       You must use the same Python that is running your 
+       Zope here. On Windows this may for example be::
+
+         "C:\Program Files\Zope\bin\python.exe" runalltests.py
+
+    3. See the HOWTO for the big picture, the README for 
+       getting started with your own tests.
+
+    Visit the "ZopeTestCaseWiki":http://zope.org/Members/shh/ZopeTestCaseWiki
+    for additional documentation.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/IZopeTestCase.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/IZopeTestCase.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/IZopeTestCase.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,108 @@
+from Interface import Interface
+
+# $Id: IZopeTestCase.py,v 1.13 2004/02/21 18:54:38 shh42 Exp $
+
+
+#
+#   ZopeTestCase.__implements__ = (
+#           IZopeTestCase, ISimpleSecurity, IExtensibleSecurity)
+#
+#   PortalTestCase.__implements__ = (
+#           IPortalTestCase, ISimpleSecurity, IExtensibleSecurity)
+#
+
+
+class ISimpleSecurity(Interface):
+
+    def setRoles(roles):
+        '''Changes the user's roles.'''
+
+    def setPermissions(permissions):
+        '''Changes the user's permissions.'''
+
+    def login():
+        '''Logs in.'''
+
+    def logout():
+        '''Logs out.'''
+
+
+class IExtensibleSecurity(Interface):
+
+    def setRoles(roles, name):
+        '''Changes the roles assigned to a user.'''
+
+    def setPermissions(permissions, role):
+        '''Changes the permissions assigned to a role.'''
+
+    def login(name):
+        '''Logs in as the specified user.'''
+
+    def logout():
+        '''Logs out.'''
+
+
+class IZopeTestCase(Interface):
+
+    def afterSetUp():
+        '''Called after setUp() has completed. This is
+           far and away the most useful hook.
+        '''
+
+    def beforeTearDown():
+        '''Called before tearDown() is executed.
+           Note that tearDown() is not called if
+           setUp() fails.
+        '''
+
+    def afterClear():
+        '''Called after the fixture has been cleared.
+           Note that this is done during setUp() *and*
+           tearDown().
+        '''
+
+    def beforeSetUp():
+        '''Called before the ZODB connection is opened,
+           at the start of setUp(). By default begins a
+           new transaction.
+        '''
+
+    def beforeClose():
+        '''Called before the ZODB connection is closed,
+           at the end of tearDown(). By default aborts
+           the transaction.
+        '''
+
+
+class IPortalTestCase(IZopeTestCase):
+
+    def getPortal():
+        '''Returns the portal object for use by the setup 
+           code. Will typically be overridden by subclasses
+           to return the object serving as the portal.
+        '''
+
+    def createMemberarea(member_id):
+        '''Creates a memberarea for the specified member.
+           Subclasses may override to provide a customized 
+           or more lightweight version of the memberarea.
+        '''
+
+
+class IProfiled(Interface):
+
+    def runcall(func, *args, **kw):
+        '''Allows to run a function under profiler control
+           adding to the accumulated profiler statistics.
+        '''
+
+
+class IFunctional(Interface):
+
+    def publish(path, basic=None, env=None, extra=None, request_method='GET'):
+        '''Publishes the object at 'path' returning an
+           extended response object. The path may contain 
+           a query string.
+        '''
+
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PROFILER.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PROFILER.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PROFILER.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,19 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+Profiler Readme
+
+    Since version 0.9.0 all xTestCases are profiler aware by default. 
+
+    You can run your tests under profiler control like this::
+
+        python testSomething.py profile
+
+    If you want to profile fixture creation or destruction type one of::
+
+        python testSomething.py profile-setup
+        python testSomething.py profile-teardown
+
+    Profiler statistics will be printed after the test results.
+
+    See the API reference for more on the 'profiler' module.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PortalTestCase.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PortalTestCase.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/PortalTestCase.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,93 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+PortalTestCase Readme
+
+    The PortalTestCase class is a close relative of the ZopeTestCase
+    class. It was devised at the Plone Castle Sprint to form the base of an
+    integration testing framework for Plone 2.0. Thanks to Gidon Friedman 
+    and Godefroid Chapelle for their collaboration.
+
+
+Much of what is true for ZopeTestCase is true for PortalTestCase as well:
+
+    * PortalTestCase handles the ZODB connection, transaction, and 
+      application object; and it provides a REQUEST.
+    
+    * PortalTestCase sets up a user folder and a user.
+
+    * PortalTestCase provides the same hooks as ZopeTestCase.
+    
+    * PortalTestCase implements the same security interfaces as 
+      ZopeTestCase.
+
+
+What's different?
+
+    * PortalTestCase is designed for testing CMF-based applications.
+
+    * The fixture is slightly more complex, consisting of a portal object,
+      a userfolder + user, and the user's memberarea.
+
+    * For flexibility, the portal is *not* created by the base class but 
+      must be provided by the user (typically a derived xTestCase) of the
+      PortalTestCase base class.
+    
+    * Subclasses will have to override 'getPortal' to return the object 
+      serving as the portal.
+
+    * The portal will however be configured by the machinery which means
+      creating a user and a fresh memberarea for every test.
+
+    * Subclasses may override 'createMemberarea' to provide customized
+      and/or more lightweight memberareas to the tests. This can improve 
+      performance quite significantly.
+    
+
+Feature Comparison:
+
+    ZopeTestCase
+
+        * 1 user + 1 role
+
+        * Folder contains user folder and role definition
+
+        * Folder also serves as workarea
+
+        * User is logged in
+
+        * Provides attributes::
+
+          self.app
+          self.app.REQUEST
+          self.folder
+          self.folder.acl_users
+
+    PortalTestCase
+
+        * 1 user + 'Member' role
+
+        * Portal contains user folder and role definition
+
+        * User's home folder serves as workarea
+
+        * User is logged in
+
+        * Provides attributes::
+
+          self.app
+          self.app.REQUEST
+          self.portal
+          self.portal.acl_users
+          self.folder
+
+
+Read the Source
+
+    As always, I recommend to look at the source code of both 
+    'ZopeTestCase.py' and 'PortalTestCase.py' for all the details you may need.
+    Interface documentation can be found in 'doc/IZopeTestCase.py'.
+
+    The test framework shipping with Plone 2.0 is a good example of how the 
+    PortalTestCase class can be put to use.
+
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/README.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/README.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/README.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,89 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+ZopeTestCase Readme
+
+    The ZopeTestCase package has been developed in the hope that it will make
+    testing Zope packages more convenient. It has features to support various 
+    scenarios from unit-testing individual components inside a "toy" environment 
+    to running regression tests against live ZEO servers.
+
+    To add a test suite to a Zope package:
+
+        1. Make a 'tests' subdirectory.
+
+        2. Create an (empty) '__init__.py' in 'tests' to make it a package.
+
+        3. Copy 'framework.py' from the 'ZopeTestCase' package into 'tests'.
+
+    Once a test suite has been set up, you can add test modules:
+
+        1. Create a file with a name matching 'test*.py'.
+
+        2. Import the 'ZopeTestCase' package as in 'from Testing import ZopeTestCase'
+           and define one or more subclasses of 'ZopeTestCase.ZopeTestCase'.
+
+        3. Define methods for the test classes.  Each method's name must start
+           with 'test'.  It should test one small case, preferably using a PyUnit 
+           assertion method.  Here's a minimal example::
+
+             class ExampleTest(ZopeTestCase.ZopeTestCase):
+                 def testAddition(self):
+                     self.assertEqual(1+1, 2)
+
+        4. You can add 'afterSetUp' and 'beforeTearDown' methods that are automatically
+           called after the fixture has been set up and before the fixture is destroyed
+           respectively. 
+
+        5. Follow the instructions in 'framework.py' about adding lines to the
+           top and bottom of the file.
+
+    Now you can run the test as 'python path/to/tests/testName.py', or
+    simply go to the 'tests' directory and type 'python testName.py'.
+
+    Note that there is a skeleton test suite named 'testSkeleton.py' that you 
+    may copy into your 'tests' directory and take it from there.
+
+    Note also that when the tests are run in an INSTANCE_HOME installation of 
+    Zope, you must set the SOFTWARE_HOME environment variable for the 'Testing' 
+    and 'ZopeTestCase' packages to be found.
+
+    See the sample tests in the 'ZopeTestCase' directory for details on writing 
+    your own tests.
+
+framework.py
+
+    1. Uses SOFTWARE_HOME (if set) to locate the Testing package.
+
+    2. Detects and handles INSTANCE_HOME installations of Zope. Please
+       see ENVIRONMENT.txt for the assumptions ZTC makes about its
+       environment.
+
+    3. Supports setting up a ZODB from a 'custom_zodb.py' file in
+       the 'tests' directory.
+
+    4. Allows to connect to a running ZEO server by setting the
+       ZEO_INSTANCE_HOME environment variable.
+
+testrunner.py
+
+    Alternatively, you may use Zope's testrunner utility to run your tests 
+    ('testrunner.py' can be found in the 'utilities' directory of your Zope 
+    installation). If you do so, you will have to define a 'test_suite' method 
+    in your modules (see examples). 
+
+    There is no need to set SOFTWARE_HOME when using the testrunner but you may
+    have to provide the -i flag when testing in an INSTANCE_HOME setup.
+
+    Example: 'python /path/to/Zope/utilities/testrunner.py -q -i -a'
+
+    If your testrunner does not appear to support the -i flag get the one from
+    'http://zope.org/Members/shh/TestRunner'
+
+    Note that the 'custom_zodb.py' magic (3. + 4.) is not available when using
+    the testrunner.
+
+    If you have tests that should not be picked up by the testrunner, make a
+    'test_suite' method that returns an empty TestSuite.
+
+    Note that in Zope 2.7 the testrunner lives in '/path/to/Zope/bin'.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/SECURITY.stx
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/SECURITY.stx	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/SECURITY.stx	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,57 @@
+<style type="text/css"> <!-- li { margin: 1em } --> </style>
+
+Default Fixture
+
+    - **'self.app'** is the root application object of the test ZODB (contains Control_Panel, ...)
+     
+      Note that a ZODB connections has already been opened and a transaction begun at this point.
+
+    - **'self.app.REQUEST'** is the request object. Note that the REQUEST is rather minimal because
+      ZPublisher is not involved when running tests, and as such many REQUEST variables are never 
+      set. Feel free to add to the REQUEST whatever your tests require.
+
+    - **'self.folder'** is the work area. This folder will be created anew for each test and thrown 
+      away once the test has finished. The name of the folder is 'test_folder_1_'. You should 
+      use the 'ZopeTestCase.folder_name' constant when you need the folder's name. 'self.folder' is a 
+      reference to the object at 'self.app[folder_name]'.
+
+      A default role definition ('ZopeTestCase.user_role') is added to the folder, and a list of 
+      permissions ('ZopeTestCase.standard_permissions') is assigned to the role. 
+
+    - **'self.folder.acl_users'** is the user folder providing a security context to the work area.
+
+      A default user account is added to the user folder with name 'test_user_1_' and password 'secret'. 
+      You should use the 'ZopeTestCase.user_name' constant when you need the user's name. 
+      
+      The default user has a single role, 'ZopeTestCase.user_role'.
+
+    At the end of the setup process the default user is logged in, and the 'afterSetUp' hook is called.
+
+Security API
+
+    - **'self.setRoles(roles, name=user_name)'** allows to change the roles assigned to a user.
+      If the 'name' argument is omitted, changes the roles of the default user.
+
+    - **'self.setPermissions(permissions, role=user_role)'** allows to change the permissions
+      assigned to a role. If the 'role' argument is omitted, changes the permissions of the
+      default role.
+
+    - **'self.login(name=user_name)'** allows to log in as a specified user.
+      If the 'name' argument is omitted, logs in as the default user.
+
+    - **'self.logout()'** allows to log out and become 'Anonymous User'.
+
+Testing Security
+
+    - **'ob.restrictedTraverse(attr)'** is a simple way to check whether the currently logged in user is
+      allowed to access attribute 'attr' of object 'ob'.
+
+    - **'getSecurityManager().validate(None, ob, attr, ob.attr)'** uses the security manager to do the same.
+      The convenience method 'getSecurityManager().validateValue(ob.attr)' will no longer work 
+      in Zope 2.8 (from what I hear).
+
+    Also see the 'testPythonScript.py' example test.
+
+    Note that you have the entire Zope security API at your disposal to further refine your fixture.
+    E.g. to add another user call 'self.folder.acl_users.userFolderAddUser("user2", "secret", ["role2"], [])'.
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/TIMELINES.txt
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/TIMELINES.txt	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/TIMELINES.txt	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,85 @@
+Timelines
+
+When the skeleton test is run by typing 'python testSkeleton.py', it
+
+    1. includes file framework.py
+
+        1.1 locates and imports the Testing package by means of
+            - SOFTWARE_HOME environment variable
+            - auto-detection
+
+        1.2 locates and includes file ztc_common.py
+
+            1.2.1 sets up the instance environment by means of
+                - ZEO_INSTANCE_HOME environment variable
+                - INSTANCE_HOME environment variable
+                - auto-detection
+                - optional custom_zodb.py
+    
+    2. imports module Testing.ZopeTestCase
+
+        2.1 imports module Testing.ZopeTestCase.ZopeLite
+
+            2.1.1 imports module ZODB
+            2.1.2 imports module Globals
+            2.1.3 patches OFS.Application to not auto-install all products
+            2.1.4 patches App.ProductContext to not auto-install all help files
+            2.1.5 imports module Zope
+            2.1.6 starts Zope
+            2.1.7 installs product PluginIndexes
+            2.1.8 installs product OFSP
+
+        2.2 imports module Testing.ZopeTestCase.ZopeTestCase
+
+            2.2.1 creates the connection registry
+            2.2.2 defines class ZopeTestCase(unittest.TestCase)
+
+    3. installs product SomeProduct
+
+    4. defines class TestSomeProduct(ZopeTestCase.ZopeTestCase)
+
+    5. executes method framework()
+
+        5.1 collects all TestCase-derived classes in a test suite
+        5.2 runs the test suite using the TextTestRunner
+
+
+When a ZopeTestCase test method is run, it
+
+    1. executes setUp()
+
+        1.1 calls the beforeSetUp() hook
+
+            1.1.1 by default begins a new transaction
+        
+        1.2 opens a ZODB connection and retrieves the root application object
+
+        1.3 sets up the default fixture
+        
+            1.3.1 creates a Folder object in the root
+            1.3.2 creates a UserFolder object in the folder
+            1.3.3 creates a default user in the user folder
+            1.3.4 logs in as the default user
+
+        1.4 calls the afterSetUp() hook
+
+    2. executes the test method
+
+    3. executes tearDown()
+
+        3.1 calls the beforeTearDown() hook
+
+        3.2 calls the beforeClose() hook
+
+            3.2.1 by default aborts the transaction
+
+        3.3 clears the fixture *)
+
+            3.1.1 aborts all transactions
+            3.1.2 closes all ZODB connections 
+            3.1.3 logs out
+            3.1.4 calls the afterClear() hook
+
+
+*) Note: The fixture is also cleared if an error occurs during setUp()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/doc/VERSION.txt
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/doc/VERSION.txt	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/doc/VERSION.txt	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,6 @@
+ZopeTestCase 0.9.0
+(c) 2002-2004, Stefan H. Holek, stefan at epy.co.at 
+http://zope.org/Members/shh/ZopeTestCase
+License: ZPL
+Zope: 2.5-2.7
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/framework.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/framework.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/framework.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# ZopeTestCase 
+#
+# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
+#
+# This version of framework.py will use the SOFTWARE_HOME
+# environment variable to locate Zope and the Testing package.
+#
+# If the tests are run in an INSTANCE_HOME installation of Zope,
+# Products.__path__ and sys.path with be adjusted to include the
+# instance's Products and lib/python directories respectively.
+#
+# If you explicitly set INSTANCE_HOME prior to running the tests,
+# auto-detection is disabled and the specified path will be used 
+# instead.
+#
+# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
+# will be adjusted to use it.
+#
+# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup 
+# is assumed, and you can attach to a running ZEO server (via the 
+# instance's custom_zodb.py).
+#
+##############################################################################
+#
+# The following code should be at the top of every test module:
+#
+# import os, sys
+# if __name__ == '__main__':
+#     execfile(os.path.join(sys.path[0], 'framework.py'))
+#
+# ...and the following at the bottom:
+#
+# if __name__ == '__main__':
+#     framework()
+#
+##############################################################################
+
+__version__ = '0.2.3'
+
+# Save start state
+#
+__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
+__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
+
+if __SOFTWARE_HOME.endswith(os.sep):
+    __SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
+
+if __INSTANCE_HOME.endswith(os.sep):
+    __INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
+
+# Find and import the Testing package
+#
+if not sys.modules.has_key('Testing'):
+    p0 = sys.path[0]
+    if p0 and __name__ == '__main__':
+        os.chdir(p0)
+        p0 = ''
+    s = __SOFTWARE_HOME
+    p = d = s and s or os.getcwd()
+    while d:
+        if os.path.isdir(os.path.join(p, 'Testing')):
+            zope_home = os.path.dirname(os.path.dirname(p))
+            sys.path[:1] = [p0, p, zope_home]
+            break
+        p, d = s and ('','') or os.path.split(p)
+    else:
+        print 'Unable to locate Testing package.',
+        print 'You might need to set SOFTWARE_HOME.'
+        sys.exit(1)
+
+import Testing, unittest
+execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
+
+# Include ZopeTestCase support
+#
+if 1:   # Create a new scope
+
+    p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
+
+    if not os.path.isdir(p):
+        print 'Unable to locate ZopeTestCase package.',
+        print 'You might need to install ZopeTestCase.'
+        sys.exit(1)
+
+    ztc_common = 'ztc_common.py'
+    ztc_common_global = os.path.join(p, ztc_common)
+
+    f = 0
+    if os.path.exists(ztc_common_global):
+        execfile(ztc_common_global)
+        f = 1
+    if os.path.exists(ztc_common):
+        execfile(ztc_common)
+        f = 1
+
+    if not f:
+        print 'Unable to locate %s.' % ztc_common
+        sys.exit(1)
+
+# Debug
+#
+print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
+print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
+sys.stdout.flush()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/functional.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/functional.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/functional.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,87 @@
+#
+# Support for functional unit testing in ZTC
+# After Marius Gedmina's functional.py module for Zope3.
+#
+
+# $Id: functional.py,v 1.2 2004/01/14 12:41:32 shh42 Exp $
+
+import sys, re, base64
+import sandbox
+
+
+class Functional(sandbox.Sandboxed):
+    '''Derive from this class and an xTestCase to get functional 
+       testing support::
+    
+           class MyTest(Functional, ZopeTestCase):
+               ...
+    '''
+
+    def publish(self, path, basic=None, env=None, extra=None, request_method='GET'):
+        '''Publishes the object at 'path' returning an enhanced response object.'''
+
+        from StringIO import StringIO
+        from ZPublisher.Response import Response
+        from ZPublisher.Test import publish_module
+
+        # Commit the sandbox for good measure
+        get_transaction().commit()
+
+        if env is None: 
+            env = {}
+        if extra is None: 
+            extra = {}
+
+        request = self.app.REQUEST
+
+        env['SERVER_NAME'] = request['SERVER_NAME']
+        env['SERVER_PORT'] = request['SERVER_PORT']
+        env['REQUEST_METHOD'] = request_method
+
+        p = path.split('?')
+        if len(p) == 1: 
+            env['PATH_INFO'] = p[0]
+        elif len(p) == 2: 
+            [env['PATH_INFO'], env['QUERY_STRING']] = p
+        else: 
+            raise TypeError, ''
+
+        if basic:
+            env['HTTP_AUTHORIZATION'] = "Basic %s" % base64.encodestring(basic)
+
+        outstream = StringIO()
+        response = Response(stdout=outstream, stderr=sys.stderr) 
+
+        publish_module('Zope', response=response, environ=env, extra=extra)
+
+        return ResponseWrapper(response, outstream, path)
+
+
+class ResponseWrapper:
+    '''Acts like a response object with some additional introspective methods.'''
+
+    _bodyre = re.compile('^$^\n(.*)', re.MULTILINE | re.DOTALL)
+
+    def __init__(self, response, outstream, path):
+        self._response = response
+        self._outstream = outstream
+        self._path = path
+
+    def getOutput(self):
+        '''Returns the complete output, headers and all.'''
+        return self._outstream.getvalue()
+
+    def getBody(self):
+        '''Returns the page body, i.e. the output par headers.'''
+        body = self._bodyre.search(self.getOutput())
+        if body is not None:
+            body = body.group(1)
+        return body
+
+    def getPath(self):
+        '''Returns the path used by the request.'''
+        return self._path
+
+    def __getattr__(self, name):
+        return getattr(self._response, name)
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/profiler.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/profiler.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/profiler.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,99 @@
+#
+# Profiling support for ZTC
+#
+
+# $Id: profiler.py,v 1.2 2004/01/12 18:45:42 shh42 Exp $
+
+import os, sys
+
+from profile import Profile
+from pstats import Stats
+
+_profile = Profile()
+_have_stats = 0
+
+limit = ('.py:', 200)
+sort = ('cumulative', 'time', 'pcalls')
+strip_dirs = 1
+
+
+def runcall(*args, **kw):
+    global _have_stats
+    _have_stats = 1
+    return apply(_profile.runcall, args, kw)
+
+
+def print_stats(limit=limit, sort=sort, strip_dirs=strip_dirs):
+    if _have_stats:
+        stats = Stats(_profile)
+        if strip_dirs:
+            stats.strip_dirs()
+        apply(stats.sort_stats, sort)
+        apply(stats.print_stats, limit)
+
+
+def dump_stats(filename):
+    if _have_stats:
+        _profile.dump_stats(filename)
+    
+
+class Profiled:
+    '''Derive from this class and an xTestCase to get profiling support::
+
+           class MyTest(Profiled, ZopeTestCase):
+               ...
+
+       Then run the test module by typing::
+
+           $ python testSomething.py profile
+
+       Profiler statistics will be printed after the test results.
+    '''
+
+    def runcall(self, *args, **kw):
+        return apply(runcall, args, kw)
+
+    def __call__(self, result=None):
+        if result is None: result = self.defaultTestResult()
+        result.startTest(self)
+        testMethod = getattr(self, self._TestCase__testMethodName)
+        try:
+            try:
+                if int(os.environ.get('PROFILE_SETUP', 0)):
+                    self.runcall(self.setUp)
+                else:
+                    self.setUp()
+            except KeyboardInterrupt:
+                raise
+            except:
+                result.addError(self, self._TestCase__exc_info())
+                return
+
+            ok = 0
+            try:
+                if int(os.environ.get('PROFILE_TESTS', 0)):
+                    self.runcall(testMethod)
+                else:
+                    testMethod()
+                ok = 1
+            except self.failureException:
+                result.addFailure(self, self._TestCase__exc_info())
+            except KeyboardInterrupt:
+                raise
+            except:
+                result.addError(self, self._TestCase__exc_info())
+
+            try:
+                if int(os.environ.get('PROFILE_TEARDOWN', 0)):
+                    self.runcall(self.tearDown)
+                else:
+                    self.tearDown()
+            except KeyboardInterrupt:
+                raise
+            except:
+                result.addError(self, self._TestCase__exc_info())
+                ok = 0
+            if ok: result.addSuccess(self)
+        finally:
+            result.stopTest(self)
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/runalltests.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/runalltests.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/runalltests.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,29 @@
+#
+# Runs all tests in the current directory
+#
+# Execute like:
+#   python runalltests.py
+#
+# Alternatively use the testrunner: 
+#   python /path/to/Zope/utilities/testrunner.py -qa
+#
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py')) 
+
+import unittest
+TestRunner = unittest.TextTestRunner
+suite = unittest.TestSuite()
+
+tests = os.listdir(os.curdir)
+tests = [n[:-3] for n in tests if n.startswith('test') and n.endswith('.py')]
+
+for test in tests:
+    m = __import__(test)
+    if hasattr(m, 'test_suite'):
+        suite.addTest(m.test_suite())
+
+if __name__ == '__main__':
+    TestRunner().run(suite)
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/sandbox.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/sandbox.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/sandbox.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,60 @@
+#
+# Support for ZODB sandboxes in ZTC
+#
+
+# $Id: sandbox.py,v 1.1 2004/01/09 15:03:04 shh42 Exp $
+
+import ZopeLite as Zope
+import utils
+
+
+class Sandboxed:
+    '''Derive from this class and an xTestCase to make each test
+       run in its own ZODB sandbox::
+
+           class MyTest(Sandboxed, ZopeTestCase):
+               ...
+    '''
+
+    def _app(self):
+        '''Returns the app object for a test.'''
+        app = Zope.app(Zope.sandbox().open())
+        AppZapper().set(app)
+        return utils.makerequest(app)
+
+    def _close(self):
+        '''Clears the transaction and the AppZapper.'''
+        get_transaction().abort()
+        AppZapper().clear()
+
+
+class AppZapper:
+    '''Application object share point'''
+
+    __shared_state = {'_app': None}
+
+    def __init__(self):
+        self.__dict__ = self.__shared_state
+
+    def set(self, app):
+        self._app = app
+
+    def clear(self):
+        self._app = None
+
+    def app(self):
+        return self._app
+
+
+def __bobo_traverse__(self, REQUEST=None, name=None):
+    '''Makes ZPublisher.publish() use the current app object.'''
+    app = AppZapper().app()
+    if app is not None:
+        return app
+    return self.__old_bobo_traverse__(REQUEST, name)
+
+
+from ZODB.ZApplication import ZApplicationWrapper
+ZApplicationWrapper.__old_bobo_traverse__ = ZApplicationWrapper.__bobo_traverse__
+ZApplicationWrapper.__bobo_traverse__ = __bobo_traverse__
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testFunctional.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testFunctional.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testFunctional.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,80 @@
+#
+# Example functional ZopeTestCase
+#
+
+# $Id: testFunctional.py,v 1.5 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+
+ZopeTestCase.installProduct('PythonScripts')
+
+
+class TestZPublication(ZopeTestCase.Functional, ZopeTestCase.ZopeTestCase):
+
+    def afterSetUp(self):
+        self.folder_path = self.folder.absolute_url(1)
+        self.basic_auth = '%s:secret' % ZopeTestCase.user_name
+
+        self.folder.addDTMLMethod('index_html', file='foo')
+
+        dispatcher = self.folder.manage_addProduct['PythonScripts']
+        dispatcher.manage_addPythonScript('script')
+        self.folder.script.ZPythonScript_edit('a=0', 'return a+1')
+
+        self.folder.manage_addFolder('object', '')
+        self.folder.addDTMLMethod('change_title', 
+            file='''<dtml-call "manage_changeProperties(title=REQUEST.get('title'))">''')
+
+    def testPublishDocument(self):
+        response = self.publish('/%s/index_html' % self.folder_path)
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getBody(), 'foo')
+
+    def testPublishScript(self):
+        response = self.publish('/%s/script' % self.folder_path)
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getBody(), '1')
+
+    def testPublishScriptWithArgument(self):
+        response = self.publish('/%s/script?a:int=2' % self.folder_path)
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getBody(), '3')
+
+    def testServerError(self):
+        response = self.publish('/%s/script?a=2' % self.folder_path)
+        self.assertEqual(response.getStatus(), 500)
+
+    def testUnauthorized(self):
+        self.folder.index_html.manage_permission('View', ['Owner'])
+        response = self.publish('/%s/index_html' % self.folder_path)
+        self.assertEqual(response.getStatus(), 401)
+
+    def testBasicAuthentication(self):
+        self.folder.index_html.manage_permission('View', ['Owner'])
+        response = self.publish('/%s/index_html' 
+                                % self.folder_path, self.basic_auth)
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(response.getBody(), 'foo')
+
+    def testModifyObject(self):
+        from AccessControl.Permissions import manage_properties
+        self.setPermissions([manage_properties])
+        response = self.publish('/%s/object/change_title?title=Foo' 
+                                % self.folder_path, self.basic_auth)
+        self.assertEqual(response.getStatus(), 200)
+        self.assertEqual(self.folder.object.title_or_id(), 'Foo')
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestZPublication))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testPortalTestCase.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testPortalTestCase.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testPortalTestCase.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,411 @@
+#
+# Tests the PortalTestCase
+#
+# NOTE: This is *not* an example PortalTestCase. Do not
+# use this file as a blueprint for your own tests!
+#
+# See testPythonScript.py and testShoppingCart.py for
+# example test cases. See testSkeleton.py for a quick
+# way of getting started.
+#
+
+# $Id: testPortalTestCase.py,v 1.21 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+
+from Acquisition import aq_base
+from AccessControl import getSecurityManager
+from types import ListType
+
+portal_name = 'dummy_1_'
+user_name = ZopeTestCase.user_name
+
+
+def hasattr_(ob, attr):
+    return hasattr(aq_base(ob), attr)
+
+
+# Dummy portal
+from OFS.SimpleItem import SimpleItem
+from OFS.Folder import Folder       
+
+class DummyMembershipTool(SimpleItem):
+    id = 'portal_membership'                
+    def createMemberarea(self, member_id):      
+        portal = self.aq_inner.aq_parent            
+        portal.Members.manage_addFolder(member_id)          
+    def getHomeFolder(self, member_id):                             
+        portal = self.aq_inner.aq_parent
+        return portal.Members[member_id]
+                  
+class DummyPortal(Folder):
+    _v_skindata = None                
+    def __init__(self, id):
+        self.id = id
+        self._addRole('Member') 
+        self._setObject('portal_membership', DummyMembershipTool())
+        self.manage_addFolder('Members')
+    def setupCurrentSkin(self):
+        if self._v_skindata is None:
+            self._v_skindata = 'refreshed'
+
+
+class TestPortalTestCase(ZopeTestCase.PortalTestCase):
+    '''Tests the PortalTestCase.'''
+
+    _setUp = ZopeTestCase.PortalTestCase.setUp
+    _tearDown = ZopeTestCase.PortalTestCase.tearDown
+
+    def getPortal(self):
+        self.app._setObject(portal_name, DummyPortal(portal_name))
+        return self.app[portal_name]
+
+    def setUp(self):
+        # For this test case we *want* to start
+        # with an empty fixture.
+        self._called = []
+        # Implicitly aborts previous transaction
+        get_transaction().begin()
+
+    def beforeSetUp(self):
+        self._called.append('beforeSetUp')
+
+    def afterSetUp(self):
+        self._called.append('afterSetUp')
+
+    def beforeTearDown(self):
+        self._called.append('beforeTearDown')
+
+    def beforeClose(self):
+        self._called.append('beforeClose')
+
+    def afterClear(self):
+        self._called.append('afterClear')
+
+    def test_01_getPortal(self):
+        # Portal should be set up
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self.failUnless(hasattr_(self.app, portal_name))
+        self.failUnless(hasattr_(self.portal, 'Members'))
+        self.failUnless(hasattr_(self.portal, 'portal_membership'))
+        self.failUnless('Member' in self.portal.userdefined_roles())
+
+    def test_02_setupUserFolder(self):
+        # User folder should be set up.
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self.failIf(hasattr_(self.portal, 'acl_users'))
+        self._setupUserFolder()
+        self.failUnless(hasattr_(self.portal, 'acl_users'))
+        # Must not complain if UF already exists
+        self._setupUserFolder()
+
+    def test_03_setupUser(self):
+        # User should be set up
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        acl_user = self.portal.acl_users.getUserById(user_name)
+        self.failUnless(acl_user)
+        self.assertEqual(acl_user.getRoles(), ('Member', 'Authenticated'))
+        self.assertEqual(type(acl_user.roles), ListType)
+
+    def test_04_setupHomeFolder(self):
+        # User's home folder should be set up
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        self.login()
+        self._setupHomeFolder()
+        self.failUnless(hasattr_(self.portal.Members, user_name))
+        self.failIf(self.folder is None)
+        # Shut up deprecation warnings
+        try: owner_info = self.folder.getOwnerTuple()
+        except AttributeError:
+            owner_info = self.folder.getOwner(info=1)
+        self.assertEqual(owner_info, ([portal_name, 'acl_users'], user_name))
+
+    def test_05_refreshSkinData(self):
+        # The _v_skindata attribute should be refreshed
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self.assertEqual(self.portal._v_skindata, None)
+        self._refreshSkinData()
+        self.assertEqual(self.portal._v_skindata, 'refreshed')
+
+    def test_06_setRoles(self):
+        # Roles should be set for user
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        test_roles = ['Manager', 'Member']
+        test_roles.sort()
+        self.setRoles(test_roles)
+        acl_user = self.portal.acl_users.getUserById(user_name)
+        user_roles = list(acl_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_07_setRoles_2(self):
+        # Roles should be set for logged in user
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        self.login()
+        test_roles = ['Manager', 'Member']
+        test_roles.sort()
+        self.setRoles(test_roles)
+        auth_user = getSecurityManager().getUser()
+        user_roles = list(auth_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_08_setRoles_3(self):
+        # Roles should be set for a specified user
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self.portal.acl_users._doAddUser('test_user_2_', 'secret', [], [])
+        test_roles = ['Manager', 'Member']
+        test_roles.sort()
+        self.setRoles(test_roles, 'test_user_2_')
+        acl_user = self.portal.acl_users.getUserById('test_user_2_')
+        user_roles = list(acl_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_09_setPermissions(self):
+        # Permissions should be set for user
+        self.app = self._app()
+        self.portal = self.getPortal()
+        test_perms = ['Add Folders']
+        self.setPermissions(test_perms)
+        self.assertPermissionsOfRole(test_perms, 'Member')
+
+    def test_10_setPermissions_2(self):
+        # Permissions should be set for a specified role
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self.portal._addRole('test_role_2_')
+        test_perms = ['Add Folders']
+        self.assertPermissionsOfRole([], 'test_role_2_')
+        self.setPermissions(test_perms, 'test_role_2_')
+        self.assertPermissionsOfRole(test_perms, 'test_role_2_')
+
+    def test_11_login(self):
+        # User should be able to log in
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.login()
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, user_name)
+
+    def test_12_login_2(self):
+        # A specified user should be logged in
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self.portal.acl_users._doAddUser('test_user_2_', 'secret', [], [])
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.login('test_user_2_')
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, 'test_user_2_')
+
+    def test_13_login_3(self):
+        # Unknown user should raise AttributeError
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self.assertRaises(AttributeError, self.login, 'test_user_3_')
+
+    def test_14_logout(self):
+        # User should be able to log out
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        self.login()
+        self.logout()
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+
+    def test_15_clear(self):
+        # Everything should be removed
+        self.app = self._app()
+        self.portal = self.getPortal()
+        self._setupUserFolder()
+        self._setupUser()
+        self._setupHomeFolder()
+        self._clear(1)
+        # XXX: No more cleanups in _clear()
+        #self.failIf(self.portal.acl_users.getUserById(user_name))
+        #self.failIf(hasattr_(self.portal.Members, user_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['beforeClose', 'afterClear'])
+        # clear must not fail when called repeatedly
+        self._clear()
+
+    def test_16_setUp(self):
+        # Everything should be set up
+        self._setUp()
+        self.failUnless(hasattr_(self.app, portal_name))
+        self.failUnless(hasattr_(self.portal, 'acl_users'))
+        self.failUnless(hasattr_(self.portal, 'Members'))
+        self.failUnless(hasattr_(self.portal, 'portal_membership'))
+        self.failUnless('Member' in self.portal.userdefined_roles())
+        self.failUnless(hasattr_(self.portal.Members, user_name))
+        acl_user = self.portal.acl_users.getUserById(user_name)
+        self.failUnless(acl_user)
+        self.assertEqual(acl_user.getRoles(), ('Member', 'Authenticated'))
+        self.assertEqual(type(acl_user.roles), ListType)
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, user_name)
+        # XXX: Changed in 0.9.0
+        #self.assertEqual(self._called, ['afterClear', 'beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self._called, ['beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self.portal._v_skindata, 'refreshed')
+
+    def test_17_tearDown(self):
+        # Everything should be removed
+        self._setUp()
+        self._called = []
+        self._tearDown()
+        # XXX: No more cleanups in _clear()
+        #self.failIf(hasattr_(self.portal.Members, user_name))
+        #self.assertEqual(self.portal.acl_users.getUserById(user_name), None)
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['beforeTearDown', 'beforeClose', 'afterClear'])
+
+    def test_18_configureFlag(self):
+        # Nothing should be configured
+        self._configure_portal = 0
+        self._setUp()
+        self.assertEqual(self.portal.acl_users.getUserById(user_name), None)
+        self.failIf(hasattr_(self.portal.Members, user_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        # XXX: Changed in 0.9.0
+        #self.assertEqual(self._called, ['afterClear', 'beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self._called, ['beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self.portal._v_skindata, 'refreshed')
+
+    def test_19_configureFlag_2(self):
+        # Nothing should be cleared
+        self._setUp()
+        self._configure_portal = 0
+        self._called = []
+        self._clear()
+        # XXX: Since 0.8.4 we abort before closing the connection
+        #self.failUnless(hasattr_(self.portal.Members, user_name))
+        #self.failUnless(self.portal.acl_users.getUserById(user_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['afterClear'])
+
+    # Helpers
+
+    def getPermissionsOfRole(self, role, context=None):
+        '''Returns sorted list of permission names of the
+           given role in the given context.
+        '''
+        if context is None: context = self.portal
+        perms = context.permissionsOfRole(role)
+        perms = [p['name'] for p in perms if p['selected']]
+        perms.sort()
+        return perms
+
+    def assertPermissionsOfRole(self, permissions, role, context=None):
+        '''Compares list of permission names to permissions of the
+           given role in the given context. Fails if the lists are not
+           found equal.
+        '''
+        perms = list(permissions)[:]
+        perms.sort()
+        self.assertEqual(self.getPermissionsOfRole(role, context), perms)
+
+
+from AccessControl.User import UserFolder
+
+class WrappingUserFolder(UserFolder):
+    '''User folder returning wrapped user objects'''
+
+    def getUser(self, name):
+        return UserFolder.getUser(self, name).__of__(self)
+
+
+class TestPlainUserFolder(ZopeTestCase.PortalTestCase):
+    '''Tests whether user objects are properly wrapped'''
+
+    def getPortal(self):
+        self.app._setObject(portal_name, DummyPortal(portal_name))
+        return self.app[portal_name]
+
+    def testGetUserDoesNotWrapUser(self):
+        user = self.portal.acl_users.getUserById(user_name)
+        self.failIf(hasattr(user, 'aq_base'))
+        self.failUnless(user is aq_base(user))
+
+    def testLoggedInUserIsWrapped(self):
+        user = getSecurityManager().getUser()
+        self.assertEqual(user.getId(), user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failUnless(user.__class__.__name__, 'User')
+        self.failUnless(user.aq_parent.__class__.__name__, 'UserFolder')
+        self.failUnless(user.aq_parent.aq_parent.__class__.__name__, 'Folder')
+
+
+class TestWrappingUserFolder(ZopeTestCase.PortalTestCase):
+    '''Tests whether user objects are properly wrapped'''
+
+    def getPortal(self):
+        self.app._setObject(portal_name, DummyPortal(portal_name))
+        return self.app[portal_name]
+
+    def _setupUserFolder(self):
+        self.portal._setObject('acl_users', WrappingUserFolder())
+
+    def testGetUserWrapsUser(self):
+        user = self.folder.acl_users.getUserById(user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failIf(user is aq_base(user))
+        self.failUnless(user.aq_parent.__class__.__name__, 'WrappingUserFolder')
+
+    def testLoggedInUserIsWrapped(self):
+        user = getSecurityManager().getUser()
+        self.assertEqual(user.getId(), user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failUnless(user.__class__.__name__, 'User')
+        self.failUnless(user.aq_parent.__class__.__name__, 'WrappingUserFolder')
+        self.failUnless(user.aq_parent.aq_parent.__class__.__name__, 'Folder')
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestPortalTestCase))
+    suite.addTest(makeSuite(TestPlainUserFolder))
+    suite.addTest(makeSuite(TestWrappingUserFolder))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testPythonScript.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testPythonScript.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testPythonScript.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,186 @@
+#
+# Example ZopeTestCase testing a PythonScript object in the default fixture.
+#
+# Note that you are encouraged to call any of the following methods
+# from your own tests to modify the test user's security credentials:
+#
+#   - setRoles()
+#   - setPermissions()
+#   - login()
+#   - logout()
+#
+
+# $Id: testPythonScript.py,v 1.9 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+from AccessControl import Unauthorized
+from AccessControl import getSecurityManager
+
+ZopeTestCase.installProduct('PythonScripts')
+
+access_permissions = ['View management screens']
+change_permissions = ['Change Python Scripts']
+
+ps_params1 = 'a=1'
+ps_body1 = 'return a'
+ps_params2 = 'a'
+ps_body2 = 'return a+1'
+
+
+class TestPythonScript(ZopeTestCase.ZopeTestCase):
+    '''Tries various things allowed by the ZopeTestCase API.'''
+
+    def afterSetUp(self):
+        '''Adds a PythonScript object to the default fixture'''
+        dispatcher = self.folder.manage_addProduct['PythonScripts']
+        dispatcher.manage_addPythonScript('ps')
+        self.ps = self.folder['ps']
+        self.ps.ZPythonScript_edit(ps_params1, ps_body1)
+
+    # Test the fixture ##############################################
+
+    def testFixture(self):
+        # The PythonScript should exist and be properly set up
+        self.failUnless(hasattr(self.folder, 'ps'))
+        self.assertEqual(self.ps.body(), ps_body1+'\n')
+        self.assertEqual(self.ps.params(), ps_params1)
+        owner = self.ps.getOwner()
+        self.assertEqual(owner.getUserName(), ZopeTestCase.user_name)
+
+    # Test the scripts ##############################################
+
+    def testCanCallScript1WithArgument(self):
+        # PythonScript should return 2
+        self.assertEqual(self.ps(2), 2)
+
+    def testCanCallScript1WithoutArgument(self):
+        # PythonScript should return 1
+        self.assertEqual(self.ps(), 1)
+
+    def testCanCallScript2WithArgument(self):
+        # PythonScript should return 2
+        self.ps.ZPythonScript_edit(ps_params2, ps_body2)
+        self.assertEqual(self.ps(1), 2)
+
+    def testCannotCallScript2WithoutArgument(self):
+        # PythonScript should raise a TypeError
+        self.ps.ZPythonScript_edit(ps_params2, ps_body2)
+        self.assertRaises(TypeError, self.ps, ())
+
+    # Test access protection ########################################
+
+    def testCannotAccessWithoutAccessPermission(self):
+        # manage_main should be protected
+        self.assertRaises(Unauthorized, self.ps.restrictedTraverse, 'manage_main')
+
+    def testCanAccessWithAccessPermission(self):
+        # manage_main should be accessible
+        self.setPermissions(access_permissions)
+        try:
+            self.ps.restrictedTraverse('manage_main')
+        except Unauthorized:
+            self.fail('Access to manage_main was denied')
+
+    def testCannotAccessIfAnonymous(self):
+        # manage_main should be protected
+        self.logout()
+        self.assertRaises(Unauthorized, self.ps.restrictedTraverse, 'manage_main')
+
+    def testCanAccessIfManager(self):
+        # manage_main should be accessible to Managers
+        self.setRoles(['Manager'])
+        try:
+            self.ps.restrictedTraverse('manage_main')
+        except Unauthorized:
+            self.fail('Access to manage_main was denied to Manager')
+
+    # Test access protection with SecurityManager ###################
+
+    def testCannotAccessWithoutAccessPermissionSecurityManager(self):
+        # manage_main should be protected
+        self.assertRaises(Unauthorized, getSecurityManager().validateValue, self.ps.manage_main)
+
+    def testCanAccessWithAccessPermissionSecurityManager(self):
+        # manage_main should be accessible
+        self.setPermissions(access_permissions)
+        try:
+            getSecurityManager().validateValue(self.ps.manage_main)
+        except Unauthorized:
+            self.fail('Access to manage_main was denied')
+
+    def testCannotAccessIfAnonymousSecurityManager(self):
+        # manage_main should be protected
+        self.logout()
+        self.assertRaises(Unauthorized, getSecurityManager().validateValue, self.ps.manage_main)
+
+    def testCanAccessIfManagerSecurityManager(self):
+        # manage_main should be accessible to Managers
+        self.setRoles(['Manager'])
+        try:
+            getSecurityManager().validateValue(self.ps.manage_main)
+        except Unauthorized:
+            self.fail('Access to manage_main was denied to Manager')
+
+    # Test edit protection ##########################################
+
+    def testCannotEditWithoutChangePermission(self):
+        # PythonScript should not be editable
+        try:
+            self.ps.restrictedTraverse('ZPythonScript_edit')(ps_params2, ps_body2)
+        except Unauthorized:
+            pass    # Test passed
+        else:
+            self.assertEqual(self.ps.body(), ps_body2+'\n', 
+                    'ZPythonScript_edit was not protected')
+            self.assertEqual(self.ps.body(), ps_body1+'\n', 
+                    'ZPythonScript_edit was protected but no exception was raised')
+
+    def testCanEditWithChangePermission(self):
+        # PythonScript should be editable
+        self.setPermissions(change_permissions)
+        try:
+            self.ps.restrictedTraverse('ZPythonScript_edit')(ps_params2, ps_body2)
+        except Unauthorized:
+            self.fail('Access to ZPythonScript_edit was denied')
+        else:
+            self.assertEqual(self.ps.body(), ps_body2+'\n')
+            self.assertEqual(self.ps.params(), ps_params2)
+
+    def testCannotEditIfAnonymous(self):
+        # PythonScript should not be editable
+        self.logout()
+        try:
+            self.ps.restrictedTraverse('ZPythonScript_edit')(ps_params2, ps_body2)
+        except Unauthorized:
+            pass    # Test passed
+        else:
+            self.assertEqual(self.ps.body(), ps_body2+'\n', 
+                    'ZPythonScript_edit was not protected')
+            self.assertEqual(self.ps.body(), ps_body1+'\n', 
+                    'ZPythonScript_edit was protected but no exception was raised')
+
+    def testCanEditIfManager(self):
+        # PythonScript should be editable for Managers
+        self.setRoles(['Manager'])
+        try:
+            self.ps.restrictedTraverse('ZPythonScript_edit')(ps_params2, ps_body2)
+        except Unauthorized:
+            self.fail('Access to ZPythonScript_edit was denied to Manager')
+        else:
+            self.assertEqual(self.ps.body(), ps_body2+'\n')
+            self.assertEqual(self.ps.params(), ps_params2)
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestPythonScript))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,116 @@
+#
+# Example ZopeTestCase testing the ShoppingCart example application.
+#
+# Note the use of sessions and how the SESSION object is added to
+# the REQUEST in afterSetUp().
+#
+# You can use zLOG.LOG() if you set up the event log variables first.
+# Handy for debugging and tracing your tests.
+#
+
+# $Id: testShoppingCart.py,v 1.10 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+#os.environ['STUPID_LOG_FILE'] = os.path.join(os.getcwd(), 'zLOG.log')
+#os.environ['STUPID_LOG_SEVERITY'] = '0'
+
+from Testing import ZopeTestCase
+
+from Globals import SOFTWARE_HOME
+examples_path = os.path.join(SOFTWARE_HOME, '..', '..', 'import', 'Examples.zexp')
+examples_path = os.path.abspath(examples_path)
+
+
+if not ZopeTestCase.hasProduct('TemporaryFolder'):
+
+    print 'testShoppingCart.py: The tests in this module require Zope >= 2.5'
+
+elif not os.path.isfile(examples_path):
+
+    print "testShoppingCart.py: Cannot find file '%s'" % examples_path
+
+else:
+
+    # Open ZODB connection
+    app = ZopeTestCase.app()
+
+    # Set up sessioning objects
+    ZopeTestCase.utils.setupCoreSessions(app)
+
+    # Set up example applications
+    if not hasattr(app, 'Examples'):
+        ZopeTestCase.utils.importObjectFromFile(app, examples_path)
+
+    # Close ZODB connection
+    ZopeTestCase.close(app)
+
+
+    class DummyOrder:
+        '''Construct an order we can add to the cart'''
+        __allow_access_to_unprotected_subobjects__ = 1
+
+        def __init__(self, id, quantity):
+            self.id = id
+            self.quantity = quantity
+
+
+    class TestShoppingCart(ZopeTestCase.ZopeTestCase):
+        '''Test the ShoppingCart example application'''
+
+        _setup_fixture = 0  # No default fixture
+
+        def afterSetUp(self):
+            self.cart = self.app.Examples.ShoppingCart
+            # Put SESSION object into REQUEST
+            request = self.app.REQUEST
+            sdm = self.app.session_data_manager
+            request.set('SESSION', sdm.getSessionData())
+            self.session = request.SESSION
+
+        def testSession(self):
+            # Session should work
+            self.session.set('boring', 'boring')
+            self.assertEqual(self.session.get('boring'), 'boring')
+
+        def testCartIsEmpty(self):
+            # Cart should be empty
+            self.assertEqual(len(self.cart.currentItems()), 0)
+
+        def testAddItems(self):
+            # Adding to the cart should work
+            self.cart.addItems([DummyOrder('510-115', 1),])
+            self.assertEqual(len(self.cart.currentItems()), 1)
+
+        def testDeleteItems(self):
+            # Deleting from the cart should work
+            self.cart.addItems([DummyOrder('510-115', 1),])
+            self.cart.deleteItems(['510-115'])
+            self.assertEqual(len(self.cart.currentItems()), 0)
+
+        def testAddQuantity(self):
+            # Adding to quantity should work
+            self.cart.addItems([DummyOrder('510-115', 1),])
+            self.cart.addItems([DummyOrder('510-115', 2),])
+            self.cart.addItems([DummyOrder('510-115', 3),])
+            self.assertEqual(self.cart.currentItems()[0]['quantity'], 6)
+
+        def testGetTotal(self):
+            # Totals should be computed correctly
+            self.cart.addItems([DummyOrder('510-115', 1),])
+            self.cart.addItems([DummyOrder('510-122', 2),])
+            self.cart.addItems([DummyOrder('510-007', 2),])
+            self.assertEqual(self.cart.getTotal(), 149.95)
+
+
+    def test_suite():
+        from unittest import TestSuite, makeSuite
+        suite = TestSuite()
+        suite.addTest(makeSuite(TestShoppingCart))
+        return suite
+
+    if __name__ == '__main__':
+        framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testSkeleton.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testSkeleton.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testSkeleton.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,32 @@
+#
+# Skeleton ZopeTestCase
+#
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+
+ZopeTestCase.installProduct('SomeProduct')
+
+
+class TestSomeProduct(ZopeTestCase.ZopeTestCase):
+
+    def afterSetUp(self):
+        pass
+
+    def testSomething(self):
+        # Test something
+        self.assertEqual(1+1, 2)
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestSomeProduct))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,195 @@
+#
+# Example ZopeTestCase testing web access to a freshly started ZServer.
+#
+# Note that we need to set up the error_log before starting the ZServer.
+#
+# Note further that the test thread needs to explicitly commit its
+# transactions, so the ZServer threads can see modifications made to
+# the ZODB.
+#
+# IF YOU THINK YOU NEED THE WEBSERVER STARTED YOU ARE PROBABLY WRONG!
+# This is only required in very special cases, like when testing
+# ZopeXMLMethods where XSLT processing is done by external tools that
+# need to URL-call back into the Zope server.
+#
+# If you want to write functional unit tests, see the testFunctional.py 
+# example instead.
+#
+
+# $Id: testWebserver.py,v 1.14 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+#os.environ['STUPID_LOG_FILE'] = os.path.join(os.getcwd(), 'zLOG.log')
+#os.environ['STUPID_LOG_SEVERITY'] = '0'
+
+from Testing import ZopeTestCase
+
+from AccessControl import Unauthorized
+import urllib
+
+# Create the error_log object
+ZopeTestCase.utils.setupSiteErrorLog()
+
+# Start the web server
+host, port = ZopeTestCase.utils.startZServer(4)
+folder_url = 'http://%s:%d/%s' %(host, port, ZopeTestCase.folder_name)
+
+
+class ManagementOpener(urllib.FancyURLopener):
+    '''Logs on as manager when prompted'''
+    def prompt_user_passwd(self, host, realm):
+        return ('manager', 'secret')
+
+class UnauthorizedOpener(urllib.FancyURLopener):
+    '''Raises Unauthorized when prompted'''
+    def prompt_user_passwd(self, host, realm):
+        raise Unauthorized, 'The URLopener was asked for authentication'
+
+
+class TestWebserver(ZopeTestCase.ZopeTestCase):
+
+    def afterSetUp(self):
+        uf = self.folder.acl_users
+        uf._doAddUser('manager', 'secret', ['Manager'], [])
+        manager = uf.getUserById('manager').__of__(uf)
+
+        self.folder.addDTMLMethod('index_html', file='index_html called')
+        self.folder.addDTMLMethod('secret_html', file='secret_html called')
+        self.folder.manage_addFolder('object', '')
+
+        for p in ZopeTestCase.standard_permissions:
+            self.folder.secret_html.manage_permission(p, ['Manager'], acquire=0)
+
+        self.folder.addDTMLMethod('object_ids', file='<dtml-var objectIds>')
+        self.folder.addDTMLMethod('user_ids', file='<dtml-var "acl_users.getUserNames()">')
+        self.folder.addDTMLMethod('change_title', 
+            file='''<dtml-call "manage_changeProperties(title=REQUEST.get('title'))">'''
+                 '''<dtml-var title_or_id>''')
+
+        self.folder.object_ids.changeOwnership(manager)
+        self.folder.user_ids.changeOwnership(manager)
+        self.folder.change_title.changeOwnership(manager)
+
+        # Commit so the ZServer threads can see the changes
+        get_transaction().commit()
+
+    def beforeClose(self):
+        # Commit after cleanup
+        get_transaction().commit()
+
+    def testAccessPublicObject(self):
+        # Test access to a public resource
+        page = self.folder.index_html(self.folder)
+        self.assertEqual(page, 'index_html called')
+
+    def testURLAccessPublicObject(self):
+        # Test web access to a public resource
+        urllib._urlopener = ManagementOpener()
+        page = urllib.urlopen(folder_url+'/index_html').read()
+        self.assertEqual(page, 'index_html called')
+
+    def testAccessProtectedObject(self):
+        # Test access to a protected resource
+        page = self.folder.secret_html(self.folder)
+        self.assertEqual(page, 'secret_html called')
+
+    def testURLAccessProtectedObject(self):
+        # Test web access to a protected resource
+        urllib._urlopener = ManagementOpener()
+        page = urllib.urlopen(folder_url+'/secret_html').read()
+        self.assertEqual(page, 'secret_html called')
+
+    def testSecurityOfPublicObject(self):
+        # Test security of a public resource
+        try: 
+            self.folder.restrictedTraverse('index_html')
+        except Unauthorized:
+            # Convert error to failure
+            self.fail('Unauthorized')
+
+    def testURLSecurityOfPublicObject(self):
+        # Test web security of a public resource
+        urllib._urlopener = UnauthorizedOpener()
+        try: 
+            urllib.urlopen(folder_url+'/index_html')
+        except Unauthorized:
+            # Convert error to failure
+            self.fail('Unauthorized')
+
+    def testSecurityOfProtectedObject(self):
+        # Test security of a protected resource
+        try:
+            self.folder.restrictedTraverse('secret_html')
+        except Unauthorized:
+            pass    # Test passed
+        else:
+            self.fail('Resource not protected')
+
+    def testURLSecurityOfProtectedObject(self):
+        # Test web security of a protected resource
+        urllib._urlopener = UnauthorizedOpener()
+        try: 
+            urllib.urlopen(folder_url+'/secret_html')
+        except Unauthorized:
+            pass    # Test passed
+        else:
+            self.fail('Resource not protected')
+
+    def testModifyObject(self):
+        # Test a script that modifies the ZODB
+        self.setRoles(['Manager'])
+        self.app.REQUEST.set('title', 'Foo')
+        page = self.folder.object.change_title(self.folder.object, 
+                                               self.app.REQUEST)
+        self.assertEqual(page, 'Foo')
+        self.assertEqual(self.folder.object.title, 'Foo')
+
+    def testURLModifyObject(self):
+        # Test a transaction that actually commits something
+        urllib._urlopener = ManagementOpener()
+        page = urllib.urlopen(folder_url+'/object/change_title?title=Foo').read()
+        self.assertEqual(page, 'Foo')
+
+    def testAbsoluteURL(self):
+        # Test absolute_url
+        self.assertEqual(self.folder.absolute_url(), folder_url)
+
+
+class TestSandboxedWebserver(ZopeTestCase.Sandboxed, TestWebserver):
+    '''Demonstrates that tests involving ZServer threads can also be 
+       run from sandboxes. In fact, it may be preferable to do so.
+    '''
+
+    # Note: By inheriting from TestWebserver we run the same 
+    # test methods as above!
+
+    def testConnectionIsShared(self):
+        # Due to sandboxing the ZServer thread operates on the
+        # same connection as the main thread, allowing us to
+        # see changes made to 'object' right away.
+        urllib._urlopener = ManagementOpener()
+        urllib.urlopen(folder_url+'/object/change_title?title=Foo')
+        self.assertEqual(self.folder.object.title, 'Foo')
+
+    def testCanCommit(self):
+        # Additionally, it allows us to commit transactions without
+        # harming the test ZODB.
+        self.folder.foo = 1
+        get_transaction().commit()
+        self.folder.foo = 2
+        get_transaction().commit()
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestWebserver))
+    suite.addTest(makeSuite(TestSandboxedWebserver))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,353 @@
+#
+# Tests ZODB behavior in ZopeTestCase
+#
+# Demonstrates that cut/copy/paste/clone/rename and import/export 
+# work in ZopeTestCase if a subtransaction is commited before performing
+# the respective operations.
+#
+
+# $Id: testZODBCompat.py,v 1.17 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+
+from AccessControl.Permissions import add_documents_images_and_files
+from AccessControl.Permissions import delete_objects
+import tempfile
+
+folder_name = ZopeTestCase.folder_name
+cutpaste_permissions = [add_documents_images_and_files, delete_objects]
+
+
+class TestCopyPaste(ZopeTestCase.ZopeTestCase):
+
+    def afterSetUp(self):
+        self.setPermissions(cutpaste_permissions)
+        self.folder.addDTMLMethod('doc', file='foo')
+        # _p_oids are None until we commit a subtransaction
+        self.assertEqual(self.folder._p_oid, None)
+        get_transaction().commit(1)
+        self.failIfEqual(self.folder._p_oid, None)
+
+    def testCutPaste(self):
+        cb = self.folder.manage_cutObjects(['doc'])
+        self.folder.manage_pasteObjects(cb)
+        self.failUnless(hasattr(self.folder, 'doc'))
+        self.failIf(hasattr(self.folder, 'copy_of_doc'))
+
+    def testCopyPaste(self):
+        cb = self.folder.manage_copyObjects(['doc'])
+        self.folder.manage_pasteObjects(cb)
+        self.failUnless(hasattr(self.folder, 'doc'))
+        self.failUnless(hasattr(self.folder, 'copy_of_doc'))
+
+    def testClone(self):
+        self.folder.manage_clone(self.folder.doc, 'new_doc')
+        self.failUnless(hasattr(self.folder, 'doc'))
+        self.failUnless(hasattr(self.folder, 'new_doc'))
+
+    def testRename(self):
+        self.folder.manage_renameObjects(['doc'], ['new_doc'])
+        self.failIf(hasattr(self.folder, 'doc'))
+        self.failUnless(hasattr(self.folder, 'new_doc'))
+
+    def testCOPY(self):
+        # WebDAV COPY
+        request = self.app.REQUEST
+        request.environ['HTTP_DEPTH'] = 'infinity'
+        request.environ['HTTP_DESTINATION'] = 'http://foo.com/%s/new_doc' % folder_name
+        self.folder.doc.COPY(request, request.RESPONSE)
+        self.failUnless(hasattr(self.folder, 'doc'))
+        self.failUnless(hasattr(self.folder, 'new_doc'))
+
+    def testMOVE(self):
+        # WebDAV MOVE
+        request = self.app.REQUEST
+        request.environ['HTTP_DEPTH'] = 'infinity'
+        request.environ['HTTP_DESTINATION'] = 'http://foo.com/%s/new_doc' % folder_name
+        self.folder.doc.MOVE(request, request.RESPONSE)
+        self.failIf(hasattr(self.folder, 'doc'))
+        self.failUnless(hasattr(self.folder, 'new_doc'))
+
+
+class TestImportExport(ZopeTestCase.ZopeTestCase):
+
+    def afterSetUp(self):
+        self.setupLocalEnvironment()
+        self.folder.addDTMLMethod('doc', file='foo')
+        # _p_oids are None until we commit a subtransaction
+        self.assertEqual(self.folder._p_oid, None)
+        get_transaction().commit(1)
+        self.failIfEqual(self.folder._p_oid, None)
+
+    def testExport(self):
+        self.folder.manage_exportObject('doc')
+        self.failUnless(os.path.exists(self.zexp_file))
+
+    def testImport(self):
+        self.folder.manage_exportObject('doc')
+        self.folder._delObject('doc')
+        self.folder.manage_importObject('doc.zexp')
+        self.failUnless(hasattr(self.folder, 'doc'))
+
+    # To make export and import happy, we have to provide a file-
+    # system 'import' directory and adapt the configuration a bit:
+
+    local_home = tempfile.gettempdir()
+    import_dir = os.path.join(local_home, 'import')
+    zexp_file  = os.path.join(import_dir, 'doc.zexp')
+
+    def setupLocalEnvironment(self):
+        # Create the 'import' directory
+        os.mkdir(self.import_dir)
+        try:
+            import App.config
+        except ImportError:
+            # Modify builtins
+            builtins = getattr(__builtins__, '__dict__', __builtins__)
+            self._ih = INSTANCE_HOME
+            builtins['INSTANCE_HOME'] = self.local_home
+            self._ch = CLIENT_HOME
+            builtins['CLIENT_HOME'] = self.import_dir
+        else:
+            # Zope >= 2.7
+            config = App.config.getConfiguration()
+            self._ih = config.instancehome
+            config.instancehome = self.local_home
+            self._ch = config.clienthome
+            config.clienthome = self.import_dir
+            App.config.setConfiguration(config)
+
+    def afterClear(self):
+        # Remove external resources
+        try: os.remove(self.zexp_file)
+        except OSError: pass
+        try: os.rmdir(self.import_dir)
+        except OSError: pass
+        try:
+            import App.config
+        except ImportError:
+            # Restore builtins
+            builtins = getattr(__builtins__, '__dict__', __builtins__)
+            if hasattr(self, '_ih'):
+                builtins['INSTANCE_HOME'] = self._ih
+            if hasattr(self, '_ch'):
+                builtins['CLIENT_HOME'] = self._ch
+        else:
+            # Zope >= 2.7
+            config = App.config.getConfiguration()
+            if hasattr(self, '_ih'):
+                config.instancehome = self._ih
+            if hasattr(self, '_ch'):
+                config.clienthome = self._ch
+            App.config.setConfiguration(config)
+
+
+# Dummy object
+from OFS.SimpleItem import SimpleItem
+
+class DummyObject(SimpleItem):
+    id = 'dummy'
+    foo = None
+    _v_foo = None
+    _p_foo = None
+
+app = ZopeTestCase.app()
+app._setObject('dummy1', DummyObject())
+app._setObject('dummy2', DummyObject())
+get_transaction().commit()
+ZopeTestCase.close(app)
+
+
+class TestAttributesOfCleanObjects(ZopeTestCase.ZopeTestCase):
+    '''This testcase shows that _v_ and _p_ attributes are NOT bothered
+       by transaction boundaries, if the respective object is otherwise
+       left untouched (clean). This means that such variables will keep
+       their values across tests.
+
+       The only use case yet encountered in the wild is portal_memberdata's
+       _v_temps attribute. Test authors are cautioned to watch out for 
+       occurrences of _v_ and _p_ attributes of objects that are not recreated
+       for every test method execution, but preexist in the test ZODB.
+
+       It is therefore deemed essential to initialize any _v_ and _p_ 
+       attributes of such objects in afterSetup(), as otherwise test results 
+       will be distorted!
+
+       Note that _v_ attributes used to be transactional in Zope < 2.6.
+
+       This testcase exploits the fact that test methods are sorted by name.
+    '''
+    
+    def afterSetUp(self):
+        self.dummy = self.app.dummy1 # See above
+
+    def testNormal_01(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+        self.dummy.foo = 'foo'
+
+    def testNormal_02(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+        self.dummy.foo = 'bar'
+
+    def testNormal_03(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+
+    def testPersistent_01(self):
+        # _p_foo is initially None
+        self.assertEqual(self.dummy._p_foo, None)
+        self.dummy._p_foo = 'foo'
+
+    def testPersistent_02(self):
+        # _p_foo retains value assigned by previous test
+        self.assertEqual(self.dummy._p_foo, 'foo')
+        self.dummy._p_foo = 'bar'
+
+    def testPersistent_03(self):
+        # _p_foo retains value assigned by previous test
+        self.assertEqual(self.dummy._p_foo, 'bar')
+
+    def testVolatile_01(self):
+        # _v_foo is initially None
+        self.assertEqual(self.dummy._v_foo, None)
+        self.dummy._v_foo = 'foo'
+
+    def testVolatile_02(self):
+        if hasattr(self.app._p_jar, 'register'):
+            # _v_foo retains value assigned by previous test
+            self.assertEqual(self.dummy._v_foo, 'foo')
+        else:
+            # XXX: _v_foo is transactional in Zope < 2.6
+            self.assertEqual(self.dummy._v_foo, None)
+        self.dummy._v_foo = 'bar'
+
+    def testVolatile_03(self):
+        if hasattr(self.app._p_jar, 'register'):
+            # _v_foo retains value assigned by previous test
+            self.assertEqual(self.dummy._v_foo, 'bar')
+        else:
+            # XXX: _v_foo is transactional in Zope < 2.6
+            self.assertEqual(self.dummy._v_foo, None)
+
+
+class TestAttributesOfDirtyObjects(ZopeTestCase.ZopeTestCase):
+    '''This testcase shows that _v_ and _p_ attributes of dirty objects 
+       ARE removed on abort.
+
+       This testcase exploits the fact that test methods are sorted by name.
+    '''
+
+    def afterSetUp(self):
+        self.dummy = self.app.dummy2 # See above
+        self.dummy.touchme = 1 # Tag, you're dirty
+
+    def testDirtyNormal_01(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+        self.dummy.foo = 'foo'
+
+    def testDirtyNormal_02(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+        self.dummy.foo = 'bar'
+
+    def testDirtyNormal_03(self):
+        # foo is always None
+        self.assertEqual(self.dummy.foo, None)
+
+    def testDirtyPersistent_01(self):
+        # _p_foo is alwas None
+        self.assertEqual(self.dummy._p_foo, None)
+        self.dummy._p_foo = 'foo'
+
+    def testDirtyPersistent_02(self):
+        # _p_foo is alwas None
+        self.assertEqual(self.dummy._p_foo, None)
+        self.dummy._p_foo = 'bar'
+
+    def testDirtyPersistent_03(self):
+        # _p_foo is alwas None
+        self.assertEqual(self.dummy._p_foo, None)
+
+    def testDirtyVolatile_01(self):
+        # _v_foo is always None
+        self.assertEqual(self.dummy._v_foo, None)
+        self.dummy._v_foo = 'foo'
+
+    def testDirtyVolatile_02(self):
+        # _v_foo is always None
+        self.assertEqual(self.dummy._v_foo, None)
+        self.dummy._v_foo = 'bar'
+
+    def testDirtyVolatile_03(self):
+        # _v_foo is always None
+        self.assertEqual(self.dummy._v_foo, None)
+
+
+class TestTransactionAbort(ZopeTestCase.ZopeTestCase):
+
+    def testTransactionAbort(self):
+        self.folder.foo = 1
+        self.failUnless(hasattr(self.folder, 'foo'))
+        get_transaction().abort()
+        # The foo attribute is still present
+        self.failUnless(hasattr(self.folder, 'foo'))
+
+    def testSubTransactionAbort(self):
+        self.folder.foo = 1
+        self.failUnless(hasattr(self.folder, 'foo'))
+        get_transaction().commit(1)
+        get_transaction().abort()
+        # This time the abort nukes the foo attribute...
+        self.failIf(hasattr(self.folder, 'foo'))
+
+    def testTransactionAbortPersistent(self):
+        self.folder._p_foo = 1
+        self.failUnless(hasattr(self.folder, '_p_foo'))
+        get_transaction().abort()
+        # The _p_foo attribute is still present
+        self.failUnless(hasattr(self.folder, '_p_foo'))
+
+    def testSubTransactionAbortPersistent(self):
+        self.folder._p_foo = 1
+        self.failUnless(hasattr(self.folder, '_p_foo'))
+        get_transaction().commit(1)
+        get_transaction().abort()
+        # This time the abort nukes the _p_foo attribute...
+        self.failIf(hasattr(self.folder, '_p_foo'))
+
+    def testTransactionAbortVolatile(self):
+        self.folder._v_foo = 1
+        self.failUnless(hasattr(self.folder, '_v_foo'))
+        get_transaction().abort()
+        # The _v_foo attribute is still present
+        self.failUnless(hasattr(self.folder, '_v_foo'))
+
+    def testSubTransactionAbortVolatile(self):
+        self.folder._v_foo = 1
+        self.failUnless(hasattr(self.folder, '_v_foo'))
+        get_transaction().commit(1)
+        get_transaction().abort()
+        # This time the abort nukes the _v_foo attribute...
+        self.failIf(hasattr(self.folder, '_v_foo'))
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestCopyPaste))
+    suite.addTest(makeSuite(TestImportExport))
+    suite.addTest(makeSuite(TestAttributesOfCleanObjects))
+    suite.addTest(makeSuite(TestAttributesOfDirtyObjects))
+    suite.addTest(makeSuite(TestTransactionAbort))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/testZopeTestCase.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testZopeTestCase.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testZopeTestCase.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,470 @@
+#
+# Tests the ZopeTestCase, eating its own dogfood
+#
+# NOTE: This is *not* an example ZopeTestCase. Do not
+# use this file as a blueprint for your own tests!
+#
+# See testPythonScript.py and testShoppingCart.py for
+# example test cases. See testSkeleton.py for a quick
+# way of getting started.
+#
+
+# $Id: testZopeTestCase.py,v 1.17 2004/04/09 12:38:37 shh42 Exp $
+
+import os, sys
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+from Testing import ZopeTestCase
+
+from Acquisition import aq_base
+from AccessControl import getSecurityManager
+from types import ListType
+
+folder_name = ZopeTestCase.folder_name
+user_name = ZopeTestCase.user_name
+user_role = ZopeTestCase.user_role
+standard_permissions = ZopeTestCase.standard_permissions
+
+
+def hasattr_(ob, attr):
+    return hasattr(aq_base(ob), attr)
+
+
+class TestZopeTestCase(ZopeTestCase.ZopeTestCase):
+    '''Incrementally exercise the ZopeTestCase API.
+       Exploit the fact that tests are sorted by name.
+    '''
+
+    _setUp = ZopeTestCase.ZopeTestCase.setUp
+    _tearDown = ZopeTestCase.ZopeTestCase.tearDown
+
+    def setUp(self):
+        # For this test case we *want* to start
+        # with an empty fixture.
+        self._called = []
+        # Implicitly aborts previous transaction
+        get_transaction().begin()
+
+    def beforeSetUp(self):
+        self._called.append('beforeSetUp')
+
+    def afterSetUp(self):
+        self._called.append('afterSetUp')
+
+    def beforeTearDown(self):
+        self._called.append('beforeTearDown')
+
+    def beforeClose(self):
+        self._called.append('beforeClose')
+
+    def afterClear(self):
+        self._called.append('afterClear')
+
+    def test_01_setupFolder(self):
+        # Folder should be set up
+        self.app = self._app()
+        self._setupFolder()
+        self.failUnless(hasattr_(self.app, folder_name))
+        self.failUnless(hasattr(self, 'folder'))
+        self.failUnless(user_role in self.folder.userdefined_roles())
+        self.assertPermissionsOfRole(standard_permissions, user_role)
+
+    def test_02_setupUserFolder(self):
+        # User folder should be set up
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self.failUnless(hasattr_(self.folder, 'acl_users'))
+
+    def test_03_setupUser(self):
+        # User should be set up
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        acl_user = self.folder.acl_users.getUserById(user_name)
+        self.failUnless(acl_user)
+        self.assertEqual(acl_user.getRoles(), (user_role, 'Authenticated'))
+        self.assertEqual(type(acl_user.roles), ListType)
+
+    def test_04_setRoles(self):
+        # Roles should be set for user
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        test_roles = ['Manager', user_role]
+        test_roles.sort()
+        self.setRoles(test_roles)
+        acl_user = self.folder.acl_users.getUserById(user_name)
+        user_roles = list(acl_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_05_setRoles_2(self):
+        # Roles of logged in user should be updated
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        self.login()
+        test_roles = ['Manager', user_role]
+        test_roles.sort()
+        self.setRoles(test_roles)
+        auth_user = getSecurityManager().getUser()
+        user_roles = list(auth_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_06_setRoles_3(self):
+        # Roles should be set for a specified user
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self.folder.acl_users._doAddUser('test_user_2_', 'secret', [], [])
+        test_roles = ['Manager', user_role]
+        test_roles.sort()
+        self.setRoles(test_roles, 'test_user_2_')
+        acl_user = self.folder.acl_users.getUserById('test_user_2_')
+        user_roles = list(acl_user.getRoles())
+        user_roles.remove('Authenticated')
+        user_roles.sort()
+        self.assertEqual(user_roles, test_roles)
+
+    def test_07_setPermissions(self):
+        # Permissions should be set for user
+        self.app = self._app()
+        self._setupFolder()
+        test_perms = standard_permissions + ['Add Folders']
+        self.assertPermissionsOfRole(standard_permissions, user_role)
+        self.setPermissions(test_perms)
+        self.assertPermissionsOfRole(test_perms, user_role)
+
+    def test_08_setPermissions_2(self):
+        # Permissions should be set for a specified role
+        self.app = self._app()
+        self._setupFolder()
+        self.folder._addRole('test_role_2_')
+        self.assertPermissionsOfRole([], 'test_role_2_')
+        self.setPermissions(standard_permissions, 'test_role_2_')
+        self.assertPermissionsOfRole(standard_permissions, 'test_role_2_')
+
+    def test_09_login(self):
+        # User should be able to log in
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.login()
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, user_name)
+
+    def test_10_login_2(self):
+        # A specified user should be logged in
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self.folder.acl_users._doAddUser('test_user_2_', 'secret', [], [])
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.login('test_user_2_')
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, 'test_user_2_')
+
+    def test_11_login_3(self):
+        # Unknown user should raise AttributeError
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self.assertRaises(AttributeError, self.login, 'test_user_3_')
+
+    def test_12_logout(self):
+        # User should be able to log out
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        self.login()
+        self.logout()
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+
+    def test_13_clear(self):
+        # Everything should be removed
+        self.app = self._app()
+        self._setupFolder()
+        self._setupUserFolder()
+        self._setupUser()
+        self._clear(1)
+        self.failIf(hasattr_(self.app, folder_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['beforeClose', 'afterClear'])
+        # _clear must not fail when called repeatedly
+        self._clear()
+
+    def test_14_setUp(self):
+        # Everything should be set up
+        self._setUp()
+        self.failUnless(hasattr_(self.app, folder_name))
+        self.failUnless(hasattr(self, 'folder'))
+        self.failUnless(user_role in self.folder.userdefined_roles())
+        self.assertPermissionsOfRole(standard_permissions, user_role)
+        self.failUnless(hasattr_(self.folder, 'acl_users'))
+        acl_user = self.folder.acl_users.getUserById(user_name)
+        self.failUnless(acl_user)
+        self.assertEqual(acl_user.getRoles(), (user_role, 'Authenticated'))
+        self.assertEqual(type(acl_user.roles), ListType)
+        auth_name = getSecurityManager().getUser().getId()
+        self.assertEqual(auth_name, user_name)
+        # XXX: Changed in 0.9.0
+        #self.assertEqual(self._called, ['afterClear', 'beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self._called, ['beforeSetUp', 'afterSetUp'])
+
+    def test_15_tearDown(self):
+        # Everything should be removed
+        self._setUp()
+        self._called = []
+        self._tearDown()
+        self.failIf(hasattr_(self.app, folder_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['beforeTearDown', 'beforeClose', 'afterClear'])
+
+    def test_16_setupFlag(self):
+        # Nothing should be set up
+        self._setup_fixture = 0
+        self._setUp()
+        self.failIf(hasattr_(self.app, folder_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        # XXX: Changed in 0.9.0
+        #self.assertEqual(self._called, ['afterClear', 'beforeSetUp', 'afterSetUp'])
+        self.assertEqual(self._called, ['beforeSetUp', 'afterSetUp'])
+
+    def test_17_setupFlag_2(self):
+        # Nothing should be cleared
+        self._setUp()
+        self._setup_fixture = 0
+        self._called = []
+        self._clear()
+        # XXX: Since 0.8.4 we abort before closing the connection
+        #self.failUnless(hasattr_(self.app, folder_name))
+        auth_name = getSecurityManager().getUser().getUserName()
+        self.assertEqual(auth_name, 'Anonymous User')
+        self.assertEqual(self._called, ['afterClear'])
+
+    # Bug tests
+
+    def test_18_setOwnerPermissions(self):
+        # Permissions should be modified for the Owner role
+        self.app = self._app()
+        self._setupFolder()
+        self.assertPermissionsOfRole([], 'Owner')
+        self.setPermissions(standard_permissions, 'Owner')
+        self.assertPermissionsOfRole(standard_permissions, 'Owner')
+
+    def test_19_setManagerPermissions(self):
+        # Permissions should *not* be modified for the Manager role
+        self.app = self._app()
+        self._setupFolder()
+        # Setting permissions for Manager role does not work like this
+        manager_perms = self.getPermissionsOfRole('Manager')
+        self.setPermissions(standard_permissions, 'Manager')
+        # Manager does still have all permissions
+        self.assertPermissionsOfRole(manager_perms, 'Manager')
+
+    def test_20_setManagerPermissions_2(self):
+        # Permissions should be modified for the Manager role
+        self.app = self._app()
+        self._setupFolder()
+        # However, it works like that (because we turn off acquisition?)
+        manager_perms = self.getPermissionsOfRole('Manager')
+        self.folder.manage_permission('Take ownership', ['Owner'], acquire=0)
+        self.assertPermissionsOfRole(['Take ownership'], 'Owner')
+        # Manager does not have 'Take ownership' anymore
+        manager_perms.remove('Take ownership')
+        self.assertPermissionsOfRole(manager_perms, 'Manager')
+
+    # Helpers
+
+    def getPermissionsOfRole(self, role, context=None):
+        '''Returns sorted list of permission names of the
+           given role in the given context.
+        '''
+        if context is None: context = self.folder
+        perms = context.permissionsOfRole(role)
+        perms = [p['name'] for p in perms if p['selected']]
+        perms.sort()
+        return perms
+
+    def assertPermissionsOfRole(self, permissions, role, context=None):
+        '''Compares list of permission names to permissions of the
+           given role in the given context. Fails if the lists are not
+           found equal.
+        '''
+        perms = list(permissions)[:]
+        perms.sort()
+        self.assertEqual(self.getPermissionsOfRole(role, context), perms)
+
+
+import unittest
+
+class TestConnectionRegistry(unittest.TestCase):
+    '''Tests the ZODB connection registry'''
+
+    class Conn:
+        closed = 0
+        def close(self):
+            self.closed = 1
+
+    def setUp(self):
+        self.reg = ZopeTestCase.utils.ConnectionRegistry()
+        self.conns = [self.Conn(), self.Conn(), self.Conn()]
+
+    def testRegister(self):
+        # Should be able to register connections
+        for conn in self.conns:
+            self.reg.register(conn)
+        assert len(self.reg) == 3
+
+    def testCloseConnection(self):
+        # Should be able to close a single registered connection
+        for conn in self.conns:
+            self.reg.register(conn)
+        assert len(self.reg) == 3
+        self.reg.close(self.conns[0])
+        assert len(self.reg) == 2
+        assert self.conns[0].closed == 1
+        assert self.conns[1].closed == 0
+        assert self.conns[2].closed == 0
+
+    def testCloseSeveralConnections(self):
+        # Should be able to close all registered connections one-by-one
+        for conn in self.conns:
+            self.reg.register(conn)
+        assert len(self.reg) == 3
+        self.reg.close(self.conns[0])
+        assert len(self.reg) == 2
+        assert self.conns[0].closed == 1
+        assert self.conns[1].closed == 0
+        assert self.conns[2].closed == 0
+        self.reg.close(self.conns[2])
+        assert len(self.reg) == 1
+        assert self.conns[0].closed == 1
+        assert self.conns[1].closed == 0
+        assert self.conns[2].closed == 1
+        self.reg.close(self.conns[1])
+        assert len(self.reg) == 0
+        assert self.conns[0].closed == 1
+        assert self.conns[1].closed == 1
+        assert self.conns[2].closed == 1
+
+    def testCloseForeignConnection(self):
+        # Should be able to close a connection that has not been registered
+        for conn in self.conns:
+            self.reg.register(conn)
+        assert len(self.reg) == 3
+        conn = self.Conn()
+        self.reg.close(conn)
+        assert len(self.reg) == 3
+        assert self.conns[0].closed == 0
+        assert self.conns[1].closed == 0
+        assert self.conns[2].closed == 0
+        assert conn.closed == 1
+
+    def testCloseAllConnections(self):
+        # Should be able to close all registered connections at once
+        for conn in self.conns:
+            self.reg.register(conn)
+        assert len(self.reg) == 3
+        self.reg.closeAll()
+        assert len(self.reg) == 0
+        assert self.conns[0].closed == 1
+        assert self.conns[1].closed == 1
+        assert self.conns[2].closed == 1
+
+
+from AccessControl.User import UserFolder
+from Acquisition import aq_inner, aq_parent, aq_chain
+
+class WrappingUserFolder(UserFolder):
+    '''User folder returning wrapped user objects'''
+
+    def getUser(self, name):
+        return UserFolder.getUser(self, name).__of__(self)
+
+
+class TestPlainUserFolder(ZopeTestCase.ZopeTestCase):
+    '''Tests whether user objects are properly wrapped'''
+
+    def testGetUserDoesNotWrapUser(self):
+        user = self.folder.acl_users.getUserById(user_name)
+        self.failIf(hasattr(user, 'aq_base'))
+        self.failUnless(user is aq_base(user))
+
+    def testLoggedInUserIsWrapped(self):
+        user = getSecurityManager().getUser()
+        self.assertEqual(user.getId(), user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failUnless(user.__class__.__name__, 'User')
+        self.failUnless(user.aq_parent.__class__.__name__, 'UserFolder')
+        self.failUnless(user.aq_parent.aq_parent.__class__.__name__, 'Folder')
+
+
+class TestWrappingUserFolder(ZopeTestCase.ZopeTestCase):
+    '''Tests whether user objects are properly wrapped'''
+
+    def _setupUserFolder(self):
+        self.folder._setObject('acl_users', WrappingUserFolder())
+
+    def testGetUserWrapsUser(self):
+        user = self.folder.acl_users.getUserById(user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failIf(user is aq_base(user))
+        self.failUnless(user.aq_parent.__class__.__name__, 'WrappingUserFolder')
+
+    def testLoggedInUserIsWrapped(self):
+        user = getSecurityManager().getUser()
+        self.assertEqual(user.getId(), user_name)
+        self.failUnless(hasattr(user, 'aq_base'))
+        self.failUnless(user.__class__.__name__, 'User')
+        self.failUnless(user.aq_parent.__class__.__name__, 'WrappingUserFolder')
+        self.failUnless(user.aq_parent.aq_parent.__class__.__name__, 'Folder')
+
+
+class TestRequestVariables(ZopeTestCase.ZopeTestCase):
+    '''Makes sure the REQUEST contains required variables'''
+
+    def testRequestVariables(self):
+        request = self.app.REQUEST
+        self.failIfEqual(request.get('SERVER_NAME', ''), '')
+        self.failIfEqual(request.get('SERVER_PORT', ''), '')
+        self.failIfEqual(request.get('REQUEST_METHOD', ''), '')
+        self.failIfEqual(request.get('URL', ''), '')
+        self.failIfEqual(request.get('SERVER_URL', ''), '')
+        self.failIfEqual(request.get('URL0', ''), '')
+        self.failIfEqual(request.get('URL1', ''), '')
+        self.failIfEqual(request.get('BASE0', ''), '')
+        self.failIfEqual(request.get('BASE1', ''), '')
+        self.failIfEqual(request.get('BASE2', ''), '')
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestZopeTestCase))
+    suite.addTest(makeSuite(TestConnectionRegistry))
+    suite.addTest(makeSuite(TestPlainUserFolder))
+    suite.addTest(makeSuite(TestWrappingUserFolder))
+    suite.addTest(makeSuite(TestRequestVariables))
+    return suite
+
+if __name__ == '__main__':
+    framework()
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/threadutils.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/threadutils.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/threadutils.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,46 @@
+#
+# Parts of ZServer support are in this module so they can
+# be imported more selectively.
+#
+
+# $Id: threadutils.py,v 1.5 2004/01/09 14:35:08 shh42 Exp $
+
+from threading import Thread
+from StringIO import StringIO
+
+dummyLOG = StringIO()
+
+
+def zserverRunner(host, port, log=None):
+    '''Runs an HTTP ZServer on host:port.'''
+    from ZServer import logger, asyncore
+    from ZServer import zhttp_server, zhttp_handler
+    if log is None: log = dummyLOG
+    lg = logger.file_logger(log)
+    hs = zhttp_server(ip=host, port=port, resolver=None, logger_object=lg)
+    zh = zhttp_handler(module='Zope', uri_base='')
+    hs.install_handler(zh)
+    asyncore.loop()
+
+
+class QuietThread(Thread):
+    '''This thread eats all exceptions'''
+    def __init__(self, target=None, args=(), kwargs={}):
+        Thread.__init__(self, target=target, args=args, kwargs=kwargs)
+        self.__old_bootstrap = Thread._Thread__bootstrap
+    def __bootstrap(self):
+        try: self.__old_bootstrap(self)
+        except: pass
+    _Thread__bootstrap = __bootstrap
+
+
+def QuietPublisher(self, accept):
+    '''This server eats all exceptions'''
+    try: self.__old_init__(accept)
+    except: pass
+
+
+from ZServer.PubCore.ZServerPublisher import ZServerPublisher
+ZServerPublisher.__old_init__ = ZServerPublisher.__init__
+ZServerPublisher.__init__ = QuietPublisher
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,162 @@
+#
+# Utility functions
+#
+# These functions are designed to be imported and run at
+# module level to add functionality to the test environment.
+#
+
+# $Id: utils.py,v 1.13 2004/02/17 19:34:36 shh42 Exp $
+
+
+def setupCoreSessions(app=None):
+    '''Sets up the session_data_manager e.a.'''
+    from Acquisition import aq_base
+    commit = 0
+
+    if app is None: 
+        return appcall(setupCoreSessions)
+
+    if not hasattr(app, 'temp_folder'):
+        from Products.TemporaryFolder.TemporaryFolder import MountedTemporaryFolder
+        tf = MountedTemporaryFolder('temp_folder','Temporary Folder')
+        app._setObject('temp_folder', tf)
+        commit = 1
+
+    if not hasattr(aq_base(app.temp_folder), 'session_data'):
+        from Products.Transience.Transience import TransientObjectContainer
+        toc = TransientObjectContainer('session_data',
+                    'Session Data Container',
+                    timeout_mins=3,
+                    limit=100)
+        app.temp_folder._setObject('session_data', toc)
+        commit = 1
+
+    if not hasattr(app, 'browser_id_manager'):
+        from Products.Sessions.BrowserIdManager import BrowserIdManager
+        bid = BrowserIdManager('browser_id_manager',
+                    'Browser Id Manager')
+        app._setObject('browser_id_manager', bid)
+        commit = 1
+
+    if not hasattr(app, 'session_data_manager'):
+        from Products.Sessions.SessionDataManager import SessionDataManager
+        sdm = SessionDataManager('session_data_manager',
+                    title='Session Data Manager',
+                    path='/temp_folder/session_data',
+                    requestName='SESSION')
+        app._setObject('session_data_manager', sdm)
+        commit = 1
+
+    if commit: get_transaction().commit()
+
+
+def setupZGlobals(app=None):
+    '''Sets up the ZGlobals BTree required by ZClasses.'''
+    if app is None: 
+        return appcall(setupZGlobals)
+
+    root = app._p_jar.root()
+    if not root.has_key('ZGlobals'):
+        from BTrees.OOBTree import OOBTree
+        root['ZGlobals'] = OOBTree()
+        get_transaction().commit()
+
+
+def setupSiteErrorLog(app=None):
+    '''Sets up the error_log object required by ZPublisher.'''
+    if app is None: 
+        return appcall(setupSiteErrorLog)
+
+    if not hasattr(app, 'error_log'):
+        try:
+            from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
+        except ImportError:
+            pass
+        else:
+            app._setObject('error_log', SiteErrorLog())
+            get_transaction().commit()
+
+
+import os, time
+
+def importObjectFromFile(container, filename, quiet=0):
+    '''Imports an object from a (.zexp) file into the given container.'''
+    from ZopeLite import _print
+    start = time.time()
+    if not quiet: _print("Importing %s ... " % os.path.basename(filename))
+    container._importObjectFromFile(filename, verify=0)
+    get_transaction().commit()
+    if not quiet: _print('done (%.3fs)\n' % (time.time() - start))
+
+
+_Z2HOST = None
+_Z2PORT = None
+
+def startZServer(number_of_threads=1, log=None):
+    '''Starts an HTTP ZServer thread.'''
+    global _Z2HOST, _Z2PORT
+    if _Z2HOST is None:
+        import random
+        _Z2HOST = '127.0.0.1'
+        _Z2PORT = random.choice(range(55000, 55500))
+        from ZServer import setNumberOfThreads
+        setNumberOfThreads(number_of_threads)
+        from threadutils import QuietThread, zserverRunner
+        t = QuietThread(target=zserverRunner, args=(_Z2HOST, _Z2PORT, log))
+        t.setDaemon(1)
+        t.start()
+    return _Z2HOST, _Z2PORT
+
+
+import sys
+
+def makerequest(app, stdout=sys.stdout):
+    '''Wraps the app into a fresh REQUEST.'''
+    from ZPublisher.BaseRequest import RequestContainer
+    from ZPublisher.Request import Request
+    from ZPublisher.Response import Response
+    response = Response(stdout=stdout)
+    environ = {}
+    environ['SERVER_NAME'] = _Z2HOST or 'nohost'
+    environ['SERVER_PORT'] = '%d' % (_Z2PORT or 80)
+    environ['REQUEST_METHOD'] = 'GET'
+    request = Request(sys.stdin, environ, response)
+    request._steps = ['noobject'] # Fake a published object
+    return app.__of__(RequestContainer(REQUEST=request))
+
+
+def appcall(function, *args, **kw):
+    '''Calls a function passing 'app' as first argument.'''
+    import ZopeTestCase
+    app = ZopeTestCase.app()
+    args = (app,) + args
+    try:
+        return function(*args, **kw)
+    finally:
+        ZopeTestCase.close(app)
+
+
+class ConnectionRegistry:
+    '''ZODB connection registry'''
+
+    def __init__(self):
+        self._conns = []
+
+    def register(self, conn):
+        self._conns.append(conn)
+
+    def close(self, conn):
+        try: self._conns.remove(conn)
+        except: pass
+        try: conn.close()
+        except: pass
+
+    def closeAll(self):
+        for conn in self._conns:
+            try: conn.close()
+            except: pass
+        self._conns = []
+
+    def __len__(self):
+        return len(self._conns)
+

Added: Zope/trunk/lib/python/Testing/ZopeTestCase/ztc_common.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/ztc_common.py	2004-09-06 15:42:10 UTC (rev 27452)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/ztc_common.py	2004-09-06 19:04:41 UTC (rev 27453)
@@ -0,0 +1,169 @@
+#
+# ztc_common.py
+#
+# This file must be called from framework.py like so
+#
+#   execfile(os.path.join(os.path.dirname(Testing.__file__),
+#            'ZopeTestCase', 'ztc_common.py'))
+#
+
+# $Id: ztc_common.py,v 1.13 2004/03/30 16:40:04 shh42 Exp $
+
+
+# Overwrites the default framework() method to expose the
+# TestRunner parameters and add profiler support.
+#
+def framework(stream=sys.stderr, descriptions=1, verbosity=1):
+    if __name__ != '__main__':
+        return
+
+    if len(sys.argv) > 1:
+        arg = sys.argv[1]
+        if arg in ('profile', 'profile-tests'):
+            os.environ['PROFILE_TESTS'] = '1'
+        elif arg == 'profile-setup':
+            os.environ['PROFILE_SETUP'] = '1'
+        elif arg == 'profile-teardown':
+            os.environ['PROFILE_TEARDOWN'] = '1'
+        else:
+            sys.exit(globals()[arg]() and 1 or 0)
+
+    errors = TestRunner(stream, descriptions, verbosity).run(test_suite())
+    from Testing.ZopeTestCase import profiler; profiler.print_stats()
+    sys.exit(errors and 1 or 0)
+
+
+# Configures the Zope environment
+#
+class Configurator:
+
+    def __init__(self):
+        '''Sets up the configurator.'''
+        self.cwd = self.realpath(os.getcwd())
+        self.software_home = self.realpath(os.environ.get('SOFTWARE_HOME', ''))
+        self.instance_home = self.realpath(globals()['__INSTANCE_HOME'])
+        self.zeo_instance_home = self.realpath(os.environ.get('ZEO_INSTANCE_HOME', ''))
+        self.zope_config = self.realpath(os.environ.get('ZOPE_CONFIG', ''))
+
+    def run(self):
+        '''Runs the configurator.'''
+        if self.zope_config:    
+            # Don't configure anything if people use the ZOPE_CONFIG patch
+            return
+        if self.zeo_instance_home:
+            self.setup_zeo_instance_home()
+        else:
+            if self.instance_home:
+                self.setup_instance_home()
+            else:
+                self.detect_and_setup_instance_home()
+            self.setup_custom_zodb()
+    
+    def setup_zeo_instance_home(self):
+        '''If ZEO_INSTANCE_HOME has been given, assume a ZEO setup and use the
+           instance's custom_zodb.py to connect to a running ZEO server.'''
+        if os.path.isdir(os.path.join(self.zeo_instance_home, 'Products')):
+            if os.path.exists(os.path.join(self.zeo_instance_home, 'custom_zodb.py')):
+                self.add_instance(self.zeo_instance_home)
+                if self.getconfig('testinghome'):
+                    self.setconfig(testinghome=self.zeo_instance_home)
+                    self.setconfig(instancehome=self.zeo_instance_home)
+                else:
+                    os.environ['INSTANCE_HOME'] = INSTANCE_HOME = self.zeo_instance_home
+                    self.setconfig(instancehome=self.zeo_instance_home)
+            else:
+                print 'Unable to locate custom_zodb.py in %s.' % self.zeo_instance_home
+                sys.exit(1)
+        else:
+            print 'Unable to locate Products directory in %s.' % self.zeo_instance_home
+            sys.exit(1)
+
+    def setup_instance_home(self):
+        '''If INSTANCE_HOME has been given, add the instance's Products
+           and lib/python directories to the appropriate paths.'''
+        if os.path.isdir(os.path.join(self.instance_home, 'Products')):
+            self.add_instance(self.instance_home)
+            if self.getconfig('testinghome'):
+                self.setconfig(instancehome=self.instance_home)
+        else:
+            print 'Unable to locate Products directory in %s.' % self.instance_home
+            sys.exit(1)
+
+    def detect_and_setup_instance_home(self):
+        '''If INSTANCE_HOME has not been given, try to detect whether we run
+           in an instance home installation by walking up from cwd until we
+           find a 'Products' dir.'''
+        if not self.cwd.startswith(self.software_home):
+            p = d = self.cwd
+            while d:
+                if os.path.isdir(os.path.join(p, 'Products')):
+                    self.add_instance(p)
+                    if self.getconfig('testinghome'):
+                        self.setconfig(instancehome=p)
+                    break
+                p, d = os.path.split(p)
+            else:
+                print 'Unable to locate Products directory.',
+                print 'You might need to set INSTANCE_HOME.'
+                sys.exit(1)
+
+    def setup_custom_zodb(self):
+        '''If there is a custom_zodb.py file in the tests dir, use it.
+           Note that the instance has already been set at this point
+           so redirecting INSTANCE_HOME should be safe.'''
+        if os.path.exists(os.path.join(self.cwd, 'custom_zodb.py')):
+            if self.getconfig('testinghome'):
+                self.setconfig(testinghome=self.cwd)
+            else:
+                os.environ['INSTANCE_HOME'] = INSTANCE_HOME = self.cwd
+                self.setconfig(instancehome=self.cwd)
+
+    def add_instance(self, p):
+        '''Adds an INSTANCE_HOME directory to Products.__path__ and sys.path.'''
+        import Products
+        products = os.path.join(p, 'Products')
+        if os.path.isdir(products) and products not in Products.__path__:
+            Products.__path__.insert(0, products)
+        libpython = os.path.join(p, 'lib', 'python')
+        if os.path.isdir(libpython) and libpython not in sys.path:
+            sys.path.insert(0, libpython)
+
+    def getconfig(self, key):
+        '''Reads a value from Zope configuration.'''
+        try:
+            import App.config
+        except ImportError:
+            pass
+        else:
+            config = App.config.getConfiguration()
+            return getattr(config, key, None)
+
+    def setconfig(self, **kw):
+        '''Updates Zope configuration'''
+        try:
+            import App.config
+        except ImportError:
+            pass
+        else:
+            config = App.config.getConfiguration()
+            for key, value in kw.items():
+                setattr(config, key, value)
+            App.config.setConfiguration(config)
+
+    def realpath(self, path):
+        try:
+            from os.path import realpath
+        except ImportError:
+            try:
+                from App.Common import realpath
+            except ImportError:
+                realpath = os.path.abspath
+        if not path:
+            return path
+        return realpath(path)
+
+
+if __name__ == '__main__':
+    Configurator().run()
+    del Configurator
+



More information about the Zope-Checkins mailing list