From 2f3922a6c664af8adb8fc298f1c925f2b75eb386 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Aug 2024 18:24:52 +0200 Subject: [PATCH] gh-111495: Add tests on PyTuple C APIs * Add file Modules/_testlimitedcapi/tuple.c. * Add file Lib/test/test_capi/test_tuple.py. --- Lib/test/test_capi/test_tuple.py | 111 ++++++++++++++++++++ Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/tuple.c | 81 +++++++++++++- Modules/_testlimitedcapi.c | 3 + Modules/_testlimitedcapi/parts.h | 1 + Modules/_testlimitedcapi/tuple.c | 128 +++++++++++++++++++++++ PCbuild/_testlimitedcapi.vcxproj | 1 + PCbuild/_testlimitedcapi.vcxproj.filters | 1 + 8 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 Lib/test/test_capi/test_tuple.py create mode 100644 Modules/_testlimitedcapi/tuple.c diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py new file mode 100644 index 00000000000000..f6de43acf9e81d --- /dev/null +++ b/Lib/test/test_capi/test_tuple.py @@ -0,0 +1,111 @@ +import unittest +from test.support import import_helper +_testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + + +NULL = None +PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN +PY_SSIZE_T_MAX = _testcapi.PY_SSIZE_T_MAX + +class TupleSubclass(tuple): + pass + + +class CAPITest(unittest.TestCase): + def test_check(self): + # Test PyTuple_Check() + check = _testlimitedcapi.tuple_check + self.assertTrue(check((1, 2))) + self.assertTrue(check(())) + self.assertTrue(check(TupleSubclass([1, 2]))) + self.assertFalse(check({1: 2})) + self.assertFalse(check([1, 2])) + self.assertFalse(check(42)) + self.assertFalse(check(object())) + # CRASHES check(NULL) + + def test_tuple_check_exact(self): + # Test PyTuple_CheckExact() + check = _testlimitedcapi.tuple_check_exact + self.assertTrue(check((1,))) + self.assertTrue(check(())) + self.assertFalse(check(TupleSubclass([1]))) + self.assertFalse(check({1: 2})) + self.assertFalse(check([1, 2])) + self.assertFalse(check(42)) + self.assertFalse(check(object())) + # CRASHES check(NULL) + + def test_tuple_size(self): + # Test PyTuple_Size() + size = _testlimitedcapi.tuple_size + self.assertEqual(size((1, 2, 3)), 3) + self.assertEqual(size(TupleSubclass((1, 2))), 2) + self.assertRaises(SystemError, size, []) + self.assertRaises(SystemError, size, 23) + self.assertRaises(SystemError, size, object()) + # CRASHES size(NULL) + + def test_tuple_get_size(self): + # Test PyTuple_GET_SIZE() + size = _testcapi.tuple_get_size + self.assertEqual(size((1, 2, 3)), 3) + self.assertEqual(size(TupleSubclass((1, 2))), 2) + # CRASHES size(object()) + # CRASHES size(23) + # CRASHES size([]) + # CRASHES size(NULL) + + def test_tuple_getitem(self): + # Test PyTuple_GetItem() + getitem = _testlimitedcapi.tuple_getitem + tpl = (1, 2, 3) + self.assertEqual(getitem(tpl, 0), 1) + self.assertEqual(getitem(tpl, 2), 3) + + self.assertRaises(IndexError, getitem, tpl, 3) + self.assertRaises(IndexError, getitem, tpl, -1) + self.assertRaises(IndexError, getitem, tpl, PY_SSIZE_T_MIN) + self.assertRaises(IndexError, getitem, tpl, PY_SSIZE_T_MAX) + + self.assertRaises(SystemError, getitem, 42, 1) + self.assertRaises(SystemError, getitem, [1, 2, 3], 1) + self.assertRaises(SystemError, getitem, {1: 2}, 1) + # CRASHES getitem(NULL, 1) + + def test_tuple_get_item(self): + # Test PyTuple_GET_ITEM() + get_item = _testcapi.tuple_get_item + tpl = (1, 2, (1, 2, 3)) + self.assertEqual(get_item(tpl, 0), 1) + self.assertEqual(get_item(tpl, 2), (1, 2, 3)) + # CRASHES for out of index: get_item(tpl, 3) + # CRASHES for get_item(tpl, PY_SSIZE_T_MIN) + # CRASHES for get_item(tpl, PY_SSIZE_T_MAX) + # CRASHES get_item(21, 2) + # CRASHES get_item(NULL, 1) + + def test_tuple_getslice(self): + # Test PyTuple_GetSlice() + getslice = _testlimitedcapi.tuple_getslice + tpl = (1, 2, 3) + + # empty + self.assertEqual(getslice(tpl, PY_SSIZE_T_MIN, 0), ()) + self.assertEqual(getslice(tpl, -1, 0), ()) + self.assertEqual(getslice(tpl, 3, PY_SSIZE_T_MAX), ()) + + # slice + self.assertEqual(getslice(tpl, 1, 3), (2, 3)) + + # whole + self.assertEqual(getslice(tpl, 0, len(tpl)), tpl) + self.assertEqual(getslice(tpl, 0, 100), tpl) + self.assertEqual(getslice(tpl, -100, 100), tpl) + + self.assertRaises(SystemError, getslice, [1, 2, 3], 0, 0) + self.assertRaises(SystemError, getslice, 'abc', 0, 0) + self.assertRaises(SystemError, getslice, 42, 0, 0) + + # CRASHES getslice(NULL, 0, 0) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 9da4e785804886..8fe9d7326f2dc3 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -163,7 +163,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 95dde8c0edadbe..848ec52c2b15c7 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -2,16 +2,87 @@ #include "util.h" +static PyObject * +tuple_get_size(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + RETURN_SIZE(PyTuple_GET_SIZE(obj)); +} + + +static PyObject * +tuple_get_item(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return Py_XNewRef(PyTuple_GET_ITEM(obj, i)); +} + + +static PyObject * +test_tuple_set_item(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyTuple_New() and PyTuple_SET_ITEM() + PyObject *tuple = PyTuple_New(2); + if (tuple == NULL) { + return NULL; + } + assert(PyTuple_CheckExact(tuple)); + + PyObject *zero = Py_GetConstantBorrowed(Py_CONSTANT_ZERO); + PyObject *one = Py_GetConstantBorrowed(Py_CONSTANT_ONE); + + PyTuple_SET_ITEM(tuple, 0, zero); + PyTuple_SET_ITEM(tuple, 1, one); + + assert(PyTuple_Size(tuple) == 2); + assert(PyTuple_GetItem(tuple, 0) == zero); + assert(PyTuple_GetItem(tuple, 1) == one); + Py_DECREF(tuple); + + Py_RETURN_NONE; +} + +static PyObject * +test_tuple_resize(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test _PyTuple_Resize() + PyObject *zero = Py_GetConstantBorrowed(Py_CONSTANT_ZERO); + PyObject *one = Py_GetConstantBorrowed(Py_CONSTANT_ONE); + PyObject *tuple = PyTuple_Pack(2, zero, one); + if (tuple == NULL) { + return NULL; + } + + if (_PyTuple_Resize(&tuple, 1) < 0) { + Py_XDECREF(tuple); + return NULL; + } + + assert(PyTuple_CheckExact(tuple)); + assert(PyTuple_Size(tuple) == 1); + assert(PyTuple_GetItem(tuple, 0) == zero); + Py_DECREF(tuple); + + Py_RETURN_NONE; +} + + static PyMethodDef test_methods[] = { + {"tuple_get_size", tuple_get_size, METH_O}, + {"tuple_get_item", tuple_get_item, METH_VARARGS}, + {"test_tuple_set_item", test_tuple_set_item, METH_NOARGS}, + {"test_tuple_set_item", test_tuple_set_item, METH_NOARGS}, + {"test_tuple_resize", test_tuple_resize, METH_NOARGS}, {NULL}, }; int _PyTestCapi_Init_Tuple(PyObject *m) { - if (PyModule_AddFunctions(m, test_methods) < 0){ - return -1; - } - - return 0; + return PyModule_AddFunctions(m, test_methods); } diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index 2f1a25ae4519b3..e74cbfe19871bf 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -71,6 +71,9 @@ PyInit__testlimitedcapi(void) if (_PyTestLimitedCAPI_Init_Sys(mod) < 0) { return NULL; } + if (_PyTestLimitedCAPI_Init_Tuple(mod) < 0) { + return NULL; + } if (_PyTestLimitedCAPI_Init_Unicode(mod) < 0) { return NULL; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index c5758605fb71fa..12b890853803f4 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -36,6 +36,7 @@ int _PyTestLimitedCAPI_Init_Long(PyObject *module); int _PyTestLimitedCAPI_Init_PyOS(PyObject *module); int _PyTestLimitedCAPI_Init_Set(PyObject *module); int _PyTestLimitedCAPI_Init_Sys(PyObject *module); +int _PyTestLimitedCAPI_Init_Tuple(PyObject *module); int _PyTestLimitedCAPI_Init_Unicode(PyObject *module); int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module); diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c new file mode 100644 index 00000000000000..4f09b6b17e3bf0 --- /dev/null +++ b/Modules/_testlimitedcapi/tuple.c @@ -0,0 +1,128 @@ +// Need limited C API version 3.13 for Py_GetConstantBorrowed() +#include "pyconfig.h" // Py_GIL_DISABLED +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +# define Py_LIMITED_API 0x030d0000 +#endif + +#include "parts.h" +#include "util.h" + + +static PyObject * +tuple_check(PyObject* Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyTuple_Check(obj)); +} + + +static PyObject * +tuple_check_exact(PyObject* Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyTuple_CheckExact(obj)); +} + + +static PyObject * +tuple_size(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + RETURN_SIZE(PyTuple_Size(obj)); +} + + +static PyObject * +tuple_getitem(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return Py_XNewRef(PyTuple_GetItem(obj, i)); +} + + +static PyObject * +tuple_getslice(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t ilow, ihigh; + if (!PyArg_ParseTuple(args, "Onn", &obj, &ilow, &ihigh)) { + return NULL; + } + NULLABLE(obj); + return PyTuple_GetSlice(obj, ilow, ihigh); +} + + +static PyObject * +test_tuple_new(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyTuple_New() and PyTuple_SetItem() + PyObject *tuple = PyTuple_New(2); + if (tuple == NULL) { + return NULL; + } + assert(PyTuple_CheckExact(tuple)); + + PyObject *zero = Py_GetConstantBorrowed(Py_CONSTANT_ZERO); + PyObject *one = Py_GetConstantBorrowed(Py_CONSTANT_ONE); + + if (PyTuple_SetItem(tuple, 0, zero) < 0) { + Py_DECREF(tuple); + return NULL; + } + if (PyTuple_SetItem(tuple, 1, one) < 0) { + Py_DECREF(tuple); + return NULL; + } + + assert(PyTuple_Size(tuple) == 2); + assert(PyTuple_GetItem(tuple, 0) == zero); + assert(PyTuple_GetItem(tuple, 1) == one); + Py_DECREF(tuple); + + Py_RETURN_NONE; +} + + +static PyObject * +test_tuple_pack(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyTuple_Pack() + PyObject *zero = Py_GetConstantBorrowed(Py_CONSTANT_ZERO); + PyObject *one = Py_GetConstantBorrowed(Py_CONSTANT_ONE); + + PyObject *tuple = PyTuple_Pack(2, zero, one); + if (tuple == NULL) { + return NULL; + } + assert(PyTuple_CheckExact(tuple)); + assert(PyTuple_Size(tuple) == 2); + assert(PyTuple_GetItem(tuple, 0) == zero); + assert(PyTuple_GetItem(tuple, 1) == one); + Py_DECREF(tuple); + + Py_RETURN_NONE; +} + + +static PyMethodDef test_methods[] = { + {"tuple_check", tuple_check, METH_O}, + {"tuple_check_exact", tuple_check_exact, METH_O}, + {"tuple_size", tuple_size, METH_O}, + {"tuple_getitem", tuple_getitem, METH_VARARGS}, + {"tuple_getslice", tuple_getslice, METH_VARARGS}, + {"test_tuple_new", test_tuple_new, METH_NOARGS}, + {"test_tuple_pack", test_tuple_pack, METH_NOARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Tuple(PyObject *m) +{ + return PyModule_AddFunctions(m, test_methods); +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index bcb2ce24fcb2bf..a1409ecf043d2d 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -108,6 +108,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index df3324b71b2f60..c0ecd6dc4446df 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -23,6 +23,7 @@ +