diff --git a/CHANGES.rst b/CHANGES.rst
index c13157e..c68406c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,10 +1,20 @@
 ``persistent`` Changelog
 ========================
 
-4.4.4 (unreleased)
+4.5.0 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Fully test the C implementation of the PickleCache, and fix
+  discrepancies between it and the Python implementation:
+
+  - The C implementation now raises ``ValueError`` instead of
+    ``AssertionError`` for certain types of bad inputs.
+  - The Python implementation uses the C wording for error messages.
+  - The C implementation properly implements ``IPickleCache``; methods
+    unique to the Python implementation were moved to
+    ``IExtendedPickleCache``.
+  - The Python implementation raises ``AttributeError`` if a
+    persistent class doesn't have a ``p_jar`` attribute.
 
 
 4.4.3 (2018-10-22)
diff --git a/persistent/__init__.py b/persistent/__init__.py
index e803f4c..844bda6 100644
--- a/persistent/__init__.py
+++ b/persistent/__init__.py
@@ -30,6 +30,7 @@ __all__ = [
 ]
 from persistent._compat import PURE_PYTHON
 from persistent.interfaces import IPersistent
+from persistent.interfaces import IPickleCache
 
 import persistent.timestamp as TimeStamp
 
@@ -48,6 +49,7 @@ else:
     # Note that the Python version already does this.
     from zope.interface import classImplements
     classImplements(_cPersistence.Persistent, IPersistent)
+    classImplements(_cPickleCache.PickleCache, IPickleCache)
 
 
 _persistence = pyPersistence if PURE_PYTHON or _cPersistence is None else _cPersistence
diff --git a/persistent/cPickleCache.c b/persistent/cPickleCache.c
index 496a8c8..8432301 100644
--- a/persistent/cPickleCache.c
+++ b/persistent/cPickleCache.c
@@ -727,7 +727,7 @@ cc_new_ghost(ccobject *self, PyObject *args)
     Py_DECREF(tmp);
     if (tmp != Py_None)
     {
-        PyErr_SetString(PyExc_AssertionError,
+        PyErr_SetString(PyExc_ValueError,
                         "New ghost object must not have an oid");
         return NULL;
     }
@@ -739,7 +739,7 @@ cc_new_ghost(ccobject *self, PyObject *args)
     Py_DECREF(tmp);
     if (tmp != Py_None)
     {
-        PyErr_SetString(PyExc_AssertionError,
+        PyErr_SetString(PyExc_ValueError,
                         "New ghost object must not have a jar");
         return NULL;
     }
@@ -748,7 +748,7 @@ cc_new_ghost(ccobject *self, PyObject *args)
     if (tmp)
     {
         Py_DECREF(tmp);
-        PyErr_SetString(PyExc_AssertionError,
+        PyErr_SetString(PyExc_ValueError,
                         "The given oid is already in the cache");
         return NULL;
     }
diff --git a/persistent/interfaces.py b/persistent/interfaces.py
index 61ed594..b91837f 100644
--- a/persistent/interfaces.py
+++ b/persistent/interfaces.py
@@ -439,12 +439,6 @@ class IPickleCache(Interface):
         o Return 'default' if not found.
         """
 
-    def mru(oid):
-        """ Move the element corresonding to 'oid' to the head.
-
-        o Raise KeyError if no element is found.
-        """
-
     def __len__():
         """ -> the number of OIDs in the cache.
         """
@@ -516,23 +510,6 @@ class IPickleCache(Interface):
         If 'oid' is already in the cache, raise.
         """
 
-    def reify(to_reify):
-        """ Reify the indicated objects.
-
-        o If 'to_reify' is a string, treat it as an OID.
-
-        o Otherwise, iterate over it as a sequence of OIDs.
-
-        o For each OID, if present in 'data' and in GHOST state:
-
-            o Call '_p_activate' on the object.
-
-            o Add it to the ring.
-
-        o If any OID is present but not in GHOST state, skip it.
-
-        o Raise KeyErrory if any OID is not present.
-        """
 
     def invalidate(to_invalidate):
         """ Invalidate the indicated objects.
@@ -566,3 +543,33 @@ class IPickleCache(Interface):
                                         'ringlen?')
     cache_data = Attribute("Property:  copy of our 'data' dict")
     cache_klass_count = Attribute("Property: len of 'persistent_classes'")
+
+
+class IExtendedPickleCache(IPickleCache):
+    """
+    Extra operations for a pickle cache.
+    """
+
+    def reify(to_reify):
+        """ Reify the indicated objects.
+
+        o If 'to_reify' is a string, treat it as an OID.
+
+        o Otherwise, iterate over it as a sequence of OIDs.
+
+        o For each OID, if present in 'data' and in GHOST state:
+
+            o Call '_p_activate' on the object.
+
+            o Add it to the ring.
+
+        o If any OID is present but not in GHOST state, skip it.
+
+        o Raise KeyErrory if any OID is not present.
+        """
+
+    def mru(oid):
+        """ Move the element corresonding to 'oid' to the head.
+
+        o Raise KeyError if no element is found.
+        """
diff --git a/persistent/picklecache.py b/persistent/picklecache.py
index 9bcb573..db69c2e 100644
--- a/persistent/picklecache.py
+++ b/persistent/picklecache.py
@@ -18,7 +18,7 @@ import weakref
 from zope.interface import implementer
 
 from persistent.interfaces import GHOST
-from persistent.interfaces import IPickleCache
+from persistent.interfaces import IExtendedPickleCache
 from persistent.interfaces import OID_TYPE
 from persistent.interfaces import UPTODATE
 from persistent.persistence import Persistent
@@ -53,7 +53,7 @@ def _sweeping_ring(f):
 
 from .ring import Ring
 
-@implementer(IPickleCache)
+@implementer(IExtendedPickleCache)
 class PickleCache(object):
 
     total_estimated_size = 0
@@ -126,16 +126,17 @@ class PickleCache(object):
                 # Raise the same type of exception as the C impl with the same
                 # message.
                 raise ValueError('A different object already has the same oid')
-        # Match the C impl: it requires a jar
-        jar = getattr(value, '_p_jar', None)
-        if jar is None and not isinstance(value, type):
+        # Match the C impl: it requires a jar. Let this raise AttributeError
+        # if no jar is found.
+        jar = getattr(value, '_p_jar')
+        if jar is None:
             raise ValueError("Cached object jar missing")
         # It also requires that it cannot be cached more than one place
         existing_cache = getattr(jar, '_cache', None)
         if (existing_cache is not None
             and existing_cache is not self
             and existing_cache.data.get(oid) is not None):
-            raise ValueError("Object already in another cache")
+            raise ValueError("Cache values may only be in one cache.")
 
         if isinstance(value, type): # ZODB.persistentclass.PersistentMetaClass
             self.persistent_classes[oid] = value
@@ -301,7 +302,10 @@ class PickleCache(object):
 
             self.total_estimated_size += new_est_size_in_bytes
 
-    cache_drain_resistance = property(lambda self: self.drain_resistance)
+    cache_drain_resistance = property(
+        lambda self: self.drain_resistance,
+        lambda self, nv: setattr(self, 'drain_resistance', nv)
+    )
     cache_non_ghost_count = property(lambda self: self.non_ghost_count)
     cache_data = property(lambda self: dict(self.data.items()))
     cache_klass_count = property(lambda self: len(self.persistent_classes))

is cPickleCache tested?  
Looking at the tests in test_picklecache, it looks like they're only testing the Python version.

Am I missing something?
diff --git a/persistent/tests/test_picklecache.py b/persistent/tests/test_picklecache.py
index 87cf433..8cca494 100644
--- a/persistent/tests/test_picklecache.py
+++ b/persistent/tests/test_picklecache.py
@@ -19,11 +19,36 @@ import unittest
 
 from persistent.interfaces import UPTODATE
 
+# pylint:disable=protected-access,too-many-lines,too-many-public-methods
+
 _is_pypy = platform.python_implementation() == 'PyPy'
 _is_jython = 'java' in sys.platform
 
 _marker = object()
 
+def skipIfNoCExtension(o):
+    import persistent
+    return unittest.skipIf(
+        persistent._cPickleCache is None,
+        "The C extension is not available")(o)
+
+
+if _is_jython: # pragma: no cover
+    def with_deterministic_gc(f):
+        def test(self):
+            # pylint:disable=no-member
+            old_flags = gc.getMonitorGlobal()
+            gc.setMonitorGlobal(True)
+            try:
+                f(self, force_collect=True)
+            finally:
+                gc.setMonitorGlobal(old_flags)
+        return test
+else:
+    def with_deterministic_gc(f):
+        return f
+
+
 class PickleCacheTests(unittest.TestCase):
 
     # py2/3 compat
@@ -45,33 +70,42 @@ class PickleCacheTests(unittest.TestCase):
         from persistent.picklecache import PickleCache
         return PickleCache
 
+    def _getTargetInterface(self):
+        from persistent.interfaces import IPickleCache
+        return IPickleCache
+
     def _makeOne(self, jar=None, target_size=10):
         if jar is None:
             jar = DummyConnection()
         return self._getTargetClass()(jar, target_size)
 
-    def _makePersist(self, state=None, oid=b'foo', jar=_marker):
+    def _getDummyPersistentClass(self):
+        return DummyPersistent
+
+    def _makePersist(self, state=None, oid=b'foo', jar=_marker, kind=_marker):
         from persistent.interfaces import GHOST
 
         if state is None:
             state = GHOST
         if jar is _marker:
             jar = DummyConnection()
-        persist = DummyPersistent()
-        persist._p_state = state
+        kind = self._getDummyPersistentClass() if kind is _marker else kind
+        persist = kind()
+        try:
+            persist._p_state = state
+        except AttributeError:
+            pass
         persist._p_oid = oid
         persist._p_jar = jar
         return persist
 
     def test_class_conforms_to_IPickleCache(self):
         from zope.interface.verify import verifyClass
-        from persistent.interfaces import IPickleCache
-        verifyClass(IPickleCache, self._getTargetClass())
+        verifyClass(self._getTargetInterface(), self._getTargetClass())
 
     def test_instance_conforms_to_IPickleCache(self):
         from zope.interface.verify import verifyObject
-        from persistent.interfaces import IPickleCache
-        verifyObject(IPickleCache, self._makeOne())
+        verifyObject(self._getTargetInterface(), self._makeOne())
 
     def test_empty(self):
         cache = self._makeOne()
@@ -142,9 +176,9 @@ class PickleCacheTests(unittest.TestCase):
         self.assertEqual(len(items), 1)
         self.assertEqual(_len(cache.klass_items()), 0)
         self.assertEqual(items[0][0], KEY)
-        self.assertEqual(cache.ringlen(), 0)
-        self.assertTrue(items[0][1] is ghost)
-        self.assertTrue(cache[KEY] is ghost)
+        self.assertIs(items[0][1], ghost)
+        self.assertIs(cache[KEY], ghost)
+        return cache
 
     def test___setitem___mismatch_key_oid(self):
         KEY = b'uptodate'
@@ -177,18 +211,19 @@ class PickleCacheTests(unittest.TestCase):
         KEY = b'pclass'
         class pclass(object):
             _p_oid = KEY
-        cache = self._makeOne()
+            _p_jar = DummyConnection()
+        cache = self._makeOne(pclass._p_jar)
 
         cache[KEY] = pclass
 
         kitems = list(cache.klass_items())
         self.assertEqual(len(cache), 1)
-        self.assertEqual(_len(cache.items()), 0)
         self.assertEqual(len(kitems), 1)
         self.assertEqual(kitems[0][0], KEY)
-        self.assertTrue(kitems[0][1] is pclass)
-        self.assertTrue(cache[KEY] is pclass)
-        self.assertTrue(cache.get(KEY) is pclass)
+        self.assertIs(kitems[0][1], pclass)
+        self.assertIs(cache[KEY], pclass)
+        self.assertIs(cache.get(KEY), pclass)
+        return cache
 
     def test___delitem___non_string_oid_raises_TypeError(self):
         cache = self._makeOne()
@@ -209,13 +244,14 @@ class PickleCacheTests(unittest.TestCase):
         cache = self._makeOne()
         class pclass(object):
             _p_oid = KEY
+            _p_jar = DummyConnection()
         cache = self._makeOne()
 
         cache[KEY] = pclass
         del cache[KEY]
-        self.assertTrue(cache.get(KEY, self) is self)
-        self.assertFalse(KEY in cache.persistent_classes)
+        self.assertIs(cache.get(KEY, self), self)
         self.assertEqual(cache.ringlen(), 0)
+        return cache, KEY
 
     def test___delitem___w_normal_object(self):
         KEY = b'uptodate'
@@ -268,106 +304,6 @@ class PickleCacheTests(unittest.TestCase):
         self.assertEqual(items[1][0], TWO)
         self.assertEqual(items[2][0], THREE)
 
-    def test_mru_nonesuch_raises_KeyError(self):
-        cache = self._makeOne()
-
-        self.assertRaises(KeyError, cache.mru, b'nonesuch')
-
-    def test_mru_normal(self):
-        ONE = b'one'
-        TWO = b'two'
-        THREE = b'three'
-        cache = self._makeOne()
-        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
-        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
-        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
-
-        cache.mru(TWO)
-
-        self.assertEqual(cache.ringlen(), 3)
-        items = cache.lru_items()
-        self.assertEqual(_len(items), 3)
-        self.assertEqual(items[0][0], ONE)
-        self.assertEqual(items[1][0], THREE)
-        self.assertEqual(items[2][0], TWO)
-
-    def test_mru_ghost(self):
-        from persistent.interfaces import GHOST
-
-        ONE = b'one'
-        TWO = b'two'
-        THREE = b'three'
-        cache = self._makeOne()
-        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
-        two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
-        # two must live to survive gc
-        self.assertIsNotNone(two)
-        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
-
-        cache.mru(TWO)
-
-        self.assertEqual(cache.ringlen(), 2)
-        items = cache.lru_items()
-        self.assertEqual(_len(items), 2)
-        self.assertEqual(items[0][0], ONE)
-        self.assertEqual(items[1][0], THREE)
-
-    def test_mru_was_ghost_now_active(self):
-        from persistent.interfaces import GHOST
-
-        ONE = b'one'
-        TWO = b'two'
-        THREE = b'three'
-        cache = self._makeOne()
-        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
-        two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
-        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
-
-        two._p_state = UPTODATE
-        cache.mru(TWO)
-
-        self.assertEqual(cache.ringlen(), 3)
-        items = cache.lru_items()
-        self.assertEqual(_len(items), 3)
-        self.assertEqual(items[0][0], ONE)
-        self.assertEqual(items[1][0], THREE)
-        self.assertEqual(items[2][0], TWO)
-
-    def test_mru_first(self):
-        ONE = b'one'
-        TWO = b'two'
-        THREE = b'three'
-        cache = self._makeOne()
-        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
-        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
-        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
-
-        cache.mru(ONE)
-
-        self.assertEqual(cache.ringlen(), 3)
-        items = cache.lru_items()
-        self.assertEqual(_len(items), 3)
-        self.assertEqual(items[0][0], TWO)
-        self.assertEqual(items[1][0], THREE)
-        self.assertEqual(items[2][0], ONE)
-
-    def test_mru_last(self):
-        ONE = b'one'
-        TWO = b'two'
-        THREE = b'three'
-        cache = self._makeOne()
-        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
-        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
-        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
-
-        cache.mru(THREE)
-
-        self.assertEqual(cache.ringlen(), 3)
-        items = cache.lru_items()
-        self.assertEqual(_len(items), 3)
-        self.assertEqual(items[0][0], ONE)
-        self.assertEqual(items[1][0], TWO)
-        self.assertEqual(items[2][0], THREE)
 
     def _numbered_oid(self, i):
         # Python 3.4 doesn't support % on bytes,
@@ -417,7 +353,7 @@ class PickleCacheTests(unittest.TestCase):
 
     def test_incrgc_w_smaller_drain_resistance(self):
         cache = self._makeOne()
-        cache.drain_resistance = 2
+        cache.cache_drain_resistance = 2
         self._populate_cache(cache)
         self.assertEqual(cache.cache_non_ghost_count, 100)
 
@@ -427,7 +363,7 @@ class PickleCacheTests(unittest.TestCase):
 
     def test_incrgc_w_larger_drain_resistance(self):
         cache = self._makeOne()
-        cache.drain_resistance = 2
+        cache.cache_drain_resistance = 2
         cache.cache_size = 90
         self._populate_cache(cache)
 
@@ -450,37 +386,6 @@ class PickleCacheTests(unittest.TestCase):
         for oid in oids:
             self.assertTrue(cache.get(oid) is None)
 
-    def test_full_sweep_w_sticky(self):
-        from persistent.interfaces import STICKY
-
-        cache = self._makeOne()
-        oids = self._populate_cache(cache, state_0=STICKY)
-        self.assertEqual(cache.cache_non_ghost_count, 100)
-
-        cache.full_sweep()
-        gc.collect() # banish the ghosts who are no longer in the ring
-
-        self.assertEqual(cache.cache_non_ghost_count, 1)
-
-        self.assertTrue(cache.get(oids[0]) is not None)
-        for oid in oids[1:]:
-            self.assertTrue(cache.get(oid) is None)
-
-    def test_full_sweep_w_changed(self):
-        from persistent.interfaces import CHANGED
-
-        cache = self._makeOne()
-        oids = self._populate_cache(cache, state_0=CHANGED)
-        self.assertEqual(cache.cache_non_ghost_count, 100)
-
-        cache.full_sweep()
-        gc.collect() # banish the ghosts who are no longer in the ring
-
-        self.assertEqual(cache.cache_non_ghost_count, 1)
-
-        self.assertTrue(cache.get(oids[0]) is not None)
-        for oid in oids[1:]:
-            self.assertTrue(cache.get(oid) is None)
 
     def test_minimize(self):
 
@@ -514,7 +419,7 @@ class PickleCacheTests(unittest.TestCase):
     def test_new_ghost_non_persistent_object(self):
 
         cache = self._makeOne()
-        with self.assertRaises(AttributeError):
+        with self.assertRaises((AttributeError, TypeError)):
             cache.new_ghost(b'123', object())
 
     def test_new_ghost_obj_already_has_oid(self):
@@ -532,7 +437,6 @@ class PickleCacheTests(unittest.TestCase):
             cache.new_ghost(b'123', candidate)
 
     def test_new_ghost_obj_already_in_cache(self):
-
         KEY = b'123'
         cache = self._makeOne()
         candidate = self._makePersist(oid=KEY)
@@ -541,35 +445,32 @@ class PickleCacheTests(unittest.TestCase):
         # (the C implementation doesn't allow it), so if we try to create
         # a ghost, we get the value error
         self.assertRaises(ValueError, cache.new_ghost, KEY, candidate)
-        candidate._p_oid = None
-        self.assertRaises(ValueError, cache.new_ghost, KEY, candidate)
-        # if we're sneaky and remove the OID and jar, then we get the duplicate
-        # key error
-        candidate._p_jar = None
-        self.assertRaises(KeyError, cache.new_ghost, KEY, candidate)
+        return cache, KEY, candidate
 
     def test_new_ghost_success_already_ghost(self):
         from persistent.interfaces import GHOST
 
         KEY = b'123'
-        cache = self._makeOne()
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
         candidate = self._makePersist(oid=None, jar=None)
         cache.new_ghost(KEY, candidate)
         self.assertTrue(cache.get(KEY) is candidate)
         self.assertEqual(candidate._p_oid, KEY)
-        self.assertEqual(candidate._p_jar, cache.jar)
+        self.assertEqual(candidate._p_jar, jar)
         self.assertEqual(candidate._p_state, GHOST)
 
     def test_new_ghost_success_not_already_ghost(self):
         from persistent.interfaces import GHOST
 
         KEY = b'123'
-        cache = self._makeOne()
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
         candidate = self._makePersist(oid=None, jar=None, state=UPTODATE)
         cache.new_ghost(KEY, candidate)
         self.assertTrue(cache.get(KEY) is candidate)
         self.assertEqual(candidate._p_oid, KEY)
-        self.assertEqual(candidate._p_jar, cache.jar)
+        self.assertEqual(candidate._p_jar, jar)
         self.assertEqual(candidate._p_state, GHOST)
 
     def test_new_ghost_w_pclass_non_ghost(self):
@@ -580,9 +481,8 @@ class PickleCacheTests(unittest.TestCase):
         cache = self._makeOne()
         cache.new_ghost(KEY, Pclass)
         self.assertTrue(cache.get(KEY) is Pclass)
-        self.assertTrue(cache.persistent_classes[KEY] is Pclass)
         self.assertEqual(Pclass._p_oid, KEY)
-        self.assertEqual(Pclass._p_jar, cache.jar)
+        return cache, Pclass, KEY
 
     def test_new_ghost_w_pclass_ghost(self):
         KEY = b'123'
@@ -592,61 +492,8 @@ class PickleCacheTests(unittest.TestCase):
         cache = self._makeOne()
         cache.new_ghost(KEY, Pclass)
         self.assertTrue(cache.get(KEY) is Pclass)
-        self.assertTrue(cache.persistent_classes[KEY] is Pclass)
         self.assertEqual(Pclass._p_oid, KEY)
-        self.assertEqual(Pclass._p_jar, cache.jar)
-
-    def test_reify_miss_single(self):
-        KEY = b'123'
-        cache = self._makeOne()
-        self.assertRaises(KeyError, cache.reify, KEY)
-
-    def test_reify_miss_multiple(self):
-        KEY = b'123'
-        KEY2 = b'456'
-        cache = self._makeOne()
-        self.assertRaises(KeyError, cache.reify, [KEY, KEY2])
-
-    def test_reify_hit_single_ghost(self):
-        from persistent.interfaces import GHOST
-
-        KEY = b'123'
-        cache = self._makeOne()
-        candidate = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
-        cache[KEY] = candidate
-        self.assertEqual(cache.ringlen(), 0)
-        cache.reify(KEY)
-        self.assertEqual(cache.ringlen(), 1)
-        items = cache.lru_items()
-        self.assertEqual(items[0][0], KEY)
-        self.assertTrue(items[0][1] is candidate)
-        self.assertEqual(candidate._p_state, UPTODATE)
-
-    def test_reify_hit_single_non_ghost(self):
-        KEY = b'123'
-        cache = self._makeOne()
-        candidate = self._makePersist(oid=KEY, jar=cache.jar, state=UPTODATE)
-        cache[KEY] = candidate
-        self.assertEqual(cache.ringlen(), 1)
-        cache.reify(KEY)
-        self.assertEqual(cache.ringlen(), 1)
-        self.assertEqual(candidate._p_state, UPTODATE)
-
-    def test_reify_hit_multiple_mixed(self):
-        from persistent.interfaces import GHOST
-
-        KEY = b'123'
-        KEY2 = b'456'
-        cache = self._makeOne()
-        c1 = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
-        cache[KEY] = c1
-        c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
-        cache[KEY2] = c2
-        self.assertEqual(cache.ringlen(), 1)
-        cache.reify([KEY, KEY2])
-        self.assertEqual(cache.ringlen(), 2)
-        self.assertEqual(c1._p_state, UPTODATE)
-        self.assertEqual(c2._p_state, UPTODATE)
+        return cache, Pclass, KEY
 
     def test_invalidate_miss_single(self):
         KEY = b'123'
@@ -659,55 +506,29 @@ class PickleCacheTests(unittest.TestCase):
         cache = self._makeOne()
         cache.invalidate([KEY, KEY2]) # doesn't raise
 
-    def test_invalidate_hit_single_ghost(self):
-        from persistent.interfaces import GHOST
-
-        KEY = b'123'
-        cache = self._makeOne()
-        candidate = self._makePersist(oid=b'123', jar=cache.jar, state=GHOST)
-        cache[KEY] = candidate
-        self.assertEqual(cache.ringlen(), 0)
-        cache.invalidate(KEY)
-        self.assertEqual(cache.ringlen(), 0)
-        self.assertEqual(candidate._p_state, GHOST)
-
     def test_invalidate_hit_single_non_ghost(self):
         from persistent.interfaces import GHOST
 
         KEY = b'123'
-        cache = self._makeOne()
-        candidate = self._makePersist(oid=b'123', jar=cache.jar, state=UPTODATE)
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
+        candidate = self._makePersist(oid=b'123', jar=jar, state=UPTODATE)
         cache[KEY] = candidate
         self.assertEqual(cache.ringlen(), 1)
         cache.invalidate(KEY)
         self.assertEqual(cache.ringlen(), 0)
         self.assertEqual(candidate._p_state, GHOST)
 
-    def test_invalidate_hit_multiple_mixed(self):
-        from persistent.interfaces import GHOST
-
-        KEY = b'123'
-        KEY2 = b'456'
-        cache = self._makeOne()
-        c1 = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
-        cache[KEY] = c1
-        c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
-        cache[KEY2] = c2
-        self.assertEqual(cache.ringlen(), 1)
-        cache.invalidate([KEY, KEY2])
-        self.assertEqual(cache.ringlen(), 0)
-        self.assertEqual(c1._p_state, GHOST)
-        self.assertEqual(c2._p_state, GHOST)
-
     def test_invalidate_hit_multiple_non_ghost(self):
         from persistent.interfaces import GHOST
 
         KEY = b'123'
         KEY2 = b'456'
+        jar = DummyConnection()
         cache = self._makeOne()
-        c1 = self._makePersist(oid=KEY, jar=cache.jar, state=UPTODATE)
+        c1 = self._makePersist(oid=KEY, jar=jar, state=UPTODATE)
         cache[KEY] = c1
-        c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
+        c2 = self._makePersist(oid=KEY2, jar=jar, state=UPTODATE)
         cache[KEY2] = c2
         self.assertEqual(cache.ringlen(), 2)
         # These should be in the opposite order of how they were
@@ -717,22 +538,12 @@ class PickleCacheTests(unittest.TestCase):
         self.assertEqual(c1._p_state, GHOST)
         self.assertEqual(c2._p_state, GHOST)
 
-    def test_invalidate_hit_pclass(self):
-        KEY = b'123'
-        class Pclass(object):
-            _p_oid = KEY
-            _p_jar = None
-        cache = self._makeOne()
-        cache[KEY] = Pclass
-        self.assertTrue(cache.persistent_classes[KEY] is Pclass)
-        cache.invalidate(KEY)
-        self.assertFalse(KEY in cache.persistent_classes)
-
     def test_debug_info_w_persistent_class(self):
         KEY = b'pclass'
         class pclass(object):
             _p_oid = KEY
-        cache = self._makeOne()
+            _p_jar = DummyConnection()
+        cache = self._makeOne(pclass._p_jar)
         pclass._p_state = UPTODATE
         cache[KEY] = pclass
 
@@ -740,11 +551,15 @@ class PickleCacheTests(unittest.TestCase):
         info = cache.debug_info()
 
         self.assertEqual(len(info), 1)
-        oid, refc, typ, state = info[0]
+        # C and Python return different length tuples,
+        # and the refcounts are off by one.
+        oid = info[0][0]
+        typ = info[0][2]
+
         self.assertEqual(oid, KEY)
-        self.assertEqual(refc, len(gc.get_referents(pclass)))
         self.assertEqual(typ, 'type')
-        self.assertEqual(state, UPTODATE)
+
+        return pclass, info[0]
 
     def test_debug_info_w_normal_object(self):
         KEY = b'uptodate'
@@ -756,11 +571,13 @@ class PickleCacheTests(unittest.TestCase):
         info = cache.debug_info()
 
         self.assertEqual(len(info), 1)
-        oid, refc, typ, state = info[0]
+        # C and Python return different length tuples,
+        # and the refcounts are off by one.
+        oid = info[0][0]
+        typ = info[0][2]
         self.assertEqual(oid, KEY)
-        self.assertEqual(refc, len(gc.get_referents(uptodate)))
-        self.assertEqual(typ, 'DummyPersistent')
-        self.assertEqual(state, UPTODATE)
+        self.assertEqual(typ, type(uptodate).__name__)
+        return uptodate, info[0]
 
 
     def test_debug_info_w_ghost(self):
@@ -775,31 +592,19 @@ class PickleCacheTests(unittest.TestCase):
         info = cache.debug_info()
 
         self.assertEqual(len(info), 1)
-        oid, refc, typ, state = info[0]
+        oid, _refc, typ, state = info[0]
         self.assertEqual(oid, KEY)
-        self.assertEqual(refc, len(gc.get_referents(ghost)))
-        self.assertEqual(typ, 'DummyPersistent')
-        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)
+        self.assertEqual(typ, type(ghost).__name__)
+        # In the C implementation, we couldn't actually set the _p_state
+        # directly.
+        self.assertEqual(state, ghost._p_state)
+        return ghost, info[0]
 
     def test_setting_non_persistent_item(self):
         cache = self._makeOne()
         with self.assertRaisesRegex(TypeError,
                                     "Cache values must be persistent objects."):
-            cache[None] = object()
+            cache[b'12345678'] = object()
 
     def test_setting_without_jar(self):
         cache = self._makeOne()
@@ -809,42 +614,17 @@ class PickleCacheTests(unittest.TestCase):
             cache[p._p_oid] = p
 
     def test_setting_already_cached(self):
-        cache1 = self._makeOne()
-        p = self._makePersist(jar=cache1.jar)
+        jar = DummyConnection()
+        cache1 = self._makeOne(jar)
+        p = self._makePersist(jar=jar)
 
         cache1[p._p_oid] = p
 
         cache2 = self._makeOne()
         with self.assertRaisesRegex(ValueError,
-                                    "Object already in another cache"):
+                                    "Cache values may only be in one cache"):
             cache2[p._p_oid] = p
 
-    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
-        # The cache accesses the private attribute directly 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)
@@ -855,16 +635,55 @@ class PickleCacheTests(unittest.TestCase):
 
     def test_sweep_empty(self):
         cache = self._makeOne()
-        self.assertEqual(cache.incrgc(), 0)
+        # Python returns 0, C returns None
+        self.assertFalse(cache.incrgc())
+
+    def test_invalidate_persistent_class_calls_p_invalidate(self):
+        KEY = b'pclass'
+        class pclass(object):
+            _p_oid = KEY
+            _p_jar = DummyConnection()
+            invalidated = False
+            @classmethod
+            def _p_invalidate(cls):
+                cls.invalidated = True
+
+
+        cache = self._makeOne(pclass._p_jar)
+
+        cache[KEY] = pclass
+
+        cache.invalidate(KEY)
+
+        self.assertTrue(pclass.invalidated)
+
+    def test_ring_impl(self):
+        from .. import ring
+
+        expected = (ring._CFFIRing
+                    if _is_pypy or ring._CFFIRing is not None or os.environ.get('USING_CFFI')
+                    else ring._DequeRing)
+        self.assertIs(ring.Ring, expected)
+
+
+class PythonPickleCacheTests(PickleCacheTests):
+    # Tests that depend on the implementation details of the
+    # Python PickleCache and the Python persistent object.
+    # Anything that involves directly setting the _p_state of a persistent
+    # object has to be here, we can't do that in the C implementation.
+
+    def _getTargetInterface(self):
+        from persistent.interfaces import IExtendedPickleCache
+        return IExtendedPickleCache
 
     def test_sweep_of_non_deactivating_object(self):
-        cache = self._makeOne()
-        p = self._makePersist(jar=cache.jar)
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
+        p = self._makePersist(jar=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"
 
@@ -882,39 +701,337 @@ class PickleCacheTests(unittest.TestCase):
         del p._p_deactivate
         self.assertEqual(cache.full_sweep(), 1)
 
-    if _is_jython: # pragma: no cover
-        def with_deterministic_gc(f):
-            def test(self):
-                old_flags = gc.getMonitorGlobal()
-                gc.setMonitorGlobal(True)
-                try:
-                    f(self, force_collect=True)
-                finally:
-                    gc.setMonitorGlobal(old_flags)
-            return test
-    else:
-        def with_deterministic_gc(f):
-            return f
+    def test_update_object_size_estimation_simple(self):
+        cache = self._makeOne()
+        p = self._makePersist(jar=cache.jar)
+
+        cache[p._p_oid] = p
+        # The cache accesses the private attribute directly 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_reify_miss_single(self):
+        KEY = b'123'
+        cache = self._makeOne()
+        self.assertRaises(KeyError, cache.reify, KEY)
+
+    def test_reify_miss_multiple(self):
+        KEY = b'123'
+        KEY2 = b'456'
+        cache = self._makeOne()
+        self.assertRaises(KeyError, cache.reify, [KEY, KEY2])
+
+    def test_reify_hit_single_non_ghost(self):
+        KEY = b'123'
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
+        candidate = self._makePersist(oid=KEY, jar=jar, state=UPTODATE)
+        cache[KEY] = candidate
+        self.assertEqual(cache.ringlen(), 1)
+        cache.reify(KEY)
+        self.assertEqual(cache.ringlen(), 1)
+        self.assertEqual(candidate._p_state, UPTODATE)
+
+    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___delitem___w_persistent_class(self):
+        cache, key = super(PythonPickleCacheTests, self).test___delitem___w_persistent_class()
+        self.assertNotIn(key, cache.persistent_classes)
+
+    def test___setitem___ghost(self):
+        cache = super(PythonPickleCacheTests, self).test___setitem___ghost()
+        self.assertEqual(cache.ringlen(), 0)
+
+    def test___setitem___persistent_class(self):
+        cache = super(PythonPickleCacheTests, self).test___setitem___persistent_class()
+        self.assertEqual(_len(cache.items()), 0)
+
+    def test_new_ghost_w_pclass_non_ghost(self):
+        cache, Pclass, key = super(PythonPickleCacheTests, self).test_new_ghost_w_pclass_non_ghost()
+        self.assertEqual(Pclass._p_jar, cache.jar)
+        self.assertIs(cache.persistent_classes[key], Pclass)
+
+    def test_new_ghost_w_pclass_ghost(self):
+        cache, Pclass, key = super(PythonPickleCacheTests, self).test_new_ghost_w_pclass_ghost()
+        self.assertEqual(Pclass._p_jar, cache.jar)
+        self.assertIs(cache.persistent_classes[key], Pclass)
+
+
+    def test_mru_nonesuch_raises_KeyError(self):
+        cache = self._makeOne()
+
+        self.assertRaises(KeyError, cache.mru, b'nonesuch')
+
+    def test_mru_normal(self):
+        ONE = b'one'
+        TWO = b'two'
+        THREE = b'three'
+        cache = self._makeOne()
+        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
+        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
+        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
+
+        cache.mru(TWO)
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(_len(items), 3)
+        self.assertEqual(items[0][0], ONE)
+        self.assertEqual(items[1][0], THREE)
+        self.assertEqual(items[2][0], TWO)
+
+    def test_mru_ghost(self):
+        from persistent.interfaces import GHOST
+
+        ONE = b'one'
+        TWO = b'two'
+        THREE = b'three'
+        cache = self._makeOne()
+        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
+        two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
+        # two must live to survive gc
+        self.assertIsNotNone(two)
+        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
+
+        cache.mru(TWO)
+
+        self.assertEqual(cache.ringlen(), 2)
+        items = cache.lru_items()
+        self.assertEqual(_len(items), 2)
+        self.assertEqual(items[0][0], ONE)
+        self.assertEqual(items[1][0], THREE)
+
+    def test_mru_was_ghost_now_active(self):
+        from persistent.interfaces import GHOST
+
+        ONE = b'one'
+        TWO = b'two'
+        THREE = b'three'
+        cache = self._makeOne()
+        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
+        two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
+        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
+
+        two._p_state = UPTODATE
+        cache.mru(TWO)
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(_len(items), 3)
+        self.assertEqual(items[0][0], ONE)
+        self.assertEqual(items[1][0], THREE)
+        self.assertEqual(items[2][0], TWO)
+
+    def test_mru_first(self):
+        ONE = b'one'
+        TWO = b'two'
+        THREE = b'three'
+        cache = self._makeOne()
+        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
+        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
+        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
+
+        cache.mru(ONE)
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(_len(items), 3)
+        self.assertEqual(items[0][0], TWO)
+        self.assertEqual(items[1][0], THREE)
+        self.assertEqual(items[2][0], ONE)
+
+    def test_mru_last(self):
+        ONE = b'one'
+        TWO = b'two'
+        THREE = b'three'
+        cache = self._makeOne()
+        cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
+        cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
+        cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
+
+        cache.mru(THREE)
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(_len(items), 3)
+        self.assertEqual(items[0][0], ONE)
+        self.assertEqual(items[1][0], TWO)
+        self.assertEqual(items[2][0], THREE)
+
+    def test_invalidate_hit_pclass(self):
+        KEY = b'123'
+        class Pclass(object):
+            _p_oid = KEY
+            _p_jar = DummyConnection()
+        cache = self._makeOne(Pclass._p_jar)
+        cache[KEY] = Pclass
+        self.assertIs(cache.persistent_classes[KEY], Pclass)
+        cache.invalidate(KEY)
+        self.assertNotIn(KEY, cache.persistent_classes)
+
+    def test_debug_info_w_normal_object(self):
+        obj, info = super(PythonPickleCacheTests, self).test_debug_info_w_normal_object()
+        self.assertEqual(info[1], len(gc.get_referents(obj)))
+        self.assertEqual(info[3], UPTODATE)
+
+    def test_debug_info_w_ghost(self):
+        ghost, info = super(PythonPickleCacheTests, self).test_debug_info_w_ghost()
+        self.assertEqual(info[1], len(gc.get_referents(ghost)))
+
+    def test_debug_info_w_persistent_class(self):
+        pclass, info = super(PythonPickleCacheTests, self).test_debug_info_w_persistent_class()
+        self.assertEqual(info[3], UPTODATE)
+        self.assertEqual(info[1], len(gc.get_referents(pclass)))
+
+    def test_full_sweep_w_sticky(self):
+        from persistent.interfaces import STICKY
+
+        cache = self._makeOne()
+        oids = self._populate_cache(cache, state_0=STICKY)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.full_sweep()
+        gc.collect() # banish the ghosts who are no longer in the ring
+
+        self.assertEqual(cache.cache_non_ghost_count, 1)
+
+        self.assertTrue(cache.get(oids[0]) is not None)
+        for oid in oids[1:]:
+            self.assertTrue(cache.get(oid) is None)
+
+    def test_full_sweep_w_changed(self):
+        from persistent.interfaces import CHANGED
+
+        cache = self._makeOne()
+        oids = self._populate_cache(cache, state_0=CHANGED)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.full_sweep()
+        gc.collect() # banish the ghosts who are no longer in the ring
+
+        self.assertEqual(cache.cache_non_ghost_count, 1)
+
+        self.assertTrue(cache.get(oids[0]) is not None)
+        for oid in oids[1:]:
+            self.assertTrue(cache.get(oid) is None)
+
+    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_invalidate_hit_multiple_mixed(self):
+        from persistent.interfaces import GHOST
+
+        KEY = b'123'
+        KEY2 = b'456'
+        jar = DummyConnection()
+        cache = self._makeOne()
+        c1 = self._makePersist(oid=KEY, jar=jar, state=GHOST)
+        cache[KEY] = c1
+        c2 = self._makePersist(oid=KEY2, jar=jar, state=UPTODATE)
+        cache[KEY2] = c2
+        self.assertEqual(cache.ringlen(), 1)
+        cache.invalidate([KEY, KEY2])
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(c1._p_state, GHOST)
+        self.assertEqual(c2._p_state, GHOST)
+
+    def test_invalidate_hit_single_ghost(self):
+        from persistent.interfaces import GHOST
+
+        KEY = b'123'
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
+        candidate = self._makePersist(oid=b'123', jar=jar, state=GHOST)
+        cache[KEY] = candidate
+        self.assertEqual(cache.ringlen(), 0)
+        cache.invalidate(KEY)
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(candidate._p_state, GHOST)
+
+    def test_reify_hit_multiple_mixed(self):
+        from persistent.interfaces import GHOST
+
+        KEY = b'123'
+        KEY2 = b'456'
+        jar = DummyConnection()
+        cache = self._makeOne(jar)
+        c1 = self._makePersist(oid=KEY, jar=jar, state=GHOST)
+        cache[KEY] = c1
+        c2 = self._makePersist(oid=KEY2, jar=jar, state=UPTODATE)
+        cache[KEY2] = c2
+        self.assertEqual(cache.ringlen(), 1)
+        cache.reify([KEY, KEY2])
+        self.assertEqual(cache.ringlen(), 2)
+        self.assertEqual(c1._p_state, UPTODATE)
+        self.assertEqual(c2._p_state, UPTODATE)
+
+    def test_reify_hit_single_ghost(self):
+        from persistent.interfaces import GHOST
+
+        KEY = b'123'
+        jar = DummyConnection()
+        cache = self._makeOne()
+        candidate = self._makePersist(oid=KEY, jar=jar, state=GHOST)
+        cache[KEY] = candidate
+        self.assertEqual(cache.ringlen(), 0)
+        cache.reify(KEY)
+        self.assertEqual(cache.ringlen(), 1)
+        items = cache.lru_items()
+        self.assertEqual(items[0][0], KEY)
+        self.assertTrue(items[0][1] is candidate)
+        self.assertEqual(candidate._p_state, UPTODATE)
 
     @with_deterministic_gc
     def test_cache_garbage_collection_bytes_also_deactivates_object(self,
                                                                     force_collect=_is_pypy or _is_jython):
+
+        class MyPersistent(self._getDummyPersistentClass()):
+            def _p_deactivate(self):
+                # mimic what the real persistent object does to update the cache
+                # size; if we don't get deactivated by sweeping, the cache size
+                # won't shrink so this also validates that _p_deactivate gets
+                # called when ejecting an object.
+                cache.update_object_size_estimation(self._p_oid, -1)
+
         cache = self._makeOne()
         cache.cache_size = 1000
         oids = []
         for i in range(100):
             oid = self._numbered_oid(i)
             oids.append(oid)
-            o = cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+            o = cache[oid] = self._makePersist(oid=oid, kind=MyPersistent, state=UPTODATE)
+
             o._Persistent__size = 0 # must start 0, ZODB sets it AFTER updating the size
+
             cache.update_object_size_estimation(oid, 64)
             o._Persistent__size = 2
 
-            # mimic what the real persistent object does to update the cache
-            # size; if we don't get deactivated by sweeping, the cache size
-            # won't shrink so this also validates that _p_deactivate gets
-            # called when ejecting an object.
-            o._p_deactivate = lambda: cache.update_object_size_estimation(oid, -1)
         self.assertEqual(cache.cache_non_ghost_count, 100)
 
         # A GC at this point does nothing
@@ -940,31 +1057,37 @@ class PickleCacheTests(unittest.TestCase):
             gc.collect()
         self.assertEqual(len(cache), 1)
 
-    def test_invalidate_persistent_class_calls_p_invalidate(self):
-        KEY = b'pclass'
-        class pclass(object):
-            _p_oid = KEY
-            invalidated = False
-            @classmethod
-            def _p_invalidate(cls):
-                cls.invalidated = True
 
+    def test_new_ghost_obj_already_in_cache(self):
+        cache, key, candidate = super(PythonPickleCacheTests, self).test_new_ghost_obj_already_in_cache()
 
-        cache = self._makeOne()
+        # If we're sneaky and remove the OID and jar, then we get the duplicate
+        # key error. Removing them only works because we're not using a real
+        # persistent object.
+        candidate._p_oid = None
+        self.assertRaises(ValueError, cache.new_ghost, key, candidate)
 
-        cache[KEY] = pclass
+        candidate._p_jar = None
+        self.assertRaises(KeyError, cache.new_ghost, key, candidate)
 
-        cache.invalidate(KEY)
 
-        self.assertTrue(pclass.invalidated)
+@skipIfNoCExtension
+class CPickleCacheTests(PickleCacheTests):
 
-    def test_ring_impl(self):
-        from .. import ring
+    def _getTargetClass(self):
+        from persistent.cPickleCache import PickleCache
+        return PickleCache
+
+    def _getDummyPersistentClass(self):
+        from persistent import Persistent
+        class DummyPersistent(Persistent):
+            __slots__ = ()
+        return DummyPersistent
+
+    def test___setitem___persistent_class(self):
+        cache = super(CPickleCacheTests, self).test___setitem___persistent_class()
+        self.assertEqual(_len(cache.items()), 1)
 
-        expected = (ring._CFFIRing
-                    if _is_pypy or ring._CFFIRing is not None or os.environ.get('USING_CFFI')
-                    else ring._DequeRing)
-        self.assertIs(ring.Ring, expected)
 
 
 class DummyPersistent(object):
@@ -981,11 +1104,9 @@ class DummyPersistent(object):
     def _p_activate(self):
         self._p_state = UPTODATE
 
-
 class DummyConnection(object):
     pass
 
-
 def _len(seq):
     return len(list(seq))
 

pip install -e .[test]
parse_log_pytest
requirements.txt
cffi zope.testrunner pytest
apt-get update apt-get install -y gcc
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
cffi==1.15.1
importlib-metadata==4.8.3
iniconfig==1.1.1
manuel==1.13.0
packaging==21.3
-e git+https://github.com/zopefoundation/persistent.git@8e1df28364d8851955f541faecf3ea1924b2798e#egg=persistent
pluggy==1.0.0
py==1.11.0
pycparser==2.21
pyparsing==3.1.4
pytest==7.0.1
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0
zope.exceptions==4.6
zope.interface==5.5.2
zope.testrunner==5.6

name: persistent
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cffi==1.15.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - manuel==1.13.0
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycparser==2.21
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
      - zope-exceptions==4.6
      - zope-interface==5.5.2
      - zope-testrunner==5.6
prefix: /opt/conda/envs/persistent

persistent/tests/test_picklecache.py::PickleCacheTests::test_incrgc_w_larger_drain_resistance persistent/tests/test_picklecache.py::PickleCacheTests::test_incrgc_w_smaller_drain_resistance persistent/tests/test_picklecache.py::PickleCacheTests::test_setting_already_cached persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_class_conforms_to_IPickleCache persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_incrgc_w_larger_drain_resistance persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_incrgc_w_smaller_drain_resistance persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_instance_conforms_to_IPickleCache persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_setting_already_cached persistent/tests/test_picklecache.py::CPickleCacheTests::test_class_conforms_to_IPickleCache persistent/tests/test_picklecache.py::CPickleCacheTests::test_instance_conforms_to_IPickleCache
persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_obj_already_has_jar persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_obj_already_has_oid persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_obj_already_in_cache
persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___w_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___w_normal_object persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___w_persistent_class persistent/tests/test_picklecache.py::PickleCacheTests::test___delitem___w_remaining_object persistent/tests/test_picklecache.py::PickleCacheTests::test___getitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___duplicate_oid_raises_ValueError persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___duplicate_oid_same_obj persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___ghost persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___mismatch_key_oid persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___non_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::PickleCacheTests::test___setitem___persistent_class persistent/tests/test_picklecache.py::PickleCacheTests::test_cache_size persistent/tests/test_picklecache.py::PickleCacheTests::test_class_conforms_to_IPickleCache persistent/tests/test_picklecache.py::PickleCacheTests::test_debug_info_w_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_debug_info_w_normal_object persistent/tests/test_picklecache.py::PickleCacheTests::test_debug_info_w_persistent_class persistent/tests/test_picklecache.py::PickleCacheTests::test_empty persistent/tests/test_picklecache.py::PickleCacheTests::test_full_sweep persistent/tests/test_picklecache.py::PickleCacheTests::test_get_nonesuch_no_default persistent/tests/test_picklecache.py::PickleCacheTests::test_get_nonesuch_w_default persistent/tests/test_picklecache.py::PickleCacheTests::test_incrgc_simple persistent/tests/test_picklecache.py::PickleCacheTests::test_instance_conforms_to_IPickleCache persistent/tests/test_picklecache.py::PickleCacheTests::test_invalidate_hit_multiple_non_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_invalidate_hit_single_non_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_invalidate_miss_multiple persistent/tests/test_picklecache.py::PickleCacheTests::test_invalidate_miss_single persistent/tests/test_picklecache.py::PickleCacheTests::test_invalidate_persistent_class_calls_p_invalidate persistent/tests/test_picklecache.py::PickleCacheTests::test_lruitems persistent/tests/test_picklecache.py::PickleCacheTests::test_minimize persistent/tests/test_picklecache.py::PickleCacheTests::test_minimize_turns_into_ghosts persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_non_persistent_object persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_obj_already_has_jar persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_obj_already_has_oid persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_obj_already_in_cache persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_success_already_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_success_not_already_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_w_pclass_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_new_ghost_w_pclass_non_ghost persistent/tests/test_picklecache.py::PickleCacheTests::test_ring_impl persistent/tests/test_picklecache.py::PickleCacheTests::test_setting_non_persistent_item persistent/tests/test_picklecache.py::PickleCacheTests::test_setting_without_jar persistent/tests/test_picklecache.py::PickleCacheTests::test_sweep_empty persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___w_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___w_normal_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___w_persistent_class persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___delitem___w_remaining_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___getitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___duplicate_oid_raises_ValueError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___duplicate_oid_same_obj persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___mismatch_key_oid persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___non_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test___setitem___persistent_class persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_cache_garbage_collection_bytes_also_deactivates_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_cache_size persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_cannot_update_mru_while_already_locked persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_debug_info_w_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_debug_info_w_normal_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_debug_info_w_persistent_class persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_empty persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_full_sweep persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_full_sweep_w_changed persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_full_sweep_w_sticky persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_get_nonesuch_no_default persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_get_nonesuch_w_default persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_incrgc_simple persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_init_with_cacheless_jar persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_hit_multiple_mixed persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_hit_multiple_non_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_hit_pclass persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_hit_single_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_hit_single_non_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_miss_multiple persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_miss_single persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_invalidate_persistent_class_calls_p_invalidate persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_lruitems persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_minimize persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_minimize_turns_into_ghosts persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_first persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_last persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_nonesuch_raises_KeyError persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_normal persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_mru_was_ghost_now_active persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_non_persistent_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_obj_already_has_jar persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_obj_already_has_oid persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_obj_already_in_cache persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_success_already_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_success_not_already_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_w_pclass_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_new_ghost_w_pclass_non_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_reify_hit_multiple_mixed persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_reify_hit_single_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_reify_hit_single_non_ghost persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_reify_miss_multiple persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_reify_miss_single persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_ring_impl persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_setting_non_persistent_item persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_setting_without_jar persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_sweep_empty persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_sweep_of_non_deactivating_object persistent/tests/test_picklecache.py::PythonPickleCacheTests::test_update_object_size_estimation_simple persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___w_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___w_normal_object persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___w_persistent_class persistent/tests/test_picklecache.py::CPickleCacheTests::test___delitem___w_remaining_object persistent/tests/test_picklecache.py::CPickleCacheTests::test___getitem___nonesuch_raises_KeyError persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___duplicate_oid_raises_ValueError persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___duplicate_oid_same_obj persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___mismatch_key_oid persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___non_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___non_string_oid_raises_TypeError persistent/tests/test_picklecache.py::CPickleCacheTests::test___setitem___persistent_class persistent/tests/test_picklecache.py::CPickleCacheTests::test_cache_size persistent/tests/test_picklecache.py::CPickleCacheTests::test_debug_info_w_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_debug_info_w_normal_object persistent/tests/test_picklecache.py::CPickleCacheTests::test_debug_info_w_persistent_class persistent/tests/test_picklecache.py::CPickleCacheTests::test_empty persistent/tests/test_picklecache.py::CPickleCacheTests::test_full_sweep persistent/tests/test_picklecache.py::CPickleCacheTests::test_get_nonesuch_no_default persistent/tests/test_picklecache.py::CPickleCacheTests::test_get_nonesuch_w_default persistent/tests/test_picklecache.py::CPickleCacheTests::test_incrgc_simple persistent/tests/test_picklecache.py::CPickleCacheTests::test_incrgc_w_larger_drain_resistance persistent/tests/test_picklecache.py::CPickleCacheTests::test_incrgc_w_smaller_drain_resistance persistent/tests/test_picklecache.py::CPickleCacheTests::test_invalidate_hit_multiple_non_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_invalidate_hit_single_non_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_invalidate_miss_multiple persistent/tests/test_picklecache.py::CPickleCacheTests::test_invalidate_miss_single persistent/tests/test_picklecache.py::CPickleCacheTests::test_invalidate_persistent_class_calls_p_invalidate persistent/tests/test_picklecache.py::CPickleCacheTests::test_lruitems persistent/tests/test_picklecache.py::CPickleCacheTests::test_minimize persistent/tests/test_picklecache.py::CPickleCacheTests::test_minimize_turns_into_ghosts persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_non_persistent_object persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_success_already_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_success_not_already_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_w_pclass_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_new_ghost_w_pclass_non_ghost persistent/tests/test_picklecache.py::CPickleCacheTests::test_ring_impl persistent/tests/test_picklecache.py::CPickleCacheTests::test_setting_already_cached persistent/tests/test_picklecache.py::CPickleCacheTests::test_setting_non_persistent_item persistent/tests/test_picklecache.py::CPickleCacheTests::test_setting_without_jar persistent/tests/test_picklecache.py::CPickleCacheTests::test_sweep_empty persistent/tests/test_picklecache.py::test_suite
persistent/__init__.py persistent/picklecache.py persistent/interfaces.py CHANGES.rst persistent/cPickleCache.c
persistent/__init__.py persistent/picklecache.py persistent/interfaces.py CHANGES.rst persistent/cPickleCache.c