diff options
Diffstat (limited to 'tests/run/trashcan.pyx')
-rw-r--r-- | tests/run/trashcan.pyx | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/tests/run/trashcan.pyx b/tests/run/trashcan.pyx new file mode 100644 index 000000000..29331fdf6 --- /dev/null +++ b/tests/run/trashcan.pyx @@ -0,0 +1,148 @@ +# mode: run + +cimport cython + + +# Count number of times an object was deallocated twice. This should remain 0. +cdef int double_deallocations = 0 +def assert_no_double_deallocations(): + global double_deallocations + err = double_deallocations + double_deallocations = 0 + assert not err + + +# Compute x = f(f(f(...(None)...))) nested n times and throw away the result. +# The real test happens when exiting this function: then a big recursive +# deallocation of x happens. We are testing two things in the tests below: +# that Python does not crash and that no double deallocation happens. +# See also https://github.com/python/cpython/pull/11841 +def recursion_test(f, int n=2**20): + x = None + cdef int i + for i in range(n): + x = f(x) + + +@cython.trashcan(True) +cdef class Recurse: + """ + >>> recursion_test(Recurse) + >>> assert_no_double_deallocations() + """ + cdef public attr + cdef int deallocated + + def __cinit__(self, x): + self.attr = x + + def __dealloc__(self): + # Check that we're not being deallocated twice + global double_deallocations + double_deallocations += self.deallocated + self.deallocated = 1 + + +cdef class RecurseSub(Recurse): + """ + >>> recursion_test(RecurseSub) + >>> assert_no_double_deallocations() + """ + cdef int subdeallocated + + def __dealloc__(self): + # Check that we're not being deallocated twice + global double_deallocations + double_deallocations += self.subdeallocated + self.subdeallocated = 1 + + +@cython.freelist(4) +@cython.trashcan(True) +cdef class RecurseFreelist: + """ + >>> recursion_test(RecurseFreelist) + >>> recursion_test(RecurseFreelist, 1000) + >>> assert_no_double_deallocations() + """ + cdef public attr + cdef int deallocated + + def __cinit__(self, x): + self.attr = x + + def __dealloc__(self): + # Check that we're not being deallocated twice + global double_deallocations + double_deallocations += self.deallocated + self.deallocated = 1 + + +# Subclass of list => uses trashcan by default +# As long as https://github.com/python/cpython/pull/11841 is not fixed, +# this does lead to double deallocations, so we skip that check. +cdef class RecurseList(list): + """ + >>> RecurseList(42) + [42] + >>> recursion_test(RecurseList) + """ + def __init__(self, x): + super().__init__((x,)) + + +# Some tests where the trashcan is NOT used. When the trashcan is not used +# in a big recursive deallocation, the __dealloc__s of the base classes are +# only run after the __dealloc__s of the subclasses. +# We use this to detect trashcan usage. +cdef int base_deallocated = 0 +cdef int trashcan_used = 0 +def assert_no_trashcan_used(): + global base_deallocated, trashcan_used + err = trashcan_used + trashcan_used = base_deallocated = 0 + assert not err + + +cdef class Base: + def __dealloc__(self): + global base_deallocated + base_deallocated = 1 + + +# Trashcan disabled by default +cdef class Sub1(Base): + """ + >>> recursion_test(Sub1, 100) + >>> assert_no_trashcan_used() + """ + cdef public attr + + def __cinit__(self, x): + self.attr = x + + def __dealloc__(self): + global base_deallocated, trashcan_used + trashcan_used += base_deallocated + + +@cython.trashcan(True) +cdef class Middle(Base): + cdef public foo + + +# Trashcan disabled explicitly +@cython.trashcan(False) +cdef class Sub2(Middle): + """ + >>> recursion_test(Sub2, 1000) + >>> assert_no_trashcan_used() + """ + cdef public attr + + def __cinit__(self, x): + self.attr = x + + def __dealloc__(self): + global base_deallocated, trashcan_used + trashcan_used += base_deallocated |