https://github.com/python/cpython/commit/c3cd75afdf86f6a811663c71da22cc24c784a6f4
commit: c3cd75afdf86f6a811663c71da22cc24c784a6f4
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-09T11:11:17Z
summary:

gh-151130: Add more tests for PyWeakref_* C API (GH-151131)

files:
A Lib/test/test_capi/test_weakref.py
A Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst
A Modules/_testcapi/weakref.c
A Modules/_testlimitedcapi/weakref.c
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M Modules/_testlimitedcapi.c
M Modules/_testlimitedcapi/parts.h
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters
M PCbuild/_testlimitedcapi.vcxproj
M PCbuild/_testlimitedcapi.vcxproj.filters

diff --git a/Lib/test/test_capi/test_weakref.py 
b/Lib/test/test_capi/test_weakref.py
new file mode 100644
index 00000000000000..86ebe92da8d95d
--- /dev/null
+++ b/Lib/test/test_capi/test_weakref.py
@@ -0,0 +1,136 @@
+import weakref
+import unittest
+from test.support import import_helper
+
+_testcapi = import_helper.import_module('_testcapi')
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+NULL = None
+
+class Object:
+    pass
+
+class Ref(weakref.ReferenceType):
+    pass
+
+
+class CAPIWeakrefTest(unittest.TestCase):
+    def test_pyweakref_check(self):
+        # Test PyWeakref_Check()
+        check = _testlimitedcapi.pyweakref_check
+        obj = Object()
+        self.assertEqual(check(obj), 0)
+        self.assertEqual(check(weakref.ref(obj)), 1)
+        self.assertEqual(check(Ref(obj)), 1)
+        self.assertEqual(check(weakref.proxy(obj)), 1)
+
+        # CRASHES check(NULL)
+
+    def test_pyweakref_checkref(self):
+        # Test PyWeakref_CheckRef()
+        checkref = _testlimitedcapi.pyweakref_checkref
+        obj = Object()
+        self.assertEqual(checkref(obj), 0)
+        self.assertEqual(checkref(weakref.ref(obj)), 1)
+        self.assertEqual(checkref(Ref(obj)), 1)
+        self.assertEqual(checkref(weakref.proxy(obj)), 0)
+
+        # CRASHES checkref(NULL)
+
+    def test_pyweakref_checkrefexact(self):
+        # Test PyWeakref_CheckRefExact()
+        checkrefexact = _testlimitedcapi.pyweakref_checkrefexact
+        obj = Object()
+        self.assertEqual(checkrefexact(obj), 0)
+        self.assertEqual(checkrefexact(weakref.ref(obj)), 1)
+        self.assertEqual(checkrefexact(Ref(obj)), 0)
+        self.assertEqual(checkrefexact(weakref.proxy(obj)), 0)
+
+        # CRASHES checkrefexact(NULL)
+
+    def test_pyweakref_checkproxy(self):
+        # Test PyWeakref_CheckProxy()
+        checkproxy = _testlimitedcapi.pyweakref_checkproxy
+        obj = Object()
+        self.assertEqual(checkproxy(obj), 0)
+        self.assertEqual(checkproxy(weakref.ref(obj)), 0)
+        self.assertEqual(checkproxy(Ref(obj)), 0)
+        self.assertEqual(checkproxy(weakref.proxy(obj)), 1)
+
+        # CRASHES checkproxy(NULL)
+
+    def test_pyweakref_getref(self):
+        # Test PyWeakref_GetRef()
+        getref = _testcapi.pyweakref_getref
+        obj = Object()
+        wr = weakref.ref(obj)
+        wp = weakref.proxy(obj)
+        self.assertEqual(getref(wr), (1, obj))
+        self.assertEqual(getref(wp), (1, obj))
+        del obj
+        self.assertEqual(getref(wr), 0)
+        self.assertEqual(getref(wp), 0)
+
+        self.assertRaises(TypeError, getref, 42)
+        self.assertRaises(SystemError, getref, NULL)
+
+    def test_pyweakref_isdead(self):
+        # Test PyWeakref_IsDead()
+        isdead = _testcapi.pyweakref_isdead
+        obj = Object()
+        wr = weakref.ref(obj)
+        wp = weakref.proxy(obj)
+        self.assertEqual(isdead(wr), 0)
+        self.assertEqual(isdead(wp), 0)
+        del obj
+        self.assertEqual(isdead(wr), 1)
+        self.assertEqual(isdead(wp), 1)
+
+        self.assertRaises(TypeError, isdead, 42)
+        self.assertRaises(SystemError, isdead, NULL)
+
+    def test_pyweakref_newref(self):
+        # Test PyWeakref_NewRef()
+        newref = _testlimitedcapi.pyweakref_newref
+        obj = Object()
+        wr = newref(obj)
+        self.assertIs(type(wr), weakref.ReferenceType)
+        # PyWeakref_NewRef() handles None callback as NULL callback
+        wr = newref(obj, None)
+        self.assertIs(type(wr), weakref.ReferenceType)
+        log = []
+        wr = newref(obj, log.append)
+        self.assertIs(type(wr), weakref.ReferenceType)
+        self.assertEqual(log, [])
+        del obj
+        self.assertEqual(log, [wr])
+
+        self.assertRaises(TypeError, newref, [])
+        # CRASHES newref(NULL)
+
+    def test_pyweakref_newproxy(self):
+        # Test PyWeakref_NewProxy()
+        newproxy = _testlimitedcapi.pyweakref_newproxy
+        obj = Object()
+        wp = newproxy(obj)
+        self.assertIs(type(wp), weakref.ProxyType)
+        # PyWeakref_NewProxy() handles None callback as NULL callback
+        wp = newproxy(obj, None)
+        self.assertIs(type(wp), weakref.ProxyType)
+        log = []
+        wp = newproxy(obj, log.append)
+        self.assertIs(type(wp), weakref.ProxyType)
+        self.assertEqual(log, [])
+        del obj
+        self.assertEqual(log, [wp])
+
+        def func():
+            pass
+        wp = newproxy(func)
+        self.assertIs(type(wp), weakref.CallableProxyType)
+
+        self.assertRaises(TypeError, newproxy, [])
+        # CRASHES newproxy(NULL)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst 
b/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst
new file mode 100644
index 00000000000000..0333e66446ce16
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst
@@ -0,0 +1 @@
+Add more tests for ``PyWeakref_*`` C API.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index c3dd47a5e40a67..8efea27824f0e8 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -173,8 +173,8 @@
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c 
_xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c 
_testinternalcapi/test_lock.c _testinternalcapi/pytime.c 
_testinternalcapi/set.c _testinternalcapi/test_critical_sections.c 
_testinternalcapi/complex.c _testinternalcapi/interpreter.c 
_testinternalcapi/tuple.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c 
_testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c 
_testcapi/type.c _testcapi/function.c _testcapi/module.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c 
_testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c 
_testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c 
_testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c 
_testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c 
_testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c 
_testlimitedcapi/version.c _testlimitedcapi/file.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c 
_testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c 
_testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c 
_testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c 
_testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c 
_testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c 
_testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c 
_testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c 
_testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c 
_testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c 
_testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c 
_testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c 
_testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c 
_testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c 
_testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c 
_testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c 
_testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c 
_testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c 
_testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c 
_testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index a7feca5bd96070..98b5dd47accde3 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -67,5 +67,6 @@ int _PyTestCapi_Init_Frame(PyObject *mod);
 int _PyTestCapi_Init_Type(PyObject *mod);
 int _PyTestCapi_Init_Function(PyObject *mod);
 int _PyTestCapi_Init_Module(PyObject *mod);
+int _PyTestCapi_Init_Weakref(PyObject *mod);
 
 #endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapi/weakref.c b/Modules/_testcapi/weakref.c
new file mode 100644
index 00000000000000..7c3ad8565991b7
--- /dev/null
+++ b/Modules/_testcapi/weakref.c
@@ -0,0 +1,46 @@
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+pyweakref_getref(PyObject *module, PyObject *ref)
+{
+    NULLABLE(ref);
+    PyObject *obj = UNINITIALIZED_PTR;
+    int rc = PyWeakref_GetRef(ref, &obj);
+    if (rc == -1 && PyErr_Occurred()) {
+        assert(obj == NULL);
+        return NULL;
+    }
+    if (obj == NULL) {
+        return Py_BuildValue("i", rc);
+    }
+    else {
+        assert(obj != UNINITIALIZED_PTR);
+        return Py_BuildValue("iN", rc, obj);
+    }
+}
+
+static PyObject *
+pyweakref_isdead(PyObject *module, PyObject *obj)
+{
+    NULLABLE(obj);
+    int rc = PyWeakref_IsDead(obj);
+    if (rc == -1 && PyErr_Occurred()) {
+        return NULL;
+    }
+    return PyLong_FromLong(rc);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"pyweakref_getref", pyweakref_getref, METH_O},
+    {"pyweakref_isdead", pyweakref_isdead, METH_O},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Weakref(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index be5ad3e9efa104..9c90d1fc36f398 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3909,6 +3909,9 @@ _testcapi_exec(PyObject *m)
     if (_PyTestCapi_Init_Module(m) < 0) {
         return -1;
     }
+    if (_PyTestCapi_Init_Weakref(m) < 0) {
+        return -1;
+    }
 
     return 0;
 }
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index 5f2be0dd43954e..9314fccc6c915a 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -98,5 +98,8 @@ PyInit__testlimitedcapi(void)
     if (_PyTestLimitedCAPI_Init_File(mod) < 0) {
         return NULL;
     }
+    if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) {
+        return NULL;
+    }
     return mod;
 }
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 1eea4f74d14416..c51d285e19ab0d 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -45,5 +45,6 @@ int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
 int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
 int _PyTestLimitedCAPI_Init_Version(PyObject *module);
 int _PyTestLimitedCAPI_Init_File(PyObject *module);
+int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
 
 #endif // Py_TESTLIMITEDCAPI_PARTS_H
diff --git a/Modules/_testlimitedcapi/weakref.c 
b/Modules/_testlimitedcapi/weakref.c
new file mode 100644
index 00000000000000..e7f9d54d1a0d59
--- /dev/null
+++ b/Modules/_testlimitedcapi/weakref.c
@@ -0,0 +1,78 @@
+#include "pyconfig.h"   // Py_GIL_DISABLED
+#ifndef Py_GIL_DISABLED
+   // Need limited C API 3.5 for PyModule_AddFunctions()
+#  define Py_LIMITED_API 0x03050000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+pyweakref_check(PyObject *module, PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyWeakref_Check(obj));
+}
+
+static PyObject *
+pyweakref_checkref(PyObject *module, PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyWeakref_CheckRef(obj));
+}
+
+static PyObject *
+pyweakref_checkrefexact(PyObject *module, PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyWeakref_CheckRefExact(obj));
+}
+
+static PyObject *
+pyweakref_checkproxy(PyObject *module, PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyWeakref_CheckProxy(obj));
+}
+
+static PyObject *
+pyweakref_newref(PyObject *module, PyObject *args)
+{
+    PyObject *obj;
+    PyObject *callback = NULL;
+    if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
+        return NULL;
+    }
+    NULLABLE(obj);
+    return PyWeakref_NewRef(obj, callback);
+}
+
+static PyObject *
+pyweakref_newproxy(PyObject *module, PyObject *args)
+{
+    PyObject *obj;
+    PyObject *callback = NULL;
+    if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
+        return NULL;
+    }
+    NULLABLE(obj);
+    return PyWeakref_NewProxy(obj, callback);
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"pyweakref_check", pyweakref_check, METH_O},
+    {"pyweakref_checkref", pyweakref_checkref, METH_O},
+    {"pyweakref_checkrefexact", pyweakref_checkrefexact, METH_O},
+    {"pyweakref_checkproxy", pyweakref_checkproxy, METH_O},
+    {"pyweakref_newref", pyweakref_newref, METH_VARARGS},
+    {"pyweakref_newproxy", pyweakref_newproxy, METH_VARARGS},
+    {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Weakref(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 62312acf248b91..64e50b67be4656 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -133,6 +133,7 @@
     <ClCompile Include="..\Modules\_testcapi\frame.c" />
     <ClCompile Include="..\Modules\_testcapi\type.c" />
     <ClCompile Include="..\Modules\_testcapi\function.c" />
+    <ClCompile Include="..\Modules\_testcapi\weakref.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters 
b/PCbuild/_testcapi.vcxproj.filters
index b0e75ce433ab14..a3b62e1df663e0 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -132,6 +132,9 @@
     <ClCompile Include="..\Modules\_testcapi\function.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\weakref.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 34841ff9780a01..69558d204dbb8e 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -117,6 +117,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters 
b/PCbuild/_testlimitedcapi.vcxproj.filters
index a29973786c9485..2bcc3f6ff176bd 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -33,6 +33,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi.c" />
   </ItemGroup>
   <ItemGroup>

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to