]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-135552: Add tests to check weakref clearing (GH-136304)
authorSergey Miryanov <sergey.miryanov@gmail.com>
Fri, 8 Aug 2025 00:45:33 +0000 (17:45 -0700)
committerGitHub <noreply@github.com>
Fri, 8 Aug 2025 00:45:33 +0000 (17:45 -0700)
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>
Lib/test/test_gc.py
Lib/test/test_weakref.py

index 3ec211531c4c700261763567f1c203eb9b66636d..7c9adf3049a13151b0e76fa3ffa8a36db52fc48a 100644 (file)
@@ -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")
index 4c7c900eb56ae16344e287b840c402776fcca56d..47f6b46061ac3045a5242714437621bb8b97f85a 100644 (file)
@@ -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):