| """ |
| 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") |
|
|