mirror of
https://github.com/python/cpython.git
synced 2025-09-30 20:31:52 +00:00
Test C functions:
* _PyObject_FastCall()
* _PyObject_FastCallDict()
* _PyObject_FastCallKeywords()
(cherry picked from commit 3b5cf85edc
)
This commit is contained in:
parent
bbeaccc76b
commit
b7577456c4
2 changed files with 277 additions and 0 deletions
|
@ -1,4 +1,10 @@
|
||||||
|
import datetime
|
||||||
import unittest
|
import unittest
|
||||||
|
from test.support import cpython_only
|
||||||
|
try:
|
||||||
|
import _testcapi
|
||||||
|
except ImportError:
|
||||||
|
_testcapi = None
|
||||||
|
|
||||||
# The test cases here cover several paths through the function calling
|
# The test cases here cover several paths through the function calling
|
||||||
# code. They depend on the METH_XXX flag that is used to define a C
|
# code. They depend on the METH_XXX flag that is used to define a C
|
||||||
|
@ -122,5 +128,175 @@ class CFunctionCalls(unittest.TestCase):
|
||||||
self.assertRaises(TypeError, [].count, x=2, y=2)
|
self.assertRaises(TypeError, [].count, x=2, y=2)
|
||||||
|
|
||||||
|
|
||||||
|
def pyfunc(arg1, arg2):
|
||||||
|
return [arg1, arg2]
|
||||||
|
|
||||||
|
|
||||||
|
def pyfunc_noarg():
|
||||||
|
return "noarg"
|
||||||
|
|
||||||
|
|
||||||
|
class PythonClass:
|
||||||
|
def method(self, arg1, arg2):
|
||||||
|
return [arg1, arg2]
|
||||||
|
|
||||||
|
def method_noarg(self):
|
||||||
|
return "noarg"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def class_method(cls):
|
||||||
|
return "classmethod"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def static_method():
|
||||||
|
return "staticmethod"
|
||||||
|
|
||||||
|
|
||||||
|
PYTHON_INSTANCE = PythonClass()
|
||||||
|
|
||||||
|
|
||||||
|
IGNORE_RESULT = object()
|
||||||
|
|
||||||
|
|
||||||
|
@cpython_only
|
||||||
|
class FastCallTests(unittest.TestCase):
|
||||||
|
# Test calls with positional arguments
|
||||||
|
CALLS_POSARGS = (
|
||||||
|
# (func, args: tuple, result)
|
||||||
|
|
||||||
|
# Python function with 2 arguments
|
||||||
|
(pyfunc, (1, 2), [1, 2]),
|
||||||
|
|
||||||
|
# Python function without argument
|
||||||
|
(pyfunc_noarg, (), "noarg"),
|
||||||
|
|
||||||
|
# Python class methods
|
||||||
|
(PythonClass.class_method, (), "classmethod"),
|
||||||
|
(PythonClass.static_method, (), "staticmethod"),
|
||||||
|
|
||||||
|
# Python instance methods
|
||||||
|
(PYTHON_INSTANCE.method, (1, 2), [1, 2]),
|
||||||
|
(PYTHON_INSTANCE.method_noarg, (), "noarg"),
|
||||||
|
(PYTHON_INSTANCE.class_method, (), "classmethod"),
|
||||||
|
(PYTHON_INSTANCE.static_method, (), "staticmethod"),
|
||||||
|
|
||||||
|
# C function: METH_NOARGS
|
||||||
|
(globals, (), IGNORE_RESULT),
|
||||||
|
|
||||||
|
# C function: METH_O
|
||||||
|
(id, ("hello",), IGNORE_RESULT),
|
||||||
|
|
||||||
|
# C function: METH_VARARGS
|
||||||
|
(dir, (1,), IGNORE_RESULT),
|
||||||
|
|
||||||
|
# C function: METH_VARARGS | METH_KEYWORDS
|
||||||
|
(min, (5, 9), 5),
|
||||||
|
|
||||||
|
# C function: METH_FASTCALL
|
||||||
|
(divmod, (1000, 33), (30, 10)),
|
||||||
|
|
||||||
|
# C type static method: METH_FASTCALL | METH_CLASS
|
||||||
|
(int.from_bytes, (b'\x01\x00', 'little'), 1),
|
||||||
|
|
||||||
|
# bpo-30524: Test that calling a C type static method with no argument
|
||||||
|
# doesn't crash (ignore the result): METH_FASTCALL | METH_CLASS
|
||||||
|
(datetime.datetime.now, (), IGNORE_RESULT),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test calls with positional and keyword arguments
|
||||||
|
CALLS_KWARGS = (
|
||||||
|
# (func, args: tuple, kwargs: dict, result)
|
||||||
|
|
||||||
|
# Python function with 2 arguments
|
||||||
|
(pyfunc, (1,), {'arg2': 2}, [1, 2]),
|
||||||
|
(pyfunc, (), {'arg1': 1, 'arg2': 2}, [1, 2]),
|
||||||
|
|
||||||
|
# Python instance methods
|
||||||
|
(PYTHON_INSTANCE.method, (1,), {'arg2': 2}, [1, 2]),
|
||||||
|
(PYTHON_INSTANCE.method, (), {'arg1': 1, 'arg2': 2}, [1, 2]),
|
||||||
|
|
||||||
|
# C function: METH_VARARGS | METH_KEYWORDS
|
||||||
|
(max, ([],), {'default': 9}, 9),
|
||||||
|
|
||||||
|
# C type static method: METH_FASTCALL | METH_CLASS
|
||||||
|
(int.from_bytes, (b'\x01\x00',), {'byteorder': 'little'}, 1),
|
||||||
|
(int.from_bytes, (), {'bytes': b'\x01\x00', 'byteorder': 'little'}, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_result(self, result, expected):
|
||||||
|
if expected is IGNORE_RESULT:
|
||||||
|
return
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_fastcall(self):
|
||||||
|
# Test _PyObject_FastCall()
|
||||||
|
|
||||||
|
for func, args, expected in self.CALLS_POSARGS:
|
||||||
|
with self.subTest(func=func, args=args):
|
||||||
|
result = _testcapi.pyobject_fastcall(func, args)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
# args=NULL, nargs=0
|
||||||
|
result = _testcapi.pyobject_fastcall(func, None)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
def test_fastcall_dict(self):
|
||||||
|
# Test _PyObject_FastCallDict()
|
||||||
|
|
||||||
|
for func, args, expected in self.CALLS_POSARGS:
|
||||||
|
with self.subTest(func=func, args=args):
|
||||||
|
# kwargs=NULL
|
||||||
|
result = _testcapi.pyobject_fastcalldict(func, args, None)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
# kwargs={}
|
||||||
|
result = _testcapi.pyobject_fastcalldict(func, args, {})
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
# args=NULL, nargs=0, kwargs=NULL
|
||||||
|
result = _testcapi.pyobject_fastcalldict(func, None, None)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
# args=NULL, nargs=0, kwargs={}
|
||||||
|
result = _testcapi.pyobject_fastcalldict(func, None, {})
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
for func, args, kwargs, expected in self.CALLS_KWARGS:
|
||||||
|
with self.subTest(func=func, args=args, kwargs=kwargs):
|
||||||
|
result = _testcapi.pyobject_fastcalldict(func, args, kwargs)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
def test_fastcall_keywords(self):
|
||||||
|
# Test _PyObject_FastCallKeywords()
|
||||||
|
|
||||||
|
for func, args, expected in self.CALLS_POSARGS:
|
||||||
|
with self.subTest(func=func, args=args):
|
||||||
|
# kwnames=NULL
|
||||||
|
result = _testcapi.pyobject_fastcallkeywords(func, args, None)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
# kwnames=()
|
||||||
|
result = _testcapi.pyobject_fastcallkeywords(func, args, ())
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
# kwnames=NULL
|
||||||
|
result = _testcapi.pyobject_fastcallkeywords(func, None, None)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
# kwnames=()
|
||||||
|
result = _testcapi.pyobject_fastcallkeywords(func, None, ())
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
for func, args, kwargs, expected in self.CALLS_KWARGS:
|
||||||
|
with self.subTest(func=func, args=args, kwargs=kwargs):
|
||||||
|
kwnames = tuple(kwargs.keys())
|
||||||
|
args = args + tuple(kwargs.values())
|
||||||
|
result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames)
|
||||||
|
self.check_result(result, expected)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -4027,6 +4027,104 @@ dict_get_version(PyObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 = &PyTuple_GET_ITEM(args, 0);
|
||||||
|
*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_FastCallDict(func, stack, nargs, kwargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
test_pyobject_fastcallkeywords(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_FastCallKeywords(func, stack, nargs, kwnames);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef TestMethods[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"raise_exception", raise_exception, METH_VARARGS},
|
{"raise_exception", raise_exception, METH_VARARGS},
|
||||||
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
|
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
|
||||||
|
@ -4230,6 +4328,9 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"tracemalloc_untrack", tracemalloc_untrack, METH_VARARGS},
|
{"tracemalloc_untrack", tracemalloc_untrack, METH_VARARGS},
|
||||||
{"tracemalloc_get_traceback", tracemalloc_get_traceback, METH_VARARGS},
|
{"tracemalloc_get_traceback", tracemalloc_get_traceback, METH_VARARGS},
|
||||||
{"dict_get_version", dict_get_version, METH_VARARGS},
|
{"dict_get_version", dict_get_version, METH_VARARGS},
|
||||||
|
{"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS},
|
||||||
|
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
|
||||||
|
{"pyobject_fastcallkeywords", test_pyobject_fastcallkeywords, METH_VARARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue