From: Sergey Miryanov Date: Fri, 8 Aug 2025 00:45:33 +0000 (-0700) Subject: GH-135552: Add tests to check weakref clearing (GH-136304) X-Git-Tag: v3.15.0a1~740 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=25518f51dc9fd3ffe4f8ae5d53baa3728936be2b;p=thirdparty%2FPython%2Fcpython.git 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 Co-authored-by: Neil Schemenauer --- diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 3ec211531c4c..7c9adf3049a1 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1155,6 +1155,37 @@ class GCTests(unittest.TestCase): """) 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") diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index 4c7c900eb56a..47f6b46061ac 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -1044,6 +1044,32 @@ class ReferencesTestCase(TestBase): 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):