Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-93649: Split gc- and allocation tests from _testcapimodule.c #104403

Merged
merged 1 commit into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
@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
@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
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c

# Some testing modules MUST be built as shared libraries.
Expand Down
344 changes: 344 additions & 0 deletions Modules/_testcapi/gc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
#include "parts.h"

static PyObject*
test_gc_control(PyObject *self, PyObject *Py_UNUSED(ignored))
{
int orig_enabled = PyGC_IsEnabled();
const char* msg = "ok";
int old_state;

old_state = PyGC_Enable();
msg = "Enable(1)";
if (old_state != orig_enabled) {
goto failed;
}
msg = "IsEnabled(1)";
if (!PyGC_IsEnabled()) {
goto failed;
}

old_state = PyGC_Disable();
msg = "disable(2)";
if (!old_state) {
goto failed;
}
msg = "IsEnabled(2)";
if (PyGC_IsEnabled()) {
goto failed;
}

old_state = PyGC_Enable();
msg = "enable(3)";
if (old_state) {
goto failed;
}
msg = "IsEnabled(3)";
if (!PyGC_IsEnabled()) {
goto failed;
}

if (!orig_enabled) {
old_state = PyGC_Disable();
msg = "disable(4)";
if (old_state) {
goto failed;
}
msg = "IsEnabled(4)";
if (PyGC_IsEnabled()) {
goto failed;
}
}

Py_RETURN_NONE;

failed:
/* Try to clean up if we can. */
if (orig_enabled) {
PyGC_Enable();
} else {
PyGC_Disable();
}
PyErr_Format(PyExc_ValueError, "GC control failed in %s", msg);
return NULL;
}

static PyObject *
without_gc(PyObject *Py_UNUSED(self), PyObject *obj)
{
PyTypeObject *tp = (PyTypeObject*)obj;
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
return PyErr_Format(PyExc_TypeError, "heap type expected, got %R", obj);
}
if (PyType_IS_GC(tp)) {
// Don't try this at home, kids:
tp->tp_flags -= Py_TPFLAGS_HAVE_GC;
tp->tp_free = PyObject_Del;
tp->tp_traverse = NULL;
tp->tp_clear = NULL;
}
assert(!PyType_IS_GC(tp));
return Py_NewRef(obj);
}

static void
slot_tp_del(PyObject *self)
{
PyObject *del, *res;

/* Temporarily resurrect the object. */
assert(Py_REFCNT(self) == 0);
Py_SET_REFCNT(self, 1);

/* Save the current exception, if any. */
PyObject *exc = PyErr_GetRaisedException();

PyObject *tp_del = PyUnicode_InternFromString("__tp_del__");
if (tp_del == NULL) {
PyErr_WriteUnraisable(NULL);
PyErr_SetRaisedException(exc);
return;
}
/* Execute __del__ method, if any. */
del = _PyType_Lookup(Py_TYPE(self), tp_del);
Py_DECREF(tp_del);
if (del != NULL) {
res = PyObject_CallOneArg(del, self);
if (res == NULL)
PyErr_WriteUnraisable(del);
else
Py_DECREF(res);
}

/* Restore the saved exception. */
PyErr_SetRaisedException(exc);

/* Undo the temporary resurrection; can't use DECREF here, it would
* cause a recursive call.
*/
assert(Py_REFCNT(self) > 0);
Py_SET_REFCNT(self, Py_REFCNT(self) - 1);
if (Py_REFCNT(self) == 0) {
/* this is the normal path out */
return;
}

/* __del__ resurrected it! Make it look like the original Py_DECREF
* never happened.
*/
{
Py_ssize_t refcnt = Py_REFCNT(self);
_Py_NewReferenceNoTotal(self);
Py_SET_REFCNT(self, refcnt);
}
assert(!PyType_IS_GC(Py_TYPE(self)) || PyObject_GC_IsTracked(self));
}

static PyObject *
with_tp_del(PyObject *self, PyObject *args)
{
PyObject *obj;
PyTypeObject *tp;

if (!PyArg_ParseTuple(args, "O:with_tp_del", &obj))
return NULL;
tp = (PyTypeObject *) obj;
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
PyErr_Format(PyExc_TypeError,
"heap type expected, got %R", obj);
return NULL;
}
tp->tp_del = slot_tp_del;
return Py_NewRef(obj);
}


struct gc_visit_state_basic {
PyObject *target;
int found;
};

static int
gc_visit_callback_basic(PyObject *obj, void *arg)
{
struct gc_visit_state_basic *state = (struct gc_visit_state_basic *)arg;
if (obj == state->target) {
state->found = 1;
return 0;
}
return 1;
}

static PyObject *
test_gc_visit_objects_basic(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
PyObject *obj;
struct gc_visit_state_basic state;

obj = PyList_New(0);
if (obj == NULL) {
return NULL;
}
state.target = obj;
state.found = 0;

PyUnstable_GC_VisitObjects(gc_visit_callback_basic, &state);
Py_DECREF(obj);
if (!state.found) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_basic: Didn't find live list");
return NULL;
}
Py_RETURN_NONE;
}

static int
gc_visit_callback_exit_early(PyObject *obj, void *arg)
{
int *visited_i = (int *)arg;
(*visited_i)++;
if (*visited_i == 2) {
return 0;
}
return 1;
}

static PyObject *
test_gc_visit_objects_exit_early(PyObject *Py_UNUSED(self),
PyObject *Py_UNUSED(ignored))
{
int visited_i = 0;
PyUnstable_GC_VisitObjects(gc_visit_callback_exit_early, &visited_i);
if (visited_i != 2) {
PyErr_SetString(
PyExc_AssertionError,
"test_gc_visit_objects_exit_early: did not exit when expected");
}
Py_RETURN_NONE;
}

typedef struct {
PyObject_HEAD
} ObjExtraData;

static PyObject *
obj_extra_data_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
size_t extra_size = sizeof(PyObject *);
PyObject *obj = PyUnstable_Object_GC_NewWithExtraData(type, extra_size);
if (obj == NULL) {
return PyErr_NoMemory();
}
PyObject_GC_Track(obj);
return obj;
}

static PyObject **
obj_extra_data_get_extra_storage(PyObject *self)
{
return (PyObject **)((char *)self + Py_TYPE(self)->tp_basicsize);
}

static PyObject *
obj_extra_data_get(PyObject *self, void *Py_UNUSED(ignored))
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
PyObject *value = *extra_storage;
if (!value) {
Py_RETURN_NONE;
}
return Py_NewRef(value);
}

static int
obj_extra_data_set(PyObject *self, PyObject *newval, void *Py_UNUSED(ignored))
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
Py_CLEAR(*extra_storage);
if (newval) {
*extra_storage = Py_NewRef(newval);
}
return 0;
}

static PyGetSetDef obj_extra_data_getset[] = {
{"extra", (getter)obj_extra_data_get, (setter)obj_extra_data_set, NULL},
{NULL}
};

static int
obj_extra_data_traverse(PyObject *self, visitproc visit, void *arg)
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
PyObject *value = *extra_storage;
Py_VISIT(value);
return 0;
}

static int
obj_extra_data_clear(PyObject *self)
{
PyObject **extra_storage = obj_extra_data_get_extra_storage(self);
Py_CLEAR(*extra_storage);
return 0;
}

static void
obj_extra_data_dealloc(PyObject *self)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
obj_extra_data_clear(self);
tp->tp_free(self);
Py_DECREF(tp);
}

static PyType_Slot ObjExtraData_Slots[] = {
{Py_tp_getset, obj_extra_data_getset},
{Py_tp_dealloc, obj_extra_data_dealloc},
{Py_tp_traverse, obj_extra_data_traverse},
{Py_tp_clear, obj_extra_data_clear},
{Py_tp_new, obj_extra_data_new},
{Py_tp_free, PyObject_GC_Del},
{0, NULL},
};

static PyType_Spec ObjExtraData_TypeSpec = {
.name = "_testcapi.ObjExtraData",
.basicsize = sizeof(ObjExtraData),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.slots = ObjExtraData_Slots,
};

static PyMethodDef test_methods[] = {
{"test_gc_control", test_gc_control, METH_NOARGS},
{"test_gc_visit_objects_basic", test_gc_visit_objects_basic, METH_NOARGS, NULL},
{"test_gc_visit_objects_exit_early", test_gc_visit_objects_exit_early, METH_NOARGS, NULL},
{"without_gc", without_gc, METH_O, NULL},
{"with_tp_del", with_tp_del, METH_VARARGS, NULL},
{NULL}
};

int _PyTestCapi_Init_GC(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}

PyObject *ObjExtraData_Type = PyType_FromModuleAndSpec(
mod, &ObjExtraData_TypeSpec, NULL);
if (ObjExtraData_Type == 0) {
return -1;
}
int ret = PyModule_AddType(mod, (PyTypeObject*)ObjExtraData_Type);
Py_DECREF(ObjExtraData_Type);
if (ret < 0) {
return ret;
}

return 0;
}
1 change: 1 addition & 0 deletions Modules/_testcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ int _PyTestCapi_Init_Code(PyObject *module);
int _PyTestCapi_Init_Buffer(PyObject *module);
int _PyTestCapi_Init_PyOS(PyObject *module);
int _PyTestCapi_Init_Immortal(PyObject *module);
int _PyTestCapi_Init_GC(PyObject *mod);

#ifdef LIMITED_API_AVAILABLE
int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
Expand Down
Loading