]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-151130: Add more tests for PyWeakref_* C API (GH-151131) (GH-151141) (...
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 9 Jun 2026 20:24:39 +0000 (23:24 +0300)
committerGitHub <noreply@github.com>
Tue, 9 Jun 2026 20:24:39 +0000 (22:24 +0200)
(cherry picked from commit cb96d5ea4a63faa0db47213f386012214447be8a)

(cherry picked from commit c3cd75afdf86f6a811663c71da22cc24c784a6f4)

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..1d5b5d3
--- /dev/null
@@ -0,0 +1,121 @@
+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_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 57b90101bbe4a6c6baf071d4168bd2776640c8ae..971549950a64eb527bd0888884a4c2e72672dca9 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
-@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/monitoring.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.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/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.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/monitoring.c _testcapi/weakref.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.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/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
index 41d190961c69ee47a3266d643624ece5702d6ba0..e348587b9208f5e3530fb65760484efd81664596 100644 (file)
@@ -60,5 +60,6 @@ int _PyTestCapi_Init_Hash(PyObject *module);
 int _PyTestCapi_Init_Time(PyObject *module);
 int _PyTestCapi_Init_Monitoring(PyObject *module);
 int _PyTestCapi_Init_Object(PyObject *module);
+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..7137895
--- /dev/null
@@ -0,0 +1,34 @@
+#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 PyMethodDef test_methods[] = {
+    {"pyweakref_getref", pyweakref_getref, METH_O},
+    {NULL},
+};
+
+int
+_PyTestCapi_Init_Weakref(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
index 51baf1e8ef667d0c1e4377c2cb3fa324c142c51a..c18b9713b0c3fe8b8af214253118e26d5a05e06d 100644 (file)
@@ -4329,6 +4329,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Object(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Weakref(m) < 0) {
+        return NULL;
+    }
 
     PyState_AddModule(m, &_testcapimodule);
     return m;
index b183df7751d8dbac47b51e1ab266084f74d70ff4..880d8836d1a7fd71e40304edba7dd505e8d02698 100644 (file)
@@ -86,5 +86,8 @@ PyInit__testlimitedcapi(void)
     if (_PyTestLimitedCAPI_Init_File(mod) < 0) {
         return NULL;
     }
+    if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) {
+        return NULL;
+    }
     return mod;
 }
index 11b2e5c6b833bb3215a8750edc8dcab527119529..4058df7fdef433676e30acd49eca64ab9f0d8c3b 100644 (file)
@@ -41,5 +41,6 @@ int _PyTestLimitedCAPI_Init_Tuple(PyObject *module);
 int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
 int _PyTestLimitedCAPI_Init_VectorcallLimited(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 44dbf2348137e17ee65dd76002c2eae073270cc4..02d755f8d5ce5b08266e626d637f60361e8837ed 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\gc.c" />
     <ClCompile Include="..\Modules\_testcapi\run.c" />
     <ClCompile Include="..\Modules\_testcapi\monitoring.c" />
+    <ClCompile Include="..\Modules\_testcapi\weakref.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
index cae44bc955f7f12748b98f990a5eab72f1efbcec..63025cecd6d6edb222f85ad16c7e24751c4fc755 100644 (file)
     <ClCompile Include="..\Modules\_testcapi\monitoring.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 a5e0be93ab93902c73576e4973be70bb019ea7f8..e020c05fb8df9e879263336f19b8c2334d162657 100644 (file)
     <ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
index 4b3521afc06158c1317a1ee4cea11bf53318e00e..73979d971eec7ec842a4af340d371b6d0ba2a8cd 100644 (file)
@@ -29,6 +29,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\weakref.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi.c" />
   </ItemGroup>
   <ItemGroup>