Commit 996ebaa9 authored by Hanno Schlichting's avatar Hanno Schlichting

Factored out Products.StandardCacheManagers

parent 3af382d3
......@@ -52,6 +52,7 @@ eggs =
Products.BTreeFolder2
Products.ExternalMethod
Products.PythonScripts
Products.StandardCacheManagers
Products.ZCTextIndex
Record
RestrictedPython
......
......@@ -37,9 +37,10 @@ Restructuring
database manager ZMI.
- Factored out the `Products.BTreeFolder2`, `Products.ExternalMethod`,
`Products.MIMETools`, `Products.OFSP` and `Products.PythonScripts` packages
into their own distributions. They will no longer be included by default in
Zope 2.14 but live on as independent add-ons.
`Products.MIMETools`, `Products.OFSP`, `Products.PythonScripts` and
`Products.StandardCacheManagers` packages into their own distributions. They
will no longer be included by default in Zope 2.14 but live on as independent
add-ons.
- Factored out the `Products.ZSQLMethods` into its own distribution. The
distribution also includes the `Shared.DC.ZRDB` code. The Zope2 distribution
......
......@@ -104,6 +104,7 @@ setup(name='Zope2',
'Products.MIMETools',
'Products.OFSP',
'Products.PythonScripts',
'Products.StandardCacheManagers',
],
include_package_data=True,
......
......@@ -14,6 +14,7 @@ Products.ExternalMethod = svn ^/Products.ExternalMethod/trunk
Products.MIMETools = svn ^/Products.MIMETools/trunk
Products.OFSP = svn ^/Products.OFSP/trunk
Products.PythonScripts = svn ^/Products.PythonScripts/trunk
Products.StandardCacheManagers = svn ^/Products.StandardCacheManagers/trunk
Products.ZCTextIndex = svn ^/Products.ZCTextIndex/trunk
Record = svn ^/Record/trunk
tempstorage = svn ^/tempstorage/trunk
......
Preface
=======
This document is intended for people interested in the internals of
RAMCacheManager, such as maintainers. It should be updated when any
significant changes are made to the RAMCacheManager.
$Id$
Introduction
===============
The caching framework does not interpret the data in any way, it acts
just as a general storage for data passed to it. It tries to check if
the data is pickleable though. IOW, only pickleable data is
cacheable.
The idea behind the RAMCacheManager is that it should be shared between
threads, so that the same objects are not cached in each thread. This
is achieved by storing the cache data structure itself as a module
level variable (RAMCacheManager.caches). This, of course, requires
locking on modifications of that data structure.
Each RAMCacheManager instance has one cache in RAMCacheManager.caches
dictionary. A unique __cacheid is generated when creating a cache
manager and it's used as a key for caches.
Object Hierarchy
================
RAMCacheManager
RAMCache
ObjectCacheEntries
CacheEntry
RAMCacheManager is a persistent placeful object. It is assigned a
unique __cacheid on its creation. It is then used as a key to look up
the corresponding RAMCache object in the global caches dictionary.
So, each RAMCacheManager has a single RAMCache related to it.
RAMCache is a volatile cache, unique for each RAMCacheManager. It is
shared among threads and does all the locking. It has a writelock.
No locking is done on reading though. RAMCache keeps a dictionary of
ObjectCacheEntries indexed by the physical path of a cached object.
ObjectCacheEntries is a container for cached values for a single object.
The values in it are indexed by a tuple of a view_name, interesting
request variables, and extra keywords passed to Cache.ZCache_set().
CacheEntry is a wrapper around a single cached value. It stores the
data itself, creation time, view_name and keeps the access count.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# 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
#
##############################################################################
'''
Some standard Zope cache managers from Digital Creations.
$Id$
'''
import RAMCacheManager
import AcceleratedHTTPCacheManager
def initialize(context):
context.registerClass(
RAMCacheManager.RAMCacheManager,
constructors = (RAMCacheManager.manage_addRAMCacheManagerForm,
RAMCacheManager.manage_addRAMCacheManager),
icon="cache.gif"
)
context.registerClass(
AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager,
constructors = (
AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManagerForm,
AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManager),
icon="cache.gif"
)
context.registerHelp()
<configure xmlns="http://namespaces.zope.org/zope">
<subscriber
for="Products.StandardCacheManagers.RAMCacheManager.RAMCacheManager
OFS.interfaces.IObjectClonedEvent"
handler="Products.StandardCacheManagers.subscribers.cloned" />
<subscriber
for="Products.StandardCacheManagers.RAMCacheManager.RAMCacheManager
zope.lifecycleevent.ObjectRemovedEvent"
handler="Products.StandardCacheManagers.subscribers.removed" />
<subscriber
for="Products.StandardCacheManagers.AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager
OFS.interfaces.IObjectClonedEvent"
handler="Products.StandardCacheManagers.subscribers.cloned" />
<subscriber
for="Products.StandardCacheManagers.AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager
zope.lifecycleevent.ObjectRemovedEvent"
handler="Products.StandardCacheManagers.subscribers.removed" />
</configure>
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add Accelerated HTTP Cache Manager',
)">
<form action="manage_addAcceleratedHTTPCacheManager" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var "manage_form_title(this(), _,
form_title='Add RAM Cache Manager',
)">
<form action="manage_addRAMCacheManager" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="manage_editProps" method="POST">
<dtml-with getSettings mapping>
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-optional">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40"
value="&dtml-title;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Interval (seconds)
</div>
</td>
<td align="left" valign="top">
<input type="text" name="interval" size="40"
value="&dtml-interval;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Cache anonymous <br />connections only?
</div>
</td>
<td align="left" valign="top">
<input type="checkbox" name="anonymous_only" value="1"<dtml-if
anonymous_only> checked="checked"</dtml-if> />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Notify URLs (via PURGE)
</div>
</td>
<td align="left" valign="top">
<textarea name="notify_urls:lines" rows="5" cols="30"><dtml-in
notify_urls>&dtml-sequence-item;</dtml-in></textarea>
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value="Save Changes" />
</div>
</td>
</tr>
</table>
</dtml-with>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
The <em>RAM Cache Manager</em> allows you to cache the result of
calling expensive objects, such as Python Scripts and External
Methods, in memory. Because it does <em>not</em> cache HTTP headers,
caching full web pages is generally not advised.
</p>
<form action="manage_editProps" method="POST">
<dtml-with getSettings mapping>
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-optional">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40"
value="&dtml-title;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
REQUEST variables
</div>
</td>
<td align="left" valign="top">
<textarea name="request_vars:lines" rows="5" cols="30"><dtml-in
request_vars>&dtml-sequence-item;
</dtml-in></textarea>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Threshold entries
</div>
</td>
<td align="left" valign="top">
<input type="text" name="threshold" size="40"
value="&dtml-threshold;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Maximum age of a cache entry (seconds)
</div>
</td>
<td align="left" valign="top">
<input type="text" name="max_age" size="40"
value="&dtml-max_age;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Cleanup interval (seconds)
</div>
</td>
<td align="left" valign="top">
<input type="text" name="cleanup_interval" size="40"
value="&dtml-cleanup_interval;" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value="Save Changes" />
</div>
</td>
</tr>
</table>
</dtml-with>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
Cache manager hits generally correspond to HTTP accelerator misses.
A hit is counted in the "authenticated hits" column even if headers
are only set for anonymous requests.
</p>
<dtml-if getCacheReport>
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr class="list-header">
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Path', 'path')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Anonymous hits', 'anon')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Authenticated hits', 'auth')">
</div>
</td>
</tr>
<dtml-in getCacheReport mapping>
<dtml-if sequence-odd>
<tr class="row-normal">
<dtml-else>
<tr class="row-hilite">
</dtml-if>
<td align="left" valign="top">
<div class="list-item">
<a href="&dtml-path;/ZCacheable_manage">&dtml-path;</a>
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-anon;
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-auth;
</div>
</td>
</tr>
</dtml-in>
</table>
<dtml-else>
<p class="form-text">
<strong>Nothing is in the cache.</strong>
</p>
</dtml-if>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
Memory usage is approximate. It is based on the pickled value of the
cached data. The cache is cleaned up by removing the least frequently
accessed entries since the last cleanup operation. The determination
is made using the <em>recent hits</em> counter.
</p>
<dtml-if getCacheReport>
<form method="post" action="manage_invalidate">
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr class="list-header">
<td align="left" valign="top" class="list-nav" width="16">
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Path', 'path')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Hits', 'hits')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Recent Hits', 'counter')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Misses', 'misses')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Memory', 'size')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Views', 'views')">
</div>
</td>
<td align="left" valign="top">
<div class="list-nav">
<dtml-var expr="sort_link('Entries', 'entries')">
</div>
</td>
</tr>
<dtml-in getCacheReport mapping>
<dtml-if sequence-odd>
<tr class="row-normal">
<dtml-else>
<tr class="row-hilite">
</dtml-if>
<td align="left" valign="top" width="16">
<input type="checkbox" name="paths:list" value="&dtml-path;" />
</td>
<td align="left" valign="top">
<div class="list-item">
<a href="&dtml-path;/ZCacheable_manage">&dtml-path;</a>
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-hits;
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-counter;
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-misses;
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-size;
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
<dtml-var expr="_.string.join(views, ', ')" html_quote>
</div>
</td>
<td align="left" valign="top">
<div class="list-item">
&dtml-entries;
</div>
</td>
</tr>
</dtml-in>
<tr>
<td width="16"> </td>
<td colspan="7">
<input type="submit" value=" Remove " />
</td>
</tr>
</table>
</form>
<dtml-else>
<p class="form-text">
<strong>Nothing is in the cache.</strong>
</p>
</dtml-if>
<dtml-var manage_page_footer>
Accelerated HTTP Cache Managers
The HTTP protocol provides for headers that can indicate to
downstream proxy caches, browser caches, and dedicated caches that
certain documents and images are cacheable. Most images, for example,
can safely be cached for a long time. Anonymous visits to most
primary pages can be cached as well.
An accelerated HTTP cache manager lets you control the headers that
get sent with the responses to requests so that downstream caches
will know what to cache and for how long. This allows you to reduce
the traffic to your site and handle larger loads than otherwise
possible. You can associate accelerated HTTP cache managers with
any kind of cacheable object that can be viewed through the web.
The main risk in using an accelerated HTTP cache manager involves
a part of a page setting headers that apply to the whole response.
If, for example, your home page contains three parts that are
cacheable and one of those parts is associated with an accelerated
HTTP cache manager, Zope will return the headers set by the part of
the page, making downstream caches think that the whole page should
be cached.
The workaround is simple: don't use an accelerated HTTP cache manager
with objects that make up parts of a page unless you really know
what you're doing.
There are some parameters available for accelerated HTTP cache managers.
The interval is the number of seconds the downstream caches should
cache the object. 3600 seconds, or one hour, is a good default.
If you find that some objects need one interval and other objects
should be set to another interval, use multiple cache managers.
If you set the *cache anonymous connections only* checkbox, you
will reduce the possibility of caching private data.
The *notify URLs* parameter allows you to specify the URLs of
specific downstream caches so they can receive invalidation messages
as 'PURGE' directives. Dedicated HTTP cache software such
as Squid will clear cached data for a given URL when receiving the
'PURGE' directive. (More details below.)
Simple statistics are provided. Remember that the only time Zope
receives a request that goes through an HTTP cache is when the
HTTP cache had a *miss*. So the hits seen by Zope correspond to
misses seen by the HTTP cache. To do traffic analysis, you should
consult the downstream HTTP caches.
When testing the accelerated HTTP cache manager, keep in mind that
the *reload* button on most browsers causes the 'Pragma: no-cache'
header to be sent, forcing HTTP caches to reload the page as well.
Try using telnet, netcat, or tcpwatch to observe the headers.
To allow Zope to execute the Squid PURGE directive, make sure the
following lines or the equivalent are in squid.conf (changing
'localhost' to the correct host name if Squid is on a different
machine)::
acl PURGE method purge
http_access allow localhost
http_access allow purge localhost
http_access deny purge
http_access deny all
RAM Cache Managers
The RAM cache manager allows you to cache the result of calling
expensive objects, such as Python Scripts and External Methods,
in memory. It provides access statistics and simple configuration
options.
Not all objects are appropriate for use with a RAM Cache Manager.
See the **caveats** section below.
Storing the result in memory results in the fastest possible cache
retrieval, but carries some risks. Unconstrained, it can consume too
much RAM. And it doesn't reduce network traffic, it only helps
Zope return a result more quickly.
Fortunately, RAM cache managers have tunable parameters. You can
configure the threshold on the number of entries that should be in
the cache, which defaults to 1000. Reduce it if the cache is taking
up too much memory or increase it if entries are being cleared too
often.
You can also configure the cleanup interval. If the RAM cache is
fluctuating too much in memory usage, reduce the cleanup interval.
Finally, you can configure the list of REQUEST variables that will
be used in the cache key. This can be a simple and effective way
to distinguish requests from authenticated versus anonymous users
or those with session cookies.
If you find that some of your objects need certain cache parameters
while others need somewhat different parameters, create multiple
RAM cache managers.
The 'Statistics' tab allows you to view a summary of the contents
of the cache. Click the column headers to re-sort the list, twice
to sort backwards. You can use the statistics to gauge the
benefit of caching each of your objects. For a given object, if
the number of hits is less than or not much greater than the number
of misses, you probably need to re-evaluate how that object is
cached.
Caveats
You should generally not cache the following with RAM Cache Manager:
* Images
* Files
* Complete web pages
Although Zope does not prevent you from doing so,
it generally does not make sense to associate any of these objects
with a RAM cache manager. The cache will simply not cache image or
file data, since the data is already available in RAM.
In addition, be careful with complete web pages.
The problem is that most cacheable objects will cache only their
return value; important out-of-band information such as the HTTP
response code is typically not cached. For example, if you cache
a page which calls RESPONSE.redirect(), a client that gets
a cache hit will see an HTTP 200 response code instead
of the redirect.
For all of the above objects, another kind of cache manager, an
*accelerated HTTP cache manager*, is available and more suitable.
##############################################################################
#
# Copyright (c) 2010 Zope Foundation 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.
#
##############################################################################
""" subscribers to events affecting StandardCacheManagers
"""
def cloned(obj, event):
"""
Reset the Id of the module level cache so the clone gets a different cache
than its source object
"""
obj._resetCacheId()
def removed(obj, event):
obj._remove_data()
##############################################################################
#
# Copyright (c) 2005 Zope Foundation 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.
#
##############################################################################
""" Unit tests for StandardCacheManagers product.
$Id$
"""
##############################################################################
#
# Copyright (c) 2005 Zope Foundation 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.
#
##############################################################################
""" Unit tests for AcceleratedCacheManager module.
$Id$
"""
import unittest
from Products.StandardCacheManagers.AcceleratedHTTPCacheManager \
import AcceleratedHTTPCache, AcceleratedHTTPCacheManager
class DummyObject:
def __init__(self, path='/path/to/object', urlpath=None):
self.path = path
if urlpath is None:
self.urlpath = path
else:
self.urlpath = urlpath
def getPhysicalPath(self):
return tuple(self.path.split('/'))
def absolute_url_path(self):
return self.urlpath
class MockResponse:
status = '200'
reason = "who knows, I'm just a mock"
def MockConnectionClassFactory():
# Returns both a class that mocks an HTTPConnection,
# and a reference to a data structure where it logs requests.
request_log = []
class MockConnection:
# Minimal replacement for httplib.HTTPConnection.
def __init__(self, host):
self.host = host
self.request_log = request_log
def request(self, method, path):
self.request_log.append({'method':method,
'host':self.host,
'path':path,})
def getresponse(self):
return MockResponse()
return MockConnection, request_log
class AcceleratedHTTPCacheTests(unittest.TestCase):
def _getTargetClass(self):
return AcceleratedHTTPCache
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_PURGE_passes_Host_header(self):
_TO_NOTIFY = 'localhost:1888'
cache = self._makeOne()
cache.notify_urls = ['http://%s' % _TO_NOTIFY]
cache.connection_factory, requests = MockConnectionClassFactory()
dummy = DummyObject()
cache.ZCache_invalidate(dummy)
self.assertEqual(len(requests), 1)
result = requests[-1]
self.assertEqual(result['method'], 'PURGE')
self.assertEqual(result['host'], _TO_NOTIFY)
self.assertEqual(result['path'], dummy.path)
def test_multiple_notify(self):
cache = self._makeOne()
cache.notify_urls = ['http://foo', 'bar', 'http://baz/bat']
cache.connection_factory, requests = MockConnectionClassFactory()
cache.ZCache_invalidate(DummyObject())
self.assertEqual(len(requests), 3)
self.assertEqual(requests[0]['host'], 'foo')
self.assertEqual(requests[1]['host'], 'bar')
self.assertEqual(requests[2]['host'], 'baz')
cache.ZCache_invalidate(DummyObject())
self.assertEqual(len(requests), 6)
def test_vhost_purging_1447(self):
# Test for http://www.zope.org/Collectors/Zope/1447
cache = self._makeOne()
cache.notify_urls = ['http://foo.com']
cache.connection_factory, requests = MockConnectionClassFactory()
dummy = DummyObject(urlpath='/published/elsewhere')
cache.ZCache_invalidate(dummy)
# That should fire off two invalidations,
# one for the physical path and one for the abs. url path.
self.assertEqual(len(requests), 2)
self.assertEqual(requests[0]['path'], dummy.absolute_url_path())
self.assertEqual(requests[1]['path'], dummy.path)
class CacheManagerTests(unittest.TestCase):
def _getTargetClass(self):
return AcceleratedHTTPCacheManager
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def _makeContext(self):
from OFS.Folder import Folder
root = Folder()
root.getPhysicalPath = lambda: ('', 'some_path',)
cm_id = 'http_cache'
manager = self._makeOne(cm_id)
root._setObject(cm_id, manager)
manager = root[cm_id]
return root, manager
def test_add(self):
# ensure __init__ doesn't raise errors.
root, cachemanager = self._makeContext()
def test_ZCacheManager_getCache(self):
root, cachemanager = self._makeContext()
cache = cachemanager.ZCacheManager_getCache()
self.assert_(isinstance(cache, AcceleratedHTTPCache))
def test_getSettings(self):
root, cachemanager = self._makeContext()
settings = cachemanager.getSettings()
self.assert_('anonymous_only' in settings.keys())
self.assert_('interval' in settings.keys())
self.assert_('notify_urls' in settings.keys())
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(AcceleratedHTTPCacheTests))
suite.addTest(unittest.makeSuite(CacheManagerTests))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2010 Zope Foundation 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.
#
##############################################################################
""" Unit tests for AcceleratedCacheManager module.
$Id$
"""
import unittest
import transaction
import zope.component
from zope.component import testing as componenttesting
from zope.component import eventtesting
from AccessControl import SecurityManager
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from OFS.Folder import Folder
from OFS.tests.testCopySupport import CopySupportTestBase
from OFS.tests.testCopySupport import UnitTestSecurityPolicy
from OFS.tests.testCopySupport import UnitTestUser
from Zope2.App import zcml
from Products.StandardCacheManagers.RAMCacheManager import RAMCacheManager
from Products.StandardCacheManagers.AcceleratedHTTPCacheManager \
import AcceleratedHTTPCacheManager
import Products.StandardCacheManagers
CACHE_META_TYPES = tuple(dict(name=instance_class.meta_type,
action='unused_constructor_name',
permission="Add %ss" % instance_class.meta_type)
for instance_class in (RAMCacheManager,
AcceleratedHTTPCacheManager)
)
class CacheManagerLocationTests(CopySupportTestBase):
_targetClass = None
def _makeOne(self, *args, **kw):
return self._targetClass(*args, **kw)
def setUp( self ):
componenttesting.setUp()
eventtesting.setUp()
zcml.load_config('meta.zcml', zope.component)
zcml.load_config('configure.zcml', Products.StandardCacheManagers)
folder1, folder2 = self._initFolders()
folder1.all_meta_types = folder2.all_meta_types = CACHE_META_TYPES
self.folder1 = folder1
self.folder2 = folder2
self.policy = UnitTestSecurityPolicy()
self.oldPolicy = SecurityManager.setSecurityPolicy( self.policy )
cm_id = 'cache'
manager = self._makeOne(cm_id)
self.folder1._setObject(cm_id, manager)
self.cachemanager = self.folder1[cm_id]
transaction.savepoint(optimistic=True)
newSecurityManager( None, UnitTestUser().__of__( self.root ) )
CopySupportTestBase.setUp(self)
def tearDown( self ):
noSecurityManager()
SecurityManager.setSecurityPolicy( self.oldPolicy )
del self.oldPolicy
del self.policy
del self.folder2
del self.folder1
self._cleanApp()
componenttesting.tearDown()
CopySupportTestBase.tearDown(self)
def test_cache_differs_on_copy(self):
# ensure copies don't hit the same cache
cache = self.cachemanager.ZCacheManager_getCache()
cachemanager_copy = self.folder2.manage_clone(self.cachemanager,
'cache_copy')
cache_copy = cachemanager_copy.ZCacheManager_getCache()
self.assertNotEqual(cache, cache_copy)
def test_cache_remains_on_move(self):
# test behaviour of cache on move.
# NOTE: This test verifies current behaviour, but there is no actual
# need for cache managers to maintain the same cache on move.
# if physical path starts being used as a cache key, this test might
# need to be fixed.
cache = self.cachemanager.ZCacheManager_getCache()
cut = self.folder1.manage_cutObjects(['cache'])
self.folder2.manage_pasteObjects(cut)
cachemanager_moved = self.folder2['cache']
cache_moved = cachemanager_moved.ZCacheManager_getCache()
self.assertEqual(cache, cache_moved)
def test_cache_deleted_on_remove(self):
old_cache = self.cachemanager.ZCacheManager_getCache()
self.folder1.manage_delObjects(['cache'])
new_cache = self.cachemanager.ZCacheManager_getCache()
self.assertNotEqual(old_cache, new_cache)
class AcceleratedHTTPCacheManagerLocationTests(CacheManagerLocationTests):
_targetClass = AcceleratedHTTPCacheManager
class RamCacheManagerLocationTests(CacheManagerLocationTests):
_targetClass = RAMCacheManager
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(AcceleratedHTTPCacheManagerLocationTests))
suite.addTest(unittest.makeSuite(RamCacheManagerLocationTests))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment