]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151130: Add more tests for PyWeakref_* C API (GH-151131)
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 9 Jun 2026 11:11:17 +0000 (14:11 +0300)
committerGitHub <noreply@github.com>
Tue, 9 Jun 2026 11:11:17 +0000 (11:11 +0000)
13 files changed:
Lib/test/test_capi/test_weakref.py [new file with mode: 0644]
Misc/NEWS.d/next/Tests/2026-06-09-11-52-52.gh-issue-151130.1vslPH.rst [new file with mode: 0644]
Modules/Setup.stdlib.in
Modules/_testcapi/parts.h
Modules/_testcapi/weakref.c [new file with mode: 0644]
Modules/_testcapimodule.c
Modules/_testlimitedcapi.c
Modules/_testlimitedcapi/parts.h
Modules/_testlimitedcapi/weakref.c [new file with mode: 0644]
PCbuild/_testcapi.vcxproj
PCbuild/_testcapi.vcxproj.filters
PCbuild/_testlimitedcapi.vcxproj
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 (file)
index 0000000..86ebe92
--- /dev/null
@@ -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 (file)
index 0000000..0333e66
--- /dev/null
@@ -0,0 +1 @@
+Add more tests for ``PyWeakref_*`` C API.
index c3dd47a5e40a6756e1d345172f50e87be1547599..8efea27824f0e886f1b1768d52604d71827746d3 100644 (file)
 @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
 
index a7feca5bd960705498ca89fab4e3125203e74a5a..98b5dd47accde35f8c90ef96eb002abdb38a21ef 100644 (file)
@@ -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 (file)
index 0000000..7c3ad85
--- /dev/null
@@ -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);
+}
index be5ad3e9efa1040611657fb6c81d7daf3421c88a..9c90d1fc36f398e97a568e787edf8be9cdf145b1 100644 (file)
@@ -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;
 }
index 5f2be0dd43954e327a3cbb2a81312fd6aac52412..9314fccc6c915a4a3082f41b46151b15f972086f 100644 (file)
@@ -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;
 }
index 1eea4f74d14416ce0006e3abf032ce117694b8ab..c51d285e19ab0d6f8413345462c1eb890cb278df 100644 (file)
@@ -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 (file)
index 0000000..e7f9d54
--- /dev/null
@@ -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);
+}
index 62312acf248b918a1090369445951777903f6ea0..64e50b67be46561c08d2f5c16acc1c81b67a2315 100644 (file)
     <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" />
index b0e75ce433ab14d994ad7707936567aa5c07b10b..a3b62e1df663e00f0a7c78c1c64a5a07a60d8d47 100644 (file)
     <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">
index 34841ff9780a0111a448c873d7571ff3a44cc696..69558d204dbb8e75bfd9a9c9a77fb15059c43ecf 100644 (file)
     <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" />
index a29973786c9485dbfcc7cb2d16a70a5b68f1c5bb..2bcc3f6ff176bd9df8613eaed16487617ba5cb50 100644 (file)
@@ -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>