Skip to content

Commit e7ab475

Browse files
committed
gh-93649: Split gc- and allocation tests from _testcapimodule.c
1 parent 7d7dd4c commit e7ab475

File tree

6 files changed

+353
-327
lines changed

6 files changed

+353
-327
lines changed

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
170170
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
171171
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
172-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c
172+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c
173173
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
174174

175175
# Some testing modules MUST be built as shared libraries.

Modules/_testcapi/gc.c

+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
#include "parts.h"
2+
3+
static PyObject*
4+
test_gc_control(PyObject *self, PyObject *Py_UNUSED(ignored))
5+
{
6+
int orig_enabled = PyGC_IsEnabled();
7+
const char* msg = "ok";
8+
int old_state;
9+
10+
old_state = PyGC_Enable();
11+
msg = "Enable(1)";
12+
if (old_state != orig_enabled) {
13+
goto failed;
14+
}
15+
msg = "IsEnabled(1)";
16+
if (!PyGC_IsEnabled()) {
17+
goto failed;
18+
}
19+
20+
old_state = PyGC_Disable();
21+
msg = "disable(2)";
22+
if (!old_state) {
23+
goto failed;
24+
}
25+
msg = "IsEnabled(2)";
26+
if (PyGC_IsEnabled()) {
27+
goto failed;
28+
}
29+
30+
old_state = PyGC_Enable();
31+
msg = "enable(3)";
32+
if (old_state) {
33+
goto failed;
34+
}
35+
msg = "IsEnabled(3)";
36+
if (!PyGC_IsEnabled()) {
37+
goto failed;
38+
}
39+
40+
if (!orig_enabled) {
41+
old_state = PyGC_Disable();
42+
msg = "disable(4)";
43+
if (old_state) {
44+
goto failed;
45+
}
46+
msg = "IsEnabled(4)";
47+
if (PyGC_IsEnabled()) {
48+
goto failed;
49+
}
50+
}
51+
52+
Py_RETURN_NONE;
53+
54+
failed:
55+
/* Try to clean up if we can. */
56+
if (orig_enabled) {
57+
PyGC_Enable();
58+
} else {
59+
PyGC_Disable();
60+
}
61+
PyErr_Format(PyExc_ValueError, "GC control failed in %s", msg);
62+
return NULL;
63+
}
64+
65+
static PyObject *
66+
without_gc(PyObject *Py_UNUSED(self), PyObject *obj)
67+
{
68+
PyTypeObject *tp = (PyTypeObject*)obj;
69+
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
70+
return PyErr_Format(PyExc_TypeError, "heap type expected, got %R", obj);
71+
}
72+
if (PyType_IS_GC(tp)) {
73+
// Don't try this at home, kids:
74+
tp->tp_flags -= Py_TPFLAGS_HAVE_GC;
75+
tp->tp_free = PyObject_Del;
76+
tp->tp_traverse = NULL;
77+
tp->tp_clear = NULL;
78+
}
79+
assert(!PyType_IS_GC(tp));
80+
return Py_NewRef(obj);
81+
}
82+
83+
static void
84+
slot_tp_del(PyObject *self)
85+
{
86+
PyObject *del, *res;
87+
88+
/* Temporarily resurrect the object. */
89+
assert(Py_REFCNT(self) == 0);
90+
Py_SET_REFCNT(self, 1);
91+
92+
/* Save the current exception, if any. */
93+
PyObject *exc = PyErr_GetRaisedException();
94+
95+
PyObject *tp_del = PyUnicode_InternFromString("__tp_del__");
96+
if (tp_del == NULL) {
97+
PyErr_WriteUnraisable(NULL);
98+
PyErr_SetRaisedException(exc);
99+
return;
100+
}
101+
/* Execute __del__ method, if any. */
102+
del = _PyType_Lookup(Py_TYPE(self), tp_del);
103+
Py_DECREF(tp_del);
104+
if (del != NULL) {
105+
res = PyObject_CallOneArg(del, self);
106+
if (res == NULL)
107+
PyErr_WriteUnraisable(del);
108+
else
109+
Py_DECREF(res);
110+
}
111+
112+
/* Restore the saved exception. */
113+
PyErr_SetRaisedException(exc);
114+
115+
/* Undo the temporary resurrection; can't use DECREF here, it would
116+
* cause a recursive call.
117+
*/
118+
assert(Py_REFCNT(self) > 0);
119+
Py_SET_REFCNT(self, Py_REFCNT(self) - 1);
120+
if (Py_REFCNT(self) == 0) {
121+
/* this is the normal path out */
122+
return;
123+
}
124+
125+
/* __del__ resurrected it! Make it look like the original Py_DECREF
126+
* never happened.
127+
*/
128+
{
129+
Py_ssize_t refcnt = Py_REFCNT(self);
130+
_Py_NewReferenceNoTotal(self);
131+
Py_SET_REFCNT(self, refcnt);
132+
}
133+
assert(!PyType_IS_GC(Py_TYPE(self)) || PyObject_GC_IsTracked(self));
134+
}
135+
136+
static PyObject *
137+
with_tp_del(PyObject *self, PyObject *args)
138+
{
139+
PyObject *obj;
140+
PyTypeObject *tp;
141+
142+
if (!PyArg_ParseTuple(args, "O:with_tp_del", &obj))
143+
return NULL;
144+
tp = (PyTypeObject *) obj;
145+
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
146+
PyErr_Format(PyExc_TypeError,
147+
"heap type expected, got %R", obj);
148+
return NULL;
149+
}
150+
tp->tp_del = slot_tp_del;
151+
return Py_NewRef(obj);
152+
}
153+
154+
155+
struct gc_visit_state_basic {
156+
PyObject *target;
157+
int found;
158+
};
159+
160+
static int
161+
gc_visit_callback_basic(PyObject *obj, void *arg)
162+
{
163+
struct gc_visit_state_basic *state = (struct gc_visit_state_basic *)arg;
164+
if (obj == state->target) {
165+
state->found = 1;
166+
return 0;
167+
}
168+
return 1;
169+
}
170+
171+
static PyObject *
172+
test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
173+
PyObject *Py_UNUSED(ignored))
174+
{
175+
PyObject *obj;
176+
struct gc_visit_state_basic state;
177+
178+
obj = PyList_New(0);
179+
if (obj == NULL) {
180+
return NULL;
181+
}
182+
state.target = obj;
183+
state.found = 0;
184+
185+
PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
186+
Py_DECREF(obj);
187+
if (!state.found) {
188+
PyErr_SetString(
189+
PyExc_AssertionError,
190+
"test_gc_visit_objects_basic: Didn't find live list");
191+
return NULL;
192+
}
193+
Py_RETURN_NONE;
194+
}
195+
196+
static int
197+
gc_visit_callback_exit_early(PyObject *obj, void *arg)
198+
{
199+
int *visited_i = (int *)arg;
200+
(*visited_i)++;
201+
if (*visited_i == 2) {
202+
return 0;
203+
}
204+
return 1;
205+
}
206+
207+
static PyObject *
208+
test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
209+
PyObject *Py_UNUSED(ignored))
210+
{
211+
int visited_i = 0;
212+
PyUnstable_GC_VisitObjects(gc_visit_callback_exit_early, &visited_i);
213+
if (visited_i != 2) {
214+
PyErr_SetString(
215+
PyExc_AssertionError,
216+
"test_gc_visit_objects_exit_early: did not exit when expected");
217+
}
218+
Py_RETURN_NONE;
219+
}
220+
221+
typedef struct {
222+
PyObject_HEAD
223+
} ObjExtraData;
224+
225+
static PyObject *
226+
obj_extra_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
227+
{
228+
size_t extra_size = sizeof(PyObject *);
229+
PyObject *obj = PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
230+
if (obj == NULL) {
231+
return PyErr_NoMemory();
232+
}
233+
PyObject_GC_Track(obj);
234+
return obj;
235+
}
236+
237+
static PyObject **
238+
obj_extra_data_get_extra_storage(PyObject *self)
239+
{
240+
return (PyObject **)((char *)self + Py_TYPE(self)->tp_basicsize);
241+
}
242+
243+
static PyObject *
244+
obj_extra_data_get(PyObject *self, void *Py_UNUSED(ignored))
245+
{
246+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
247+
PyObject *value = *extra_storage;
248+
if (!value) {
249+
Py_RETURN_NONE;
250+
}
251+
return Py_NewRef(value);
252+
}
253+
254+
static int
255+
obj_extra_data_set(PyObject *self, PyObject *newval, void *Py_UNUSED(ignored))
256+
{
257+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
258+
Py_CLEAR(*extra_storage);
259+
if (newval) {
260+
*extra_storage = Py_NewRef(newval);
261+
}
262+
return 0;
263+
}
264+
265+
static PyGetSetDef obj_extra_data_getset[] = {
266+
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
267+
{NULL}
268+
};
269+
270+
static int
271+
obj_extra_data_traverse(PyObject *self, visitproc visit, void *arg)
272+
{
273+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
274+
PyObject *value = *extra_storage;
275+
Py_VISIT(value);
276+
return 0;
277+
}
278+
279+
static int
280+
obj_extra_data_clear(PyObject *self)
281+
{
282+
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
283+
Py_CLEAR(*extra_storage);
284+
return 0;
285+
}
286+
287+
static void
288+
obj_extra_data_dealloc(PyObject *self)
289+
{
290+
PyTypeObject *tp = Py_TYPE(self);
291+
PyObject_GC_UnTrack(self);
292+
obj_extra_data_clear(self);
293+
tp->tp_free(self);
294+
Py_DECREF(tp);
295+
}
296+
297+
static PyType_Slot ObjExtraData_Slots[] = {
298+
{Py_tp_getset, obj_extra_data_getset},
299+
{Py_tp_dealloc, obj_extra_data_dealloc},
300+
{Py_tp_traverse, obj_extra_data_traverse},
301+
{Py_tp_clear, obj_extra_data_clear},
302+
{Py_tp_new, obj_extra_data_new},
303+
{Py_tp_free, PyObject_GC_Del},
304+
{0, NULL},
305+
};
306+
307+
static PyType_Spec ObjExtraData_TypeSpec = {
308+
.name = "_testcapi.ObjExtraData",
309+
.basicsize = sizeof(ObjExtraData),
310+
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
311+
.slots = ObjExtraData_Slots,
312+
};
313+
314+
static PyMethodDef test_methods[] = {
315+
{"test_gc_control", test_gc_control, METH_NOARGS},
316+
{"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL},
317+
{"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL},
318+
{"without_gc", without_gc, METH_O, NULL},
319+
{"with_tp_del", with_tp_del, METH_VARARGS, NULL},
320+
{NULL}
321+
};
322+
323+
int _PyTestCapi_Init_GC(PyObject *mod)
324+
{
325+
if (PyModule_AddFunctions(mod, test_methods) < 0) {
326+
return -1;
327+
}
328+
if (PyModule_AddFunctions(mod, test_methods) < 0) {
329+
return -1;
330+
}
331+
332+
PyObject *ObjExtraData_Type = PyType_FromModuleAndSpec(
333+
mod, &ObjExtraData_TypeSpec, NULL);
334+
if (ObjExtraData_Type == 0) {
335+
return -1;
336+
}
337+
int ret = PyModule_AddType(mod, (PyTypeObject*)ObjExtraData_Type);
338+
Py_DECREF(ObjExtraData_Type);
339+
if (ret < 0) {
340+
return ret;
341+
}
342+
343+
return 0;
344+
}

Modules/_testcapi/parts.h

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ int _PyTestCapi_Init_Code(PyObject *module);
4141
int _PyTestCapi_Init_Buffer(PyObject *module);
4242
int _PyTestCapi_Init_PyOS(PyObject *module);
4343
int _PyTestCapi_Init_Immortal(PyObject *module);
44+
int _PyTestCapi_Init_GC(PyObject *mod);
4445

4546
#ifdef LIMITED_API_AVAILABLE
4647
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);

0 commit comments

Comments
 (0)