Skip to content

Commit 9020fe3

Browse files
committed
pythongh-93649: Split vectorcall testing from _testcapimodule.c
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.
1 parent fd76eb5 commit 9020fe3

8 files changed

+293
-254
lines changed

Modules/Setup.stdlib.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
169169
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
170170
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
171-
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c
171+
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/test_vectorcall.c
172172

173173
# Some testing modules MUST be built as shared libraries.
174174
*shared*

Modules/_testcapi/README.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Tests in this directory are compiled into the _testcapi extension.
2+
The main file for the extension is Modules/_testcapimodule.c, which
3+
calls `_PyTestCapi_Init_*` from these functions.

Modules/_testcapi/test_vectorcall.c

+270
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#include "testcapimodule_parts.h"
2+
#include <stddef.h> // offsetof
3+
4+
5+
/* Test PEP 590 - Vectorcall */
6+
7+
static int
8+
fastcall_args(PyObject *args, PyObject ***stack, Py_ssize_t *nargs)
9+
{
10+
if (args == Py_None) {
11+
*stack = NULL;
12+
*nargs = 0;
13+
}
14+
else if (PyTuple_Check(args)) {
15+
*stack = ((PyTupleObject *)args)->ob_item;
16+
*nargs = PyTuple_GET_SIZE(args);
17+
}
18+
else {
19+
PyErr_SetString(PyExc_TypeError, "args must be None or a tuple");
20+
return -1;
21+
}
22+
return 0;
23+
}
24+
25+
26+
static PyObject *
27+
test_pyobject_fastcall(PyObject *self, PyObject *args)
28+
{
29+
PyObject *func, *func_args;
30+
PyObject **stack;
31+
Py_ssize_t nargs;
32+
33+
if (!PyArg_ParseTuple(args, "OO", &func, &func_args)) {
34+
return NULL;
35+
}
36+
37+
if (fastcall_args(func_args, &stack, &nargs) < 0) {
38+
return NULL;
39+
}
40+
return _PyObject_FastCall(func, stack, nargs);
41+
}
42+
43+
static PyObject *
44+
test_pyobject_fastcalldict(PyObject *self, PyObject *args)
45+
{
46+
PyObject *func, *func_args, *kwargs;
47+
PyObject **stack;
48+
Py_ssize_t nargs;
49+
50+
if (!PyArg_ParseTuple(args, "OOO", &func, &func_args, &kwargs)) {
51+
return NULL;
52+
}
53+
54+
if (fastcall_args(func_args, &stack, &nargs) < 0) {
55+
return NULL;
56+
}
57+
58+
if (kwargs == Py_None) {
59+
kwargs = NULL;
60+
}
61+
else if (!PyDict_Check(kwargs)) {
62+
PyErr_SetString(PyExc_TypeError, "kwnames must be None or a dict");
63+
return NULL;
64+
}
65+
66+
return PyObject_VectorcallDict(func, stack, nargs, kwargs);
67+
}
68+
69+
static PyObject *
70+
test_pyobject_vectorcall(PyObject *self, PyObject *args)
71+
{
72+
PyObject *func, *func_args, *kwnames = NULL;
73+
PyObject **stack;
74+
Py_ssize_t nargs, nkw;
75+
76+
if (!PyArg_ParseTuple(args, "OOO", &func, &func_args, &kwnames)) {
77+
return NULL;
78+
}
79+
80+
if (fastcall_args(func_args, &stack, &nargs) < 0) {
81+
return NULL;
82+
}
83+
84+
if (kwnames == Py_None) {
85+
kwnames = NULL;
86+
}
87+
else if (PyTuple_Check(kwnames)) {
88+
nkw = PyTuple_GET_SIZE(kwnames);
89+
if (nargs < nkw) {
90+
PyErr_SetString(PyExc_ValueError, "kwnames longer than args");
91+
return NULL;
92+
}
93+
nargs -= nkw;
94+
}
95+
else {
96+
PyErr_SetString(PyExc_TypeError, "kwnames must be None or a tuple");
97+
return NULL;
98+
}
99+
return PyObject_Vectorcall(func, stack, nargs, kwnames);
100+
}
101+
102+
static PyObject *
103+
test_pyvectorcall_call(PyObject *self, PyObject *args)
104+
{
105+
PyObject *func;
106+
PyObject *argstuple;
107+
PyObject *kwargs = NULL;
108+
109+
if (!PyArg_ParseTuple(args, "OO|O", &func, &argstuple, &kwargs)) {
110+
return NULL;
111+
}
112+
113+
if (!PyTuple_Check(argstuple)) {
114+
PyErr_SetString(PyExc_TypeError, "args must be a tuple");
115+
return NULL;
116+
}
117+
if (kwargs != NULL && !PyDict_Check(kwargs)) {
118+
PyErr_SetString(PyExc_TypeError, "kwargs must be a dict");
119+
return NULL;
120+
}
121+
122+
return PyVectorcall_Call(func, argstuple, kwargs);
123+
}
124+
125+
static PyMethodDef TestMethods[] = {
126+
{"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS},
127+
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
128+
{"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS},
129+
{"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS},
130+
{NULL},
131+
};
132+
133+
134+
typedef struct {
135+
PyObject_HEAD
136+
vectorcallfunc vectorcall;
137+
} MethodDescriptorObject;
138+
139+
static PyObject *
140+
MethodDescriptor_vectorcall(PyObject *callable, PyObject *const *args,
141+
size_t nargsf, PyObject *kwnames)
142+
{
143+
/* True if using the vectorcall function in MethodDescriptorObject
144+
* but False for MethodDescriptor2Object */
145+
MethodDescriptorObject *md = (MethodDescriptorObject *)callable;
146+
return PyBool_FromLong(md->vectorcall != NULL);
147+
}
148+
149+
static PyObject *
150+
MethodDescriptor_new(PyTypeObject* type, PyObject* args, PyObject *kw)
151+
{
152+
MethodDescriptorObject *op = (MethodDescriptorObject *)type->tp_alloc(type, 0);
153+
op->vectorcall = MethodDescriptor_vectorcall;
154+
return (PyObject *)op;
155+
}
156+
157+
static PyObject *
158+
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
159+
{
160+
if (obj == Py_None || obj == NULL) {
161+
Py_INCREF(func);
162+
return func;
163+
}
164+
return PyMethod_New(func, obj);
165+
}
166+
167+
static PyObject *
168+
nop_descr_get(PyObject *func, PyObject *obj, PyObject *type)
169+
{
170+
Py_INCREF(func);
171+
return func;
172+
}
173+
174+
static PyObject *
175+
call_return_args(PyObject *self, PyObject *args, PyObject *kwargs)
176+
{
177+
Py_INCREF(args);
178+
return args;
179+
}
180+
181+
static PyTypeObject MethodDescriptorBase_Type = {
182+
PyVarObject_HEAD_INIT(NULL, 0)
183+
"MethodDescriptorBase",
184+
sizeof(MethodDescriptorObject),
185+
.tp_new = MethodDescriptor_new,
186+
.tp_call = PyVectorcall_Call,
187+
.tp_vectorcall_offset = offsetof(MethodDescriptorObject, vectorcall),
188+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
189+
Py_TPFLAGS_METHOD_DESCRIPTOR | Py_TPFLAGS_HAVE_VECTORCALL,
190+
.tp_descr_get = func_descr_get,
191+
};
192+
193+
static PyTypeObject MethodDescriptorDerived_Type = {
194+
PyVarObject_HEAD_INIT(NULL, 0)
195+
"MethodDescriptorDerived",
196+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
197+
};
198+
199+
static PyTypeObject MethodDescriptorNopGet_Type = {
200+
PyVarObject_HEAD_INIT(NULL, 0)
201+
"MethodDescriptorNopGet",
202+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
203+
.tp_call = call_return_args,
204+
.tp_descr_get = nop_descr_get,
205+
};
206+
207+
typedef struct {
208+
MethodDescriptorObject base;
209+
vectorcallfunc vectorcall;
210+
} MethodDescriptor2Object;
211+
212+
static PyObject *
213+
MethodDescriptor2_new(PyTypeObject* type, PyObject* args, PyObject *kw)
214+
{
215+
MethodDescriptor2Object *op = PyObject_New(MethodDescriptor2Object, type);
216+
op->base.vectorcall = NULL;
217+
op->vectorcall = MethodDescriptor_vectorcall;
218+
return (PyObject *)op;
219+
}
220+
221+
static PyTypeObject MethodDescriptor2_Type = {
222+
PyVarObject_HEAD_INIT(NULL, 0)
223+
"MethodDescriptor2",
224+
sizeof(MethodDescriptor2Object),
225+
.tp_new = MethodDescriptor2_new,
226+
.tp_call = PyVectorcall_Call,
227+
.tp_vectorcall_offset = offsetof(MethodDescriptor2Object, vectorcall),
228+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_VECTORCALL,
229+
};
230+
231+
232+
int
233+
_PyTestCapi_Init_Vectorcall(PyObject *m) {
234+
if (PyModule_AddFunctions(m, TestMethods) < 0) {
235+
return -1;
236+
}
237+
238+
if (PyType_Ready(&MethodDescriptorBase_Type) < 0) {
239+
return -1;
240+
}
241+
if (PyModule_AddType(m, &MethodDescriptorBase_Type) < 0) {
242+
return -1;
243+
}
244+
245+
MethodDescriptorDerived_Type.tp_base = &MethodDescriptorBase_Type;
246+
if (PyType_Ready(&MethodDescriptorDerived_Type) < 0) {
247+
return -1;
248+
}
249+
if (PyModule_AddType(m, &MethodDescriptorDerived_Type) < 0) {
250+
return -1;
251+
}
252+
253+
MethodDescriptorNopGet_Type.tp_base = &MethodDescriptorBase_Type;
254+
if (PyType_Ready(&MethodDescriptorNopGet_Type) < 0) {
255+
return -1;
256+
}
257+
if (PyModule_AddType(m, &MethodDescriptorNopGet_Type) < 0) {
258+
return -1;
259+
}
260+
261+
MethodDescriptor2_Type.tp_base = &MethodDescriptorBase_Type;
262+
if (PyType_Ready(&MethodDescriptor2_Type) < 0) {
263+
return -1;
264+
}
265+
if (PyModule_AddType(m, &MethodDescriptor2_Type) < 0) {
266+
return -1;
267+
}
268+
269+
return 0;
270+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "Python.h"
2+
3+
PyAPI_FUNC(int) _PyTestCapi_Init_Vectorcall(PyObject *module);

0 commit comments

Comments
 (0)