Skip to content

Commit c3cd75a

Browse files
gh-151130: Add more tests for PyWeakref_* C API (GH-151131)
1 parent db4b194 commit c3cd75a

13 files changed

Lines changed: 277 additions & 2 deletions

Lib/test/test_capi/test_weakref.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import weakref
2+
import unittest
3+
from test.support import import_helper
4+
5+
_testcapi = import_helper.import_module('_testcapi')
6+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
7+
NULL = None
8+
9+
class Object:
10+
pass
11+
12+
class Ref(weakref.ReferenceType):
13+
pass
14+
15+
16+
class CAPIWeakrefTest(unittest.TestCase):
17+
def test_pyweakref_check(self):
18+
# Test PyWeakref_Check()
19+
check = _testlimitedcapi.pyweakref_check
20+
obj = Object()
21+
self.assertEqual(check(obj), 0)
22+
self.assertEqual(check(weakref.ref(obj)), 1)
23+
self.assertEqual(check(Ref(obj)), 1)
24+
self.assertEqual(check(weakref.proxy(obj)), 1)
25+
26+
# CRASHES check(NULL)
27+
28+
def test_pyweakref_checkref(self):
29+
# Test PyWeakref_CheckRef()
30+
checkref = _testlimitedcapi.pyweakref_checkref
31+
obj = Object()
32+
self.assertEqual(checkref(obj), 0)
33+
self.assertEqual(checkref(weakref.ref(obj)), 1)
34+
self.assertEqual(checkref(Ref(obj)), 1)
35+
self.assertEqual(checkref(weakref.proxy(obj)), 0)
36+
37+
# CRASHES checkref(NULL)
38+
39+
def test_pyweakref_checkrefexact(self):
40+
# Test PyWeakref_CheckRefExact()
41+
checkrefexact = _testlimitedcapi.pyweakref_checkrefexact
42+
obj = Object()
43+
self.assertEqual(checkrefexact(obj), 0)
44+
self.assertEqual(checkrefexact(weakref.ref(obj)), 1)
45+
self.assertEqual(checkrefexact(Ref(obj)), 0)
46+
self.assertEqual(checkrefexact(weakref.proxy(obj)), 0)
47+
48+
# CRASHES checkrefexact(NULL)
49+
50+
def test_pyweakref_checkproxy(self):
51+
# Test PyWeakref_CheckProxy()
52+
checkproxy = _testlimitedcapi.pyweakref_checkproxy
53+
obj = Object()
54+
self.assertEqual(checkproxy(obj), 0)
55+
self.assertEqual(checkproxy(weakref.ref(obj)), 0)
56+
self.assertEqual(checkproxy(Ref(obj)), 0)
57+
self.assertEqual(checkproxy(weakref.proxy(obj)), 1)
58+
59+
# CRASHES checkproxy(NULL)
60+
61+
def test_pyweakref_getref(self):
62+
# Test PyWeakref_GetRef()
63+
getref = _testcapi.pyweakref_getref
64+
obj = Object()
65+
wr = weakref.ref(obj)
66+
wp = weakref.proxy(obj)
67+
self.assertEqual(getref(wr), (1, obj))
68+
self.assertEqual(getref(wp), (1, obj))
69+
del obj
70+
self.assertEqual(getref(wr), 0)
71+
self.assertEqual(getref(wp), 0)
72+
73+
self.assertRaises(TypeError, getref, 42)
74+
self.assertRaises(SystemError, getref, NULL)
75+
76+
def test_pyweakref_isdead(self):
77+
# Test PyWeakref_IsDead()
78+
isdead = _testcapi.pyweakref_isdead
79+
obj = Object()
80+
wr = weakref.ref(obj)
81+
wp = weakref.proxy(obj)
82+
self.assertEqual(isdead(wr), 0)
83+
self.assertEqual(isdead(wp), 0)
84+
del obj
85+
self.assertEqual(isdead(wr), 1)
86+
self.assertEqual(isdead(wp), 1)
87+
88+
self.assertRaises(TypeError, isdead, 42)
89+
self.assertRaises(SystemError, isdead, NULL)
90+
91+
def test_pyweakref_newref(self):
92+
# Test PyWeakref_NewRef()
93+
newref = _testlimitedcapi.pyweakref_newref
94+
obj = Object()
95+
wr = newref(obj)
96+
self.assertIs(type(wr), weakref.ReferenceType)
97+
# PyWeakref_NewRef() handles None callback as NULL callback
98+
wr = newref(obj, None)
99+
self.assertIs(type(wr), weakref.ReferenceType)
100+
log = []
101+
wr = newref(obj, log.append)
102+
self.assertIs(type(wr), weakref.ReferenceType)
103+
self.assertEqual(log, [])
104+
del obj
105+
self.assertEqual(log, [wr])
106+
107+
self.assertRaises(TypeError, newref, [])
108+
# CRASHES newref(NULL)
109+
110+
def test_pyweakref_newproxy(self):
111+
# Test PyWeakref_NewProxy()
112+
newproxy = _testlimitedcapi.pyweakref_newproxy
113+
obj = Object()
114+
wp = newproxy(obj)
115+
self.assertIs(type(wp), weakref.ProxyType)
116+
# PyWeakref_NewProxy() handles None callback as NULL callback
117+
wp = newproxy(obj, None)
118+
self.assertIs(type(wp), weakref.ProxyType)
119+
log = []
120+
wp = newproxy(obj, log.append)
121+
self.assertIs(type(wp), weakref.ProxyType)
122+
self.assertEqual(log, [])
123+
del obj
124+
self.assertEqual(log, [wp])
125+
126+
def func():
127+
pass
128+
wp = newproxy(func)
129+
self.assertIs(type(wp), weakref.CallableProxyType)
130+
131+
self.assertRaises(TypeError, newproxy, [])
132+
# CRASHES newproxy(NULL)
133+
134+
135+
if __name__ == "__main__":
136+
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add more tests for ``PyWeakref_*`` C API.

Modules/Setup.stdlib.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@
173173
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
174174
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
175175
@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
176-
@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
177-
@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
176+
@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
177+
@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
178178
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
179179
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
180180

Modules/_testcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,6 @@ int _PyTestCapi_Init_Frame(PyObject *mod);
6767
int _PyTestCapi_Init_Type(PyObject *mod);
6868
int _PyTestCapi_Init_Function(PyObject *mod);
6969
int _PyTestCapi_Init_Module(PyObject *mod);
70+
int _PyTestCapi_Init_Weakref(PyObject *mod);
7071

7172
#endif // Py_TESTCAPI_PARTS_H

Modules/_testcapi/weakref.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "parts.h"
2+
#include "util.h"
3+
4+
5+
static PyObject *
6+
pyweakref_getref(PyObject *module, PyObject *ref)
7+
{
8+
NULLABLE(ref);
9+
PyObject *obj = UNINITIALIZED_PTR;
10+
int rc = PyWeakref_GetRef(ref, &obj);
11+
if (rc == -1 && PyErr_Occurred()) {
12+
assert(obj == NULL);
13+
return NULL;
14+
}
15+
if (obj == NULL) {
16+
return Py_BuildValue("i", rc);
17+
}
18+
else {
19+
assert(obj != UNINITIALIZED_PTR);
20+
return Py_BuildValue("iN", rc, obj);
21+
}
22+
}
23+
24+
static PyObject *
25+
pyweakref_isdead(PyObject *module, PyObject *obj)
26+
{
27+
NULLABLE(obj);
28+
int rc = PyWeakref_IsDead(obj);
29+
if (rc == -1 && PyErr_Occurred()) {
30+
return NULL;
31+
}
32+
return PyLong_FromLong(rc);
33+
}
34+
35+
36+
static PyMethodDef test_methods[] = {
37+
{"pyweakref_getref", pyweakref_getref, METH_O},
38+
{"pyweakref_isdead", pyweakref_isdead, METH_O},
39+
{NULL},
40+
};
41+
42+
int
43+
_PyTestCapi_Init_Weakref(PyObject *m)
44+
{
45+
return PyModule_AddFunctions(m, test_methods);
46+
}

Modules/_testcapimodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3909,6 +3909,9 @@ _testcapi_exec(PyObject *m)
39093909
if (_PyTestCapi_Init_Module(m) < 0) {
39103910
return -1;
39113911
}
3912+
if (_PyTestCapi_Init_Weakref(m) < 0) {
3913+
return -1;
3914+
}
39123915

39133916
return 0;
39143917
}

Modules/_testlimitedcapi.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,8 @@ PyInit__testlimitedcapi(void)
9898
if (_PyTestLimitedCAPI_Init_File(mod) < 0) {
9999
return NULL;
100100
}
101+
if (_PyTestLimitedCAPI_Init_Weakref(mod) < 0) {
102+
return NULL;
103+
}
101104
return mod;
102105
}

Modules/_testlimitedcapi/parts.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
4545
int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
4646
int _PyTestLimitedCAPI_Init_Version(PyObject *module);
4747
int _PyTestLimitedCAPI_Init_File(PyObject *module);
48+
int _PyTestLimitedCAPI_Init_Weakref(PyObject *module);
4849

4950
#endif // Py_TESTLIMITEDCAPI_PARTS_H

Modules/_testlimitedcapi/weakref.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "pyconfig.h" // Py_GIL_DISABLED
2+
#ifndef Py_GIL_DISABLED
3+
// Need limited C API 3.5 for PyModule_AddFunctions()
4+
# define Py_LIMITED_API 0x03050000
5+
#endif
6+
7+
#include "parts.h"
8+
#include "util.h"
9+
10+
11+
static PyObject *
12+
pyweakref_check(PyObject *module, PyObject *obj)
13+
{
14+
NULLABLE(obj);
15+
return PyLong_FromLong(PyWeakref_Check(obj));
16+
}
17+
18+
static PyObject *
19+
pyweakref_checkref(PyObject *module, PyObject *obj)
20+
{
21+
NULLABLE(obj);
22+
return PyLong_FromLong(PyWeakref_CheckRef(obj));
23+
}
24+
25+
static PyObject *
26+
pyweakref_checkrefexact(PyObject *module, PyObject *obj)
27+
{
28+
NULLABLE(obj);
29+
return PyLong_FromLong(PyWeakref_CheckRefExact(obj));
30+
}
31+
32+
static PyObject *
33+
pyweakref_checkproxy(PyObject *module, PyObject *obj)
34+
{
35+
NULLABLE(obj);
36+
return PyLong_FromLong(PyWeakref_CheckProxy(obj));
37+
}
38+
39+
static PyObject *
40+
pyweakref_newref(PyObject *module, PyObject *args)
41+
{
42+
PyObject *obj;
43+
PyObject *callback = NULL;
44+
if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
45+
return NULL;
46+
}
47+
NULLABLE(obj);
48+
return PyWeakref_NewRef(obj, callback);
49+
}
50+
51+
static PyObject *
52+
pyweakref_newproxy(PyObject *module, PyObject *args)
53+
{
54+
PyObject *obj;
55+
PyObject *callback = NULL;
56+
if (!PyArg_ParseTuple(args, "O|O", &obj, &callback)) {
57+
return NULL;
58+
}
59+
NULLABLE(obj);
60+
return PyWeakref_NewProxy(obj, callback);
61+
}
62+
63+
64+
static PyMethodDef test_methods[] = {
65+
{"pyweakref_check", pyweakref_check, METH_O},
66+
{"pyweakref_checkref", pyweakref_checkref, METH_O},
67+
{"pyweakref_checkrefexact", pyweakref_checkrefexact, METH_O},
68+
{"pyweakref_checkproxy", pyweakref_checkproxy, METH_O},
69+
{"pyweakref_newref", pyweakref_newref, METH_VARARGS},
70+
{"pyweakref_newproxy", pyweakref_newproxy, METH_VARARGS},
71+
{NULL},
72+
};
73+
74+
int
75+
_PyTestLimitedCAPI_Init_Weakref(PyObject *m)
76+
{
77+
return PyModule_AddFunctions(m, test_methods);
78+
}

PCbuild/_testcapi.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
<ClCompile Include="..\Modules\_testcapi\frame.c" />
134134
<ClCompile Include="..\Modules\_testcapi\type.c" />
135135
<ClCompile Include="..\Modules\_testcapi\function.c" />
136+
<ClCompile Include="..\Modules\_testcapi\weakref.c" />
136137
</ItemGroup>
137138
<ItemGroup>
138139
<ResourceCompile Include="..\PC\python_nt.rc" />

0 commit comments

Comments
 (0)