##############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Test behavior of Connection plus cPickleCache."""
from zope.testing import doctest
from persistent import Persistent
import transaction
from ZODB.config import databaseFromString
class RecalcitrantObject(Persistent):
"""A Persistent object that will not become a ghost."""
deactivations = 0
def _p_deactivate(self):
self.__class__.deactivations += 1
def init(cls):
cls.deactivations = 0
init = classmethod(init)
class RegularObject(Persistent):
deactivations = 0
invalidations = 0
def _p_deactivate(self):
self.__class__.deactivations += 1
super(RegularObject, self)._p_deactivate()
def _p_invalidate(self):
self.__class__.invalidations += 1
super(RegularObject, self)._p_invalidate()
def init(cls):
cls.deactivations = 0
cls.invalidations = 0
init = classmethod(init)
class CacheTests:
def test_cache(self):
r"""Test basic cache methods.
Let's start with a clean transaction
>>> transaction.abort()
>>> RegularObject.init()
>>> db = databaseFromString("\n"
... "cache-size 4\n"
... "\n"
... "")
>>> cn = db.open()
>>> r = cn.root()
>>> L = []
>>> for i in range(5):
... o = RegularObject()
... L.append(o)
... r[i] = o
>>> transaction.commit()
After committing a transaction and calling cacheGC(), there
should be cache-size (4) objects in the cache. One of the
RegularObjects was deactivated.
>>> cn._cache.ringlen()
4
>>> RegularObject.deactivations
1
If we explicitly activate the objects again, the ringlen
should go back up to 5.
>>> for o in L:
... o._p_activate()
>>> cn._cache.ringlen()
5
>>> cn.cacheGC()
>>> cn._cache.ringlen()
4
>>> RegularObject.deactivations
2
>>> cn.cacheMinimize()
>>> cn._cache.ringlen()
0
>>> RegularObject.deactivations
6
If we activate all the objects again and mark one as modified,
then the one object should not be deactivated even by a
minimize.
>>> for o in L:
... o._p_activate()
>>> o.attr = 1
>>> cn._cache.ringlen()
5
>>> cn.cacheMinimize()
>>> cn._cache.ringlen()
1
>>> RegularObject.deactivations
10
Clean up
>>> transaction.abort()
"""
def test_cache_gc_recalcitrant(self):
r"""Test that a cacheGC() call will return.
It's possible for a particular object to ignore the
_p_deactivate() call. We want to check several things in this
case. The cache should called the real _p_deactivate() method
not the one provided by Persistent. The cacheGC() call should
also return when it's looked at each item, regardless of whether
it became a ghost.
>>> RecalcitrantObject.init()
>>> db = databaseFromString("\n"
... "cache-size 4\n"
... "\n"
... "")
>>> cn = db.open()
>>> r = cn.root()
>>> L = []
>>> for i in range(5):
... o = RecalcitrantObject()
... L.append(o)
... r[i] = o
>>> transaction.commit()
>>> [o._p_state for o in L]
[0, 0, 0, 0, 0]
The Connection calls cacheGC() after it commits a transaction.
Since the cache will now have more objects that it's target size,
it will call _p_deactivate() on each RecalcitrantObject.
>>> RecalcitrantObject.deactivations
5
>>> [o._p_state for o in L]
[0, 0, 0, 0, 0]
An explicit call to cacheGC() has the same effect.
>>> cn.cacheGC()
>>> RecalcitrantObject.deactivations
10
>>> [o._p_state for o in L]
[0, 0, 0, 0, 0]
"""
def test_cache_on_abort(self):
r"""Test that the cache handles transaction abort correctly.
>>> RegularObject.init()
>>> db = databaseFromString("\n"
... "cache-size 4\n"
... "\n"
... "")
>>> cn = db.open()
>>> r = cn.root()
>>> L = []
>>> for i in range(5):
... o = RegularObject()
... L.append(o)
... r[i] = o
>>> transaction.commit()
>>> RegularObject.deactivations
1
Modify three of the objects and verify that they are
deactivated when the transaction aborts.
>>> for i in range(0, 5, 2):
... L[i].attr = i
>>> [L[i]._p_state for i in range(0, 5, 2)]
[1, 1, 1]
>>> cn._cache.ringlen()
5
>>> transaction.abort()
>>> cn._cache.ringlen()
2
>>> RegularObject.deactivations
4
"""
def test_suite():
return doctest.DocTestSuite()