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 @@
+