GH-135552: Add tests to check weakref clearing (GH-136304)

These are tests to ensure behaviour introduced by GH-136189 is working as expected.

Co-authored-by: Mikhail Borisov <43937008+fxeqxmulfx@users.noreply.github.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Co-authored-by: Neil Schemenauer <nas-github@arctrix.com>
This commit is contained in:
Sergey Miryanov 2025-08-07 17:45:33 -07:00 committed by GitHub
parent 37b5a0d671
commit 25518f51dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 0 deletions

View file

@ -1155,6 +1155,37 @@ def test_something(self):
""")
assert_python_ok("-c", source)
def test_do_not_cleanup_type_subclasses_before_finalization(self):
# See https://github.com/python/cpython/issues/135552
# If we cleanup weakrefs for tp_subclasses before calling
# the finalizer (__del__) then the line `fail = BaseNode.next.next`
# should fail because we are trying to access a subclass
# attribute. But subclass type cache was not properly invalidated.
code = """
class BaseNode:
def __del__(self):
BaseNode.next = BaseNode.next.next
fail = BaseNode.next.next
class Node(BaseNode):
pass
BaseNode.next = Node()
BaseNode.next.next = Node()
"""
# this test checks garbage collection while interp
# finalization
assert_python_ok("-c", textwrap.dedent(code))
code_inside_function = textwrap.dedent(F"""
def test():
{textwrap.indent(code, ' ')}
test()
""")
# this test checks regular garbage collection
assert_python_ok("-c", code_inside_function)
class IncrementalGCTests(unittest.TestCase):
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")

View file

@ -1044,6 +1044,32 @@ def callback(obj):
stderr = res.err.decode("ascii", "backslashreplace")
self.assertNotRegex(stderr, "_Py_Dealloc: Deallocator of type 'TestObj'")
def test_clearing_weakrefs_in_gc(self):
# This test checks that when finalizers are called:
# 1. weakrefs with callbacks have been cleared
# 2. weakrefs without callbacks have not been cleared
errors = []
def test():
class Class:
def __init__(self):
self._self = self
self.wr1 = weakref.ref(Class, lambda x: None)
self.wr2 = weakref.ref(Class)
def __del__(self):
# we can't use assert* here, because gc will swallow
# exceptions
if self.wr1() is not None:
errors.append("weakref with callback as cleared")
if self.wr2() is not Class:
errors.append("weakref without callback was cleared")
Class()
test()
gc.collect()
self.assertEqual(errors, [])
class SubclassableWeakrefTestCase(TestBase):