Commit ba3c0022 authored by Jason Madden's avatar Jason Madden

100% coverage for picklecache.

parent e3a9b13a
...@@ -22,9 +22,11 @@ from persistent.interfaces import IPickleCache ...@@ -22,9 +22,11 @@ from persistent.interfaces import IPickleCache
from persistent.interfaces import OID_TYPE from persistent.interfaces import OID_TYPE
from persistent.interfaces import UPTODATE from persistent.interfaces import UPTODATE
from persistent import Persistent from persistent import Persistent
from persistent.persistence import _estimated_size_in_24_bits
# Tests may modify this to add additional types # Tests may modify this to add additional types
_CACHEABLE_TYPES = (type, Persistent) _CACHEABLE_TYPES = (type, Persistent)
_SWEEPABLE_TYPES = (Persistent,)
class RingNode(object): class RingNode(object):
# 32 byte fixed size wrapper. # 32 byte fixed size wrapper.
...@@ -38,7 +40,7 @@ def _sweeping_ring(f): ...@@ -38,7 +40,7 @@ def _sweeping_ring(f):
def locked(self, *args, **kwargs): def locked(self, *args, **kwargs):
self._is_sweeping_ring = True self._is_sweeping_ring = True
try: try:
f(self, *args, **kwargs) return f(self, *args, **kwargs)
finally: finally:
self._is_sweeping_ring = False self._is_sweeping_ring = False
return locked return locked
...@@ -114,7 +116,7 @@ class PickleCache(object): ...@@ -114,7 +116,7 @@ class PickleCache(object):
raise ValueError('A different object already has the same oid') raise ValueError('A different object already has the same oid')
# Match the C impl: it requires a jar # Match the C impl: it requires a jar
jar = getattr(value, '_p_jar', None) jar = getattr(value, '_p_jar', None)
if jar is None and type(value) is not type: if jar is None and not isinstance(value, type):
raise ValueError("Cached object jar missing") raise ValueError("Cached object jar missing")
# It also requires that it cannot be cached more than one place # It also requires that it cannot be cached more than one place
existing_cache = getattr(jar, '_cache', None) existing_cache = getattr(jar, '_cache', None)
...@@ -162,7 +164,7 @@ class PickleCache(object): ...@@ -162,7 +164,7 @@ class PickleCache(object):
# accessess during sweeping, such as with an # accessess during sweeping, such as with an
# overridden _p_deactivate, don't mutate the ring # overridden _p_deactivate, don't mutate the ring
# because that could leave it inconsistent # because that could leave it inconsistent
return return False # marker return for tests
node = self.ring.next node = self.ring.next
while node is not self.ring and node.object._p_oid != oid: while node is not self.ring and node.object._p_oid != oid:
node = node.next node = node.next
...@@ -220,12 +222,14 @@ class PickleCache(object): ...@@ -220,12 +222,14 @@ class PickleCache(object):
target2 = size - 1 - (size // self.drain_resistance) target2 = size - 1 - (size // self.drain_resistance)
if target2 < target: if target2 < target:
target = target2 target = target2
self._sweep(target, self.cache_size_bytes) # return value for testing
return self._sweep(target, self.cache_size_bytes)
def full_sweep(self, target=None): def full_sweep(self, target=None):
""" See IPickleCache. """ See IPickleCache.
""" """
self._sweep(0) # return value for testing
return self._sweep(0)
minimize = full_sweep minimize = full_sweep
...@@ -355,8 +359,8 @@ class PickleCache(object): ...@@ -355,8 +359,8 @@ class PickleCache(object):
if (self._persistent_deactivate_ran if (self._persistent_deactivate_ran
# Test-cases sneak in non-Persistent objects, sigh, so naturally # Test-cases sneak in non-Persistent objects, sigh, so naturally
# they don't cooperate (without this check a bunch of test_picklecache # they don't cooperate (without this check a bunch of test_picklecache
#breaks) # breaks)
or not isinstance(node.object, Persistent)): or not isinstance(node.object, _SWEEPABLE_TYPES)):
ejected += 1 ejected += 1
self.__remove_from_ring(node) self.__remove_from_ring(node)
node = node.next node = node.next
...@@ -394,8 +398,3 @@ class PickleCache(object): ...@@ -394,8 +398,3 @@ class PickleCache(object):
node.object = None node.object = None
node.prev.next, node.next.prev = node.next, node.prev node.prev.next, node.next.prev = node.next, node.prev
self.non_ghost_count -= 1 self.non_ghost_count -= 1
def _estimated_size_in_24_bits(value):
if value > 1073741696:
return 16777215
return (value//64) + 1
...@@ -858,6 +858,131 @@ class PickleCacheTests(unittest.TestCase): ...@@ -858,6 +858,131 @@ class PickleCacheTests(unittest.TestCase):
self.assertEqual(typ, 'DummyPersistent') self.assertEqual(typ, 'DummyPersistent')
self.assertEqual(state, GHOST) self.assertEqual(state, GHOST)
def test_init_with_cacheless_jar(self):
# Sometimes ZODB tests pass objects that don't
# have a _cache
class Jar(object):
was_set = False
def __setattr__(self, name, value):
if name == '_cache':
object.__setattr__(self, 'was_set', True)
raise AttributeError(name)
jar = Jar()
self._makeOne(jar)
self.assertTrue(jar.was_set)
def test_setting_non_persistent_item(self):
cache = self._makeOne()
try:
cache[None] = object()
except TypeError as e:
self.assertEqual(str(e), "Cache values must be persistent objects.")
else:
self.fail("Should raise TypeError")
def test_setting_without_jar(self):
cache = self._makeOne()
p = self._makePersist(jar=None)
try:
cache[p._p_oid] = p
except ValueError as e:
self.assertEqual(str(e), "Cached object jar missing")
else:
self.fail("Should raise ValueError")
def test_setting_already_cached(self):
cache1 = self._makeOne()
p = self._makePersist(jar=cache1.jar)
cache1[p._p_oid] = p
cache2 = self._makeOne()
try:
cache2[p._p_oid] = p
except ValueError as e:
self.assertEqual(str(e), "Object already in another cache")
else:
self.fail("Should raise value error")
def test_cannot_update_mru_while_already_locked(self):
cache = self._makeOne()
cache._is_sweeping_ring = True
updated = cache.mru(None)
self.assertFalse(updated)
def test_update_object_size_estimation_simple(self):
cache = self._makeOne()
p = self._makePersist(jar=cache.jar)
cache[p._p_oid] = p
# It accesses the private version directory to bypass
# the bit conversion
# Note that the _p_estimated_size is set *after*
# the update call is made in ZODB's serialize
p._Persistent__size = 0
cache.update_object_size_estimation(p._p_oid, 2)
self.assertEqual(cache.total_estimated_size, 64)
# A missing object does nothing
cache.update_object_size_estimation(None, 2)
self.assertEqual(cache.total_estimated_size, 64)
def test_cache_size(self):
size = 42
cache = self._makeOne(target_size=size)
self.assertEqual(cache.cache_size, size)
cache.cache_size = 64
self.assertEqual(cache.cache_size, 64)
def test_sweep_empty(self):
cache = self._makeOne()
self.assertEqual(cache.incrgc(), 0)
def test_sweep_of_non_deactivating_object(self):
cache = self._makeOne()
p = self._makePersist(jar=cache.jar)
p._p_state = 0 # non-ghost, get in the ring
cache[p._p_oid] = p
def bad_deactivate():
"Doesn't call super, for it's own reasons, so can't be ejected"
return
p._p_deactivate = bad_deactivate
import persistent.picklecache
sweep_types = persistent.picklecache._SWEEPABLE_TYPES
try:
persistent.picklecache._SWEEPABLE_TYPES = DummyPersistent
self.assertEqual(cache.full_sweep(), 0)
persistent.picklecache._SWEEPABLE_TYPES = sweep_types
del p._p_deactivate
self.assertEqual(cache.full_sweep(), 1)
finally:
persistent.picklecache._SWEEPABLE_TYPES = sweep_types
def test_invalidate_not_in_cache(self):
# A contrived test of corruption
cache = self._makeOne()
p = self._makePersist(jar=cache.jar)
p._p_state = 0 # non-ghost, get in the ring
cache[p._p_oid] = p
self.assertEqual(cache.ring.next.object, p)
cache.ring.next.object = None
# Nothing to test, just that it doesn't break
cache._invalidate(p._p_oid)
class DummyPersistent(object): class DummyPersistent(object):
......
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