7
# The tests here are to do with a deterministic order of destructors which
8
# isn't reliable for PyPy. Therefore, on PyPy we treat the test as
9
# "compiles and doesn't crash"
10
IS_PYPY = hasattr(sys, 'pypy_version_info')
12
# Count number of times an object was deallocated twice. This should remain 0.
13
cdef int double_deallocations = 0
14
def assert_no_double_deallocations():
17
global double_deallocations
18
err = double_deallocations
19
double_deallocations = 0
23
# Compute x = f(f(f(...(None)...))) nested n times and throw away the result.
24
# The real test happens when exiting this function: then a big recursive
25
# deallocation of x happens. We are testing two things in the tests below:
26
# that Python does not crash and that no double deallocation happens.
27
# See also https://github.com/python/cpython/pull/11841
28
def recursion_test(f, int n=2**20):
38
>>> recursion_test(Recurse)
39
>>> assert_no_double_deallocations()
44
def __cinit__(self, x):
47
def __dealloc__(self):
48
# Check that we're not being deallocated twice
49
global double_deallocations
50
double_deallocations += self.deallocated
54
cdef class RecurseSub(Recurse):
56
>>> recursion_test(RecurseSub)
57
>>> assert_no_double_deallocations()
59
cdef int subdeallocated
61
def __dealloc__(self):
62
# Check that we're not being deallocated twice
63
global double_deallocations
64
double_deallocations += self.subdeallocated
65
self.subdeallocated = 1
70
cdef class RecurseFreelist:
72
>>> recursion_test(RecurseFreelist)
73
>>> recursion_test(RecurseFreelist, 1000)
74
>>> assert_no_double_deallocations()
79
def __cinit__(self, x):
82
def __dealloc__(self):
83
# Check that we're not being deallocated twice
84
global double_deallocations
85
double_deallocations += self.deallocated
89
# Subclass of list => uses trashcan by default
90
# As long as https://github.com/python/cpython/pull/11841 is not fixed,
91
# this does lead to double deallocations, so we skip that check.
92
cdef class RecurseList(list):
96
>>> recursion_test(RecurseList)
98
def __init__(self, x):
99
super().__init__((x,))
102
# Some tests where the trashcan is NOT used. When the trashcan is not used
103
# in a big recursive deallocation, the __dealloc__s of the base classes are
104
# only run after the __dealloc__s of the subclasses.
105
# We use this to detect trashcan usage.
106
cdef int base_deallocated = 0
107
cdef int trashcan_used = 0
108
def assert_no_trashcan_used():
111
global base_deallocated, trashcan_used
113
trashcan_used = base_deallocated = 0
118
def __dealloc__(self):
119
global base_deallocated
123
# Trashcan disabled by default
124
cdef class Sub1(Base):
126
>>> recursion_test(Sub1, 100)
127
>>> assert_no_trashcan_used()
131
def __cinit__(self, x):
134
def __dealloc__(self):
135
global base_deallocated, trashcan_used
136
trashcan_used += base_deallocated
139
@cython.trashcan(True)
140
cdef class Middle(Base):
144
# Trashcan disabled explicitly
145
@cython.trashcan(False)
146
cdef class Sub2(Middle):
148
>>> recursion_test(Sub2, 1000)
149
>>> assert_no_trashcan_used()
153
def __cinit__(self, x):
156
def __dealloc__(self):
157
global base_deallocated, trashcan_used
158
trashcan_used += base_deallocated