[Zope-Checkins] SVN: Zope/trunk/ Testing.ZopeTestCase: Introduced a
"ZopeLite" test layer, making it
Stefan H. Holek
stefan at epy.co.at
Sat Oct 13 12:15:39 EDT 2007
Log message for revision 80864:
Testing.ZopeTestCase: Introduced a "ZopeLite" test layer, making it
possible to mix ZTC and non-ZTC tests much more freely.
Changed:
U Zope/trunk/doc/CHANGES.txt
U Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/base.py
A Zope/trunk/lib/python/Testing/ZopeTestCase/layer.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py
U Zope/trunk/lib/python/Testing/ZopeTestCase/zopedoctest/testLayerExtraction.py
-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/doc/CHANGES.txt 2007-10-13 16:15:38 UTC (rev 80864)
@@ -71,6 +71,9 @@
Features added
+ - Testing.ZopeTestCase: Introduced a "ZopeLite" test layer, making it
+ possible to mix ZTC and non-ZTC tests much more freely.
+
- Testing/custom_zodb.py: added support use a different storage other
than DemoStorage. A dedicated FileStorage can be mount by setting the
$TEST_FILESTORAGE environment variable to a custom Data.fs file. A
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/ZopeLite.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -26,6 +26,7 @@
"""
import os, sys, time
+import layer
# Allow code to tell it is run by the test framework
os.environ['ZOPETESTCASE'] = '1'
@@ -105,7 +106,12 @@
_patched = False
+ at layer.onsetup
def _apply_patches():
+ # Do not patch a running Zope
+ if Zope2._began_startup:
+ return
+
# Avoid expensive product import
def null_import_products(): pass
OFS.Application.import_products = null_import_products
@@ -126,10 +132,18 @@
global _patched
_patched = True
-# Do not patch a running Zope
-if not Zope2._began_startup:
- _apply_patches()
+_apply_patches()
+_theApp = None
+
+ at layer.onsetup
+def _startup():
+ global _theApp
+ _theApp = Zope2.app()
+
+# Start ZopeLite
+_startup()
+
# Allow test authors to install Zope products into the test environment. Note
# that installProduct() must be called at module level -- never from tests.
from OFS.Application import get_folder_permissions, get_products
@@ -137,7 +151,6 @@
from OFS.Folder import Folder
import Products
-_theApp = Zope2.app()
_installedProducts = {}
_installedPackages = {}
@@ -145,7 +158,13 @@
'''Checks if a product can be found along Products.__path__'''
return name in [n[1] for n in get_products()]
+ at layer.onsetup
def installProduct(name, quiet=0):
+ '''Installs a Zope product at layer setup time.'''
+ quiet = 1 # Ignore argument
+ _installProduct(name, quiet)
+
+def _installProduct(name, quiet=0):
'''Installs a Zope product.'''
start = time.time()
meta_types = []
@@ -170,8 +189,14 @@
'''Checks if a package has been registered with five:registerPackage.'''
return name in [m.__name__ for m in getattr(Products, '_registered_packages', [])]
+ at layer.onsetup
def installPackage(name, quiet=0):
- '''Installs a registered Python package like a Zope product.'''
+ '''Installs a registered Python package at layer setup time.'''
+ quiet = 1 # Ignore argument
+ _installPackage(name, quiet)
+
+def _installPackage(name, quiet=0):
+ '''Installs a registered Python package.'''
start = time.time()
if _patched and not _installedPackages.has_key(name):
for module, init_func in getattr(Products, '_packages_to_initialize', []):
@@ -187,28 +212,9 @@
else:
if not quiet: _print('Installing %s ... NOT FOUND\n' % name)
-def _load_control_panel():
- # Loading the Control_Panel of an existing ZODB may take
- # a while; print another dot if it does.
- start = time.time()
- max = (start - _start) / 4
- _exec('_theApp.Control_Panel')
- _theApp.Control_Panel
- if (time.time() - start) > max:
- _write('.')
+installProduct('PluginIndexes', 1) # Must install first
+installProduct('OFSP', 1)
-def _install_products():
- installProduct('PluginIndexes', 1) # Must install first
- installProduct('OFSP', 1)
- #installProduct('ExternalMethod', 1)
- #installProduct('ZSQLMethods', 1)
- #installProduct('ZGadflyDA', 1)
- #installProduct('MIMETools', 1)
- #installProduct('MailHost', 1)
-
-_load_control_panel()
-_install_products()
-
# So people can use ZopeLite.app()
app = Zope2.app
debug = Zope2.debug
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/__init__.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -17,6 +17,7 @@
import ZopeLite as Zope2
import utils
+import layer
from ZopeLite import hasProduct
from ZopeLite import installProduct
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/base.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/base.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/base.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -21,12 +21,12 @@
import utils
import interfaces
import connections
+import layer
from zope.interface import implements
from AccessControl.SecurityManagement import noSecurityManager
-
def app():
'''Opens a ZODB connection and returns the app object.'''
app = Zope2.app()
@@ -34,18 +34,20 @@
connections.register(app)
return app
+
def close(app):
'''Closes the app's ZODB connection.'''
connections.close(app)
-
class TestCase(unittest.TestCase, object):
'''Base test case for Zope testing
'''
implements(interfaces.IZopeTestCase)
+ layer = layer.ZopeLite
+
def afterSetUp(self):
'''Called after setUp() has completed. This is
far and away the most useful hook.
Copied: Zope/trunk/lib/python/Testing/ZopeTestCase/layer.py (from rev 80450, Zope/branches/shh-2.11-zopelitelayer/lib/python/Testing/ZopeTestCase/layer.py)
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/layer.py (rev 0)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/layer.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""ZopeLite layer
+
+$Id$
+"""
+
+_deferred_setup = []
+
+
+class ZopeLite:
+ '''The most base layer'''
+
+ @classmethod
+ def setUp(cls):
+ '''Brings up the ZopeLite environment.'''
+ for func, args, kw in _deferred_setup:
+ func(*args, **kw)
+
+ @classmethod
+ def tearDown(cls):
+ '''ZopeLite doesn't support tear down.
+
+ We don't raise NotImplementedError to avoid
+ triggering the testrunner's "resume layer"
+ mechanism.
+
+ See zope.testing.testrunner-layers-ntd.txt
+ '''
+
+ZopeLiteLayer = ZopeLite
+
+
+def onsetup(func):
+ '''Defers a function call to layer setup.
+ Used as a decorator.
+ '''
+ def deferred_func(*args, **kw):
+ _deferred_setup.append((func, args, kw))
+ return deferred_func
+
+
+def appcall(func):
+ '''Defers a function call to layer setup.
+ Used as a decorator.
+
+ In addition, this decorator implements the appcall
+ protocol:
+
+ * The decorated function expects 'app' as first argument.
+
+ * If 'app' is provided by the caller, the function is
+ called immediately.
+
+ * If 'app' is omitted or None, the 'app' argument is
+ provided by the decorator, and the function call is
+ deferred to ZopeLite layer setup.
+
+ Also see utils.appcall.
+ '''
+ def appcalled_func(*args, **kw):
+ if args and args[0] is not None:
+ return func(*args, **kw)
+ if kw.get('app') is not None:
+ return func(*args, **kw)
+ def caller(*args, **kw):
+ import utils
+ utils.appcall(func, *args, **kw)
+ _deferred_setup.append((caller, args, kw))
+ return appcalled_func
+
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testShoppingCart.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -30,23 +30,31 @@
from Testing import ZopeTestCase
+from Testing.ZopeTestCase import layer
+from Testing.ZopeTestCase import utils
+from Testing.ZopeTestCase import transaction
+
from Globals import SOFTWARE_HOME
examples_path = os.path.join(SOFTWARE_HOME, '..', '..', 'skel', 'import', 'Examples.zexp')
examples_path = os.path.abspath(examples_path)
-# Open ZODB connection
-app = ZopeTestCase.app()
+class ShoppingCartLayer(layer.ZopeLite):
-# Set up sessioning objects
-ZopeTestCase.utils.setupCoreSessions(app)
+ @classmethod
+ def setUp(cls):
+ # Set up sessioning objects
+ utils.appcall(utils.setupCoreSessions)
-# Set up example applications
-if not hasattr(app, 'Examples'):
- ZopeTestCase.utils.importObjectFromFile(app, examples_path)
+ # Set up example applications
+ utils.appcall(utils.importObjectFromFile, examples_path, quiet=1)
-# Close ZODB connection
-ZopeTestCase.close(app)
+ @classmethod
+ def tearDown(cls):
+ def cleanup(app):
+ app._delObject('Examples')
+ transaction.commit()
+ utils.appcall(cleanup)
class DummyOrder:
@@ -63,6 +71,8 @@
_setup_fixture = 0 # No default fixture
+ layer = ShoppingCartLayer
+
def afterSetUp(self):
self.cart = self.app.Examples.ShoppingCart
# Put SESSION object into REQUEST
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testWebserver.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -46,8 +46,7 @@
ZopeTestCase.utils.setupSiteErrorLog()
# Start the web server
-host, port = ZopeTestCase.utils.startZServer(4)
-folder_url = 'http://%s:%d/%s' %(host, port, ZopeTestCase.folder_name)
+ZopeTestCase.utils.startZServer()
class ManagementOpener(urllib.FancyURLopener):
@@ -55,6 +54,7 @@
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):
@@ -67,6 +67,8 @@
uf = self.folder.acl_users
uf.userFolderAddUser('manager', 'secret', ['Manager'], [])
+ self.folder_url = self.folder.absolute_url()
+
# A simple document
self.folder.addDTMLDocument('index_html', file='index_html called')
@@ -99,7 +101,7 @@
def testURLAccessPublicObject(self):
# Test web access to a public resource
urllib._urlopener = ManagementOpener()
- page = urllib.urlopen(folder_url+'/index_html').read()
+ page = urllib.urlopen(self.folder_url+'/index_html').read()
self.assertEqual(page, 'index_html called')
def testAccessProtectedObject(self):
@@ -110,7 +112,7 @@
def testURLAccessProtectedObject(self):
# Test web access to a protected resource
urllib._urlopener = ManagementOpener()
- page = urllib.urlopen(folder_url+'/secret_html').read()
+ page = urllib.urlopen(self.folder_url+'/secret_html').read()
self.assertEqual(page, 'secret_html called')
def testSecurityOfPublicObject(self):
@@ -125,7 +127,7 @@
# Test web security of a public resource
urllib._urlopener = UnauthorizedOpener()
try:
- urllib.urlopen(folder_url+'/index_html')
+ urllib.urlopen(self.folder_url+'/index_html')
except Unauthorized:
# Convert error to failure
self.fail('Unauthorized')
@@ -143,7 +145,7 @@
# Test web security of a protected resource
urllib._urlopener = UnauthorizedOpener()
try:
- urllib.urlopen(folder_url+'/secret_html')
+ urllib.urlopen(self.folder_url+'/secret_html')
except Unauthorized:
pass # Test passed
else:
@@ -161,14 +163,10 @@
def testURLModifyObject(self):
# Test a transaction that actually commits something
urllib._urlopener = ManagementOpener()
- page = urllib.urlopen(folder_url+'/index_html/change_title?title=Foo').read()
+ page = urllib.urlopen(self.folder_url+'/index_html/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.
@@ -182,7 +180,7 @@
# same connection as the main thread, allowing us to
# see changes made to 'index_html' right away.
urllib._urlopener = ManagementOpener()
- urllib.urlopen(folder_url+'/index_html/change_title?title=Foo')
+ urllib.urlopen(self.folder_url+'/index_html/change_title?title=Foo')
self.assertEqual(self.folder.index_html.title, 'Foo')
def testCanCommit(self):
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/testZODBCompat.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -25,7 +25,9 @@
from Testing import ZopeTestCase
-import transaction
+from Testing.ZopeTestCase import layer
+from Testing.ZopeTestCase import utils
+from Testing.ZopeTestCase import transaction
from AccessControl.Permissions import add_documents_images_and_files
from AccessControl.Permissions import delete_objects
@@ -34,7 +36,36 @@
folder_name = ZopeTestCase.folder_name
cutpaste_permissions = [add_documents_images_and_files, delete_objects]
+# Dummy object
+from OFS.SimpleItem import SimpleItem
+class DummyObject(SimpleItem):
+ id = 'dummy'
+ foo = None
+ _v_foo = None
+ _p_foo = None
+
+
+
+class ZODBCompatLayer(layer.ZopeLite):
+
+ @classmethod
+ def setUp(cls):
+ def setup(app):
+ app._setObject('dummy1', DummyObject())
+ app._setObject('dummy2', DummyObject())
+ transaction.commit()
+ utils.appcall(setup)
+
+ @classmethod
+ def tearDown(cls):
+ def cleanup(app):
+ app._delObject('dummy1')
+ app._delObject('dummy2')
+ transaction.commit()
+ utils.appcall(cleanup)
+
+
class TestCopyPaste(ZopeTestCase.ZopeTestCase):
def afterSetUp(self):
@@ -159,22 +190,6 @@
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())
-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
@@ -194,7 +209,9 @@
This testcase exploits the fact that test methods are sorted by name.
'''
-
+
+ layer = ZODBCompatLayer
+
def afterSetUp(self):
self.dummy = self.app.dummy1 # See above
@@ -256,6 +273,8 @@
This testcase exploits the fact that test methods are sorted by name.
'''
+ layer = ZODBCompatLayer
+
def afterSetUp(self):
self.dummy = self.app.dummy2 # See above
self.dummy.touchme = 1 # Tag, you're dirty
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/utils.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -23,16 +23,15 @@
import time
import random
import transaction
+import layer
-def setupCoreSessions(app=None):
+ at layer.appcall
+def setupCoreSessions(app):
'''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')
@@ -68,11 +67,9 @@
transaction.commit()
-def setupZGlobals(app=None):
+ at layer.appcall
+def setupZGlobals(app):
'''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
@@ -80,11 +77,9 @@
transaction.commit()
-def setupSiteErrorLog(app=None):
+ at layer.appcall
+def setupSiteErrorLog(app):
'''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
@@ -135,13 +130,13 @@
return _makerequest(app, stdout=stdout, environ=environ)
-def appcall(function, *args, **kw):
+def appcall(func, *args, **kw):
'''Calls a function passing 'app' as first argument.'''
from base import app, close
app = app()
args = (app,) + args
try:
- return function(*args, **kw)
+ return func(*args, **kw)
finally:
transaction.abort()
close(app)
Modified: Zope/trunk/lib/python/Testing/ZopeTestCase/zopedoctest/testLayerExtraction.py
===================================================================
--- Zope/trunk/lib/python/Testing/ZopeTestCase/zopedoctest/testLayerExtraction.py 2007-10-13 16:02:33 UTC (rev 80863)
+++ Zope/trunk/lib/python/Testing/ZopeTestCase/zopedoctest/testLayerExtraction.py 2007-10-13 16:15:38 UTC (rev 80864)
@@ -20,9 +20,10 @@
from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import ZopeDocTestSuite
from Testing.ZopeTestCase import transaction
+from Testing.ZopeTestCase import layer
-class TestLayer:
+class TestLayer(layer.ZopeLite):
"""
If the layer is extracted properly, we should see the following
variable
More information about the Zope-Checkins
mailing list