106 lines
2.6 KiB
Python
106 lines
2.6 KiB
Python
"""
|
|
Module for testing automatic garbage collection of objects
|
|
|
|
.. autosummary::
|
|
:toctree: generated/
|
|
|
|
set_gc_state - enable or disable garbage collection
|
|
gc_state - context manager for given state of garbage collector
|
|
assert_deallocated - context manager to check for circular references on object
|
|
|
|
"""
|
|
import weakref
|
|
import gc
|
|
|
|
from contextlib import contextmanager
|
|
from platform import python_implementation
|
|
|
|
__all__ = ['set_gc_state', 'gc_state', 'assert_deallocated']
|
|
|
|
|
|
IS_PYPY = python_implementation() == 'PyPy'
|
|
|
|
|
|
class ReferenceError(AssertionError):
|
|
pass
|
|
|
|
|
|
def set_gc_state(state):
|
|
""" Set status of garbage collector """
|
|
if gc.isenabled() == state:
|
|
return
|
|
if state:
|
|
gc.enable()
|
|
else:
|
|
gc.disable()
|
|
|
|
|
|
@contextmanager
|
|
def gc_state(state):
|
|
""" Context manager to set state of garbage collector to `state`
|
|
|
|
Parameters
|
|
----------
|
|
state : bool
|
|
True for gc enabled, False for disabled
|
|
|
|
Examples
|
|
--------
|
|
>>> with gc_state(False):
|
|
... assert not gc.isenabled()
|
|
>>> with gc_state(True):
|
|
... assert gc.isenabled()
|
|
"""
|
|
orig_state = gc.isenabled()
|
|
set_gc_state(state)
|
|
yield
|
|
set_gc_state(orig_state)
|
|
|
|
|
|
@contextmanager
|
|
def assert_deallocated(func, *args, **kwargs):
|
|
"""Context manager to check that object is deallocated
|
|
|
|
This is useful for checking that an object can be freed directly by
|
|
reference counting, without requiring gc to break reference cycles.
|
|
GC is disabled inside the context manager.
|
|
|
|
This check is not available on PyPy.
|
|
|
|
Parameters
|
|
----------
|
|
func : callable
|
|
Callable to create object to check
|
|
\\*args : sequence
|
|
positional arguments to `func` in order to create object to check
|
|
\\*\\*kwargs : dict
|
|
keyword arguments to `func` in order to create object to check
|
|
|
|
Examples
|
|
--------
|
|
>>> class C: pass
|
|
>>> with assert_deallocated(C) as c:
|
|
... # do something
|
|
... del c
|
|
|
|
>>> class C:
|
|
... def __init__(self):
|
|
... self._circular = self # Make circular reference
|
|
>>> with assert_deallocated(C) as c: #doctest: +IGNORE_EXCEPTION_DETAIL
|
|
... # do something
|
|
... del c
|
|
Traceback (most recent call last):
|
|
...
|
|
ReferenceError: Remaining reference(s) to object
|
|
"""
|
|
if IS_PYPY:
|
|
raise RuntimeError("assert_deallocated is unavailable on PyPy")
|
|
|
|
with gc_state(False):
|
|
obj = func(*args, **kwargs)
|
|
ref = weakref.ref(obj)
|
|
yield obj
|
|
del obj
|
|
if ref() is not None:
|
|
raise ReferenceError("Remaining reference(s) to object")
|