Created
April 5, 2010 14:40
-
-
Save fdb/356404 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import gc | |
import weakref | |
_destroyed = False | |
_superDestroyed = False | |
class MyClass(object): | |
'''A simple class that sets a global variable when it is destroyed. | |
Optionally it can hold a cyclic reference when passing cyclic=True to the constructor.''' | |
def __init__(self, cyclic=False): | |
if cyclic: | |
self.cycle = self | |
def __del__(self): | |
global _destroyed | |
_destroyed = True | |
class BadSuperDestructor(MyClass): | |
'''Overrides MyClass and provides a custom destructor. | |
This implementation does not call the super destructor.''' | |
def __del__(self): | |
global _superDestroyed | |
_superDestroyed = True | |
class GoodSuperDestructor(MyClass): | |
'''Overrides MyClass and provides a custom destructor. | |
This implementation calls the super destructor in the finally clause.''' | |
def __del__(self): | |
try: | |
global _superDestroyed | |
_superDestroyed = True | |
finally: | |
super(GoodSuperDestructor, self).__del__() | |
class FaultyDestructor(object): | |
'''A class that raises an exception in its destructor.''' | |
def __del__(self): | |
global destroyed | |
raise RuntimeError("Destructor has an error.") | |
_destroyed = True | |
import unittest | |
class GarbageCollectionTest(unittest.TestCase): | |
'''Test the behavior of the CPython garbage collector. | |
The GC is very implementation-specific. All of these tests fail on Jython.''' | |
def setUp(self): | |
global _destroyed, _superDestroyed | |
_destroyed = False | |
_superDestroyed = False | |
gc.enable() | |
def testObject(self): | |
'''Test if the object is immediately destroyed after the last reference to it is removed.''' | |
mc = MyClass() | |
self.assertFalse(_destroyed) | |
del mc | |
self.assertTrue(_destroyed) | |
def testTwoReferences(self): | |
'''Test what happens if two references point to the same object. | |
Objects are destroyed as soon as all the references dissapear. ''' | |
ref1 = ref2 = MyClass() | |
self.assertFalse(_destroyed) | |
del ref1 | |
self.assertFalse(_destroyed) | |
ref2 = None | |
self.assertTrue(_destroyed) | |
def testWeakReferences(self): | |
'''Test if the weak reference affects garbage collection. | |
A weak reference holds a reference to an object, but this reference does not | |
increase the reference count. As soon as all the strong references are removed, the object | |
is garbage collected. Retrieving the referent (by calling the reference object) returns None.''' | |
strongRef = MyClass() | |
weakRef = weakref.ref(strongRef) | |
self.assertTrue(strongRef == weakRef()) | |
self.assertFalse(_destroyed) | |
del strongRef | |
self.assertTrue(_destroyed) | |
self.assertTrue(weakRef() is None) | |
def testCycle(self): | |
'''Test if an object is removed if it holds a cyclic reference. | |
Cyclic references are removed by the garbage collector, not the reference-counting implementation. | |
Note that if objects with a __del__ method contain a cycle, they're not automatically garbage-collected. | |
Instead, they're put in the gc.garbage list. See http://docs.python.org/library/gc.html#gc.garbage | |
''' | |
mc = MyClass(cyclic=True) | |
self.assertFalse(_destroyed) | |
mc = None | |
self.assertFalse(_destroyed) | |
self.assertEquals(0, len(gc.garbage)) | |
gc.collect() | |
self.assertFalse(_destroyed) | |
self.assertEquals(1, len(gc.garbage)) | |
self.assertEquals(MyClass, type(gc.garbage[0])) | |
def testExplicitGarbageCollection(self): | |
'''Test if objects are still destroyed even with the GC disabled. | |
CPython has actually two "garbage collectors". A simple refence counting implementation cleans up | |
objects without cyclic dependencies. The real garbage collector cleans up object cycles that go | |
out of scope. | |
Since MyClass is not cyclic, it is immediately destroyed.''' | |
gc.disable() | |
ref = MyClass() | |
self.assertFalse(_destroyed) | |
del ref | |
self.assertTrue(_destroyed) | |
def testFaultyDestructor(self): | |
'''Test what happens if the destructor raises an exception. | |
The exception is ignored, but reported to sys.stderr.''' | |
ref = FaultyDestructor() | |
del ref | |
# The exception is ignored, but the rest of the destructor is not executed. | |
self.assertFalse(_destroyed) | |
def testBadSuperDestructor(self): | |
'''Test that the destructor of a superclass does not automatically call the subclass destructor.''' | |
ref = BadSuperDestructor() | |
del ref | |
self.assertTrue(_superDestroyed) | |
self.assertFalse(_destroyed) | |
def testGoodSuperDestructor(self): | |
'''Test the correct implementation for a superclass destructor, using try/finally.''' | |
ref = GoodSuperDestructor() | |
del ref | |
self.assertTrue(_superDestroyed) | |
self.assertTrue(_destroyed) | |
if __name__=='__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment