summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2017-06-09 14:48:45 (GMT)
committerGitHub <noreply@github.com>2017-06-09 14:48:45 (GMT)
commit3b5cf85edc188345668f987c824a2acb338a7816 (patch)
tree5a1dc54996378334a88139539736f3e6a9c06730
parent5eb7075915f0509c5027376bda0e6d9c1e505a2c (diff)
downloadcpython-3b5cf85edc188345668f987c824a2acb338a7816.zip
cpython-3b5cf85edc188345668f987c824a2acb338a7816.tar.gz
cpython-3b5cf85edc188345668f987c824a2acb338a7816.tar.bz2
bpo-30524: Write unit tests for FASTCALL (#2022)
Test C functions: * _PyObject_FastCall() * _PyObject_FastCallDict() * _PyObject_FastCallKeywords()
-rw-r--r--Lib/test/test_call.py175
-rw-r--r--Modules/_testcapimodule.c101
2 files changed, 276 insertions, 0 deletions
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py
index 0992a0e..d929cfc 100644
--- a/Lib/test/test_call.py
+++ b/Lib/test/test_call.py
@@ -1,5 +1,10 @@
+import datetime
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
# code. They depend on the METH_XXX flag that is used to define a C
@@ -176,5 +181,175 @@ class CFunctionCallsErrorMessages(unittest.TestCase):
self.assertRaisesRegex(TypeError, msg, [].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__":
unittest.main()
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 7f7a13e..8c44ad2 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4051,6 +4051,104 @@ raise_SIGINT_then_send_None(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[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@@ -4256,6 +4354,9 @@ static PyMethodDef TestMethods[] = {
{"tracemalloc_get_traceback", tracemalloc_get_traceback, METH_VARARGS},
{"dict_get_version", dict_get_version, METH_VARARGS},
{"raise_SIGINT_then_send_None", raise_SIGINT_then_send_None, 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 */
};