diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2017-06-09 14:48:45 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-09 14:48:45 (GMT) |
commit | 3b5cf85edc188345668f987c824a2acb338a7816 (patch) | |
tree | 5a1dc54996378334a88139539736f3e6a9c06730 /Lib | |
parent | 5eb7075915f0509c5027376bda0e6d9c1e505a2c (diff) | |
download | cpython-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()
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_call.py | 175 |
1 files changed, 175 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() |