Skip to content

Commit

Permalink
pythongh-93649: Split vectorcall testing from _testcapimodule.c
Browse files Browse the repository at this point in the history
The _testcapimodule.c file is getting too large to work with effectively.
Vectorcall tests aren't the biggest issue -- it's just an area I want to work
on next, so I started there.
It does make it clear that MethodDescriptor2 is related to testing vectorcall,
which wasn't clear before (the /* Test PEP 590 */ section had an ambiguous end).

This PR lays out a general structure of how tests can be split up,
with more splitting to come later if the structure is OK.
  • Loading branch information
encukou committed Jul 4, 2022
1 parent fd76eb5 commit 9020fe3
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 254 deletions.
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,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
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/test_vectorcall.c

# Some testing modules MUST be built as shared libraries.
*shared*
Expand Down
3 changes: 3 additions & 0 deletions Modules/_testcapi/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Tests in this directory are compiled into the _testcapi extension.
The main file for the extension is Modules/_testcapimodule.c, which
calls `_PyTestCapi_Init_*` from these functions.
270 changes: 270 additions & 0 deletions Modules/_testcapi/test_vectorcall.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
#include "testcapimodule_parts.h"
#include <stddef.h> // offsetof


/* Test PEP 590 - Vectorcall */

static int
fastcall_args(PyObject *args, PyObject ***stack, Py_ssize_t *nargs)
{
if (args == Py_None) {
*stack = NULL;
*nargs = 0;
}
else if (PyTuple_Check(args)) {
*stack = ((PyTupleObject *)args)->ob_item;
*nargs = PyTuple_GET_SIZE(args);
}
else {
PyErr_SetString(PyExc_TypeError, "args must be None or a tuple");
return -1;
}
return 0;
}


static PyObject *
test_pyobject_fastcall(PyObject *self, PyObject *args)
{
PyObject *func, *func_args;
PyObject **stack;
Py_ssize_t nargs;

if (!PyArg_ParseTuple(args, "OO", &func, &func_args)) {
return NULL;
}

if (fastcall_args(func_args, &stack, &nargs) < 0) {
return NULL;
}
return _PyObject_FastCall(func, stack, nargs);
}

static PyObject *
test_pyobject_fastcalldict(PyObject *self, PyObject *args)
{
PyObject *func, *func_args, *kwargs;
PyObject **stack;
Py_ssize_t nargs;

if (!PyArg_ParseTuple(args, "OOO", &func, &func_args, &kwargs)) {
return NULL;
}

if (fastcall_args(func_args, &stack, &nargs) < 0) {
return NULL;
}

if (kwargs == Py_None) {
kwargs = NULL;
}
else if (!PyDict_Check(kwargs)) {
PyErr_SetString(PyExc_TypeError, "kwnames must be None or a dict");
return NULL;
}

return PyObject_VectorcallDict(func, stack, nargs, kwargs);
}

static PyObject *
test_pyobject_vectorcall(PyObject *self, PyObject *args)
{
PyObject *func, *func_args, *kwnames = NULL;
PyObject **stack;
Py_ssize_t nargs, nkw;

if (!PyArg_ParseTuple(args, "OOO", &func, &func_args, &kwnames)) {
return NULL;
}

if (fastcall_args(func_args, &stack, &nargs) < 0) {
return NULL;
}

if (kwnames == Py_None) {
kwnames = NULL;
}
else if (PyTuple_Check(kwnames)) {
nkw = PyTuple_GET_SIZE(kwnames);
if (nargs < nkw) {
PyErr_SetString(PyExc_ValueError, "kwnames longer than args");
return NULL;
}
nargs -= nkw;
}
else {
PyErr_SetString(PyExc_TypeError, "kwnames must be None or a tuple");
return NULL;
}
return PyObject_Vectorcall(func, stack, nargs, kwnames);
}

static PyObject *
test_pyvectorcall_call(PyObject *self, PyObject *args)
{
PyObject *func;
PyObject *argstuple;
PyObject *kwargs = NULL;

if (!PyArg_ParseTuple(args, "OO|O", &func, &argstuple, &kwargs)) {
return NULL;
}

if (!PyTuple_Check(argstuple)) {
PyErr_SetString(PyExc_TypeError, "args must be a tuple");
return NULL;
}
if (kwargs != NULL && !PyDict_Check(kwargs)) {
PyErr_SetString(PyExc_TypeError, "kwargs must be a dict");
return NULL;
}

return PyVectorcall_Call(func, argstuple, kwargs);
}

static PyMethodDef TestMethods[] = {
{"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS},
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
{"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS},
{"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS},
{NULL},
};


typedef struct {
PyObject_HEAD
vectorcallfunc vectorcall;
} MethodDescriptorObject;

static PyObject *
MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
{
/* True if using the vectorcall function in MethodDescriptorObject
* but False for MethodDescriptor2Object */
MethodDescriptorObject *md = (MethodDescriptorObject *)callable;
return PyBool_FromLong(md->vectorcall != NULL);
}

static PyObject *
MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw)
{
MethodDescriptorObject *op = (MethodDescriptorObject *)type->tp_alloc(type, 0);
op->vectorcall = MethodDescriptor_vectorcall;
return (PyObject *)op;
}

static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
if (obj == Py_None || obj == NULL) {
Py_INCREF(func);
return func;
}
return PyMethod_New(func, obj);
}

static PyObject *
nop_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
Py_INCREF(func);
return func;
}

static PyObject *
call_return_args(PyObject *self, PyObject *args, PyObject *kwargs)
{
Py_INCREF(args);
return args;
}

static PyTypeObject MethodDescriptorBase_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethodDescriptorBase",
sizeof(MethodDescriptorObject),
.tp_new = MethodDescriptor_new,
.tp_call = PyVectorcall_Call,
.tp_vectorcall_offset = offsetof(MethodDescriptorObject, vectorcall),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_METHOD_DESCRIPTOR | Py_TPFLAGS_HAVE_VECTORCALL,
.tp_descr_get = func_descr_get,
};

static PyTypeObject MethodDescriptorDerived_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethodDescriptorDerived",
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
};

static PyTypeObject MethodDescriptorNopGet_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethodDescriptorNopGet",
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_call = call_return_args,
.tp_descr_get = nop_descr_get,
};

typedef struct {
MethodDescriptorObject base;
vectorcallfunc vectorcall;
} MethodDescriptor2Object;

static PyObject *
MethodDescriptor2_new(PyTypeObject* type, PyObject* args, PyObject *kw)
{
MethodDescriptor2Object *op = PyObject_New(MethodDescriptor2Object, type);
op->base.vectorcall = NULL;
op->vectorcall = MethodDescriptor_vectorcall;
return (PyObject *)op;
}

static PyTypeObject MethodDescriptor2_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"MethodDescriptor2",
sizeof(MethodDescriptor2Object),
.tp_new = MethodDescriptor2_new,
.tp_call = PyVectorcall_Call,
.tp_vectorcall_offset = offsetof(MethodDescriptor2Object, vectorcall),
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_VECTORCALL,
};


int
_PyTestCapi_Init_Vectorcall(PyObject *m) {
if (PyModule_AddFunctions(m, TestMethods) < 0) {
return -1;
}

if (PyType_Ready(&MethodDescriptorBase_Type) < 0) {
return -1;
}
if (PyModule_AddType(m, &MethodDescriptorBase_Type) < 0) {
return -1;
}

MethodDescriptorDerived_Type.tp_base = &MethodDescriptorBase_Type;
if (PyType_Ready(&MethodDescriptorDerived_Type) < 0) {
return -1;
}
if (PyModule_AddType(m, &MethodDescriptorDerived_Type) < 0) {
return -1;
}

MethodDescriptorNopGet_Type.tp_base = &MethodDescriptorBase_Type;
if (PyType_Ready(&MethodDescriptorNopGet_Type) < 0) {
return -1;
}
if (PyModule_AddType(m, &MethodDescriptorNopGet_Type) < 0) {
return -1;
}

MethodDescriptor2_Type.tp_base = &MethodDescriptorBase_Type;
if (PyType_Ready(&MethodDescriptor2_Type) < 0) {
return -1;
}
if (PyModule_AddType(m, &MethodDescriptor2_Type) < 0) {
return -1;
}

return 0;
}
3 changes: 3 additions & 0 deletions Modules/_testcapi/testcapimodule_parts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "Python.h"

PyAPI_FUNC(int) _PyTestCapi_Init_Vectorcall(PyObject *module);
Loading

0 comments on commit 9020fe3

Please sign in to comment.