]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-111495: Add tests for PyTuple C API (GH-118757) (#123379)
authorSergey B Kirpichev <skirpichev@gmail.com>
Thu, 5 Sep 2024 22:51:02 +0000 (01:51 +0300)
committerGitHub <noreply@github.com>
Thu, 5 Sep 2024 22:51:02 +0000 (00:51 +0200)
(cherry picked from commit dbc1752d4107532d312c78263212e807a3674eb1)

Co-authored-by: kalyanr <kalyan.ben10@live.com>
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
Lib/test/test_capi/test_list.py
Lib/test/test_capi/test_set.py
Lib/test/test_capi/test_tuple.py [new file with mode: 0644]
Modules/_testcapi/tuple.c

index 197da03e07fa27427694993839623f8fa0f785da..7dc4d3b284b0c6c3d0545d8d3e2863e21176cb2f 100644 (file)
@@ -275,3 +275,7 @@ class CAPITest(unittest.TestCase):
         self.assertRaises(SystemError, astuple, ())
         self.assertRaises(SystemError, astuple, object())
         self.assertRaises(SystemError, astuple, NULL)
+
+
+if __name__ == "__main__":
+    unittest.main()
index e9165e7e6806ddd7ae3b6c16c779b4658d443982..5131e67431b1b7d5e912f0a4f3a598144c055b70 100644 (file)
@@ -213,3 +213,7 @@ class TestSetCAPI(unittest.TestCase):
             clear(object())
         self.assertImmutable(clear)
         # CRASHES: clear(NULL)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py
new file mode 100644 (file)
index 0000000..baf0d17
--- /dev/null
@@ -0,0 +1,261 @@
+import unittest
+import sys
+from collections import namedtuple
+from test.support import import_helper
+
+_testcapi = import_helper.import_module('_testcapi')
+_testlimitedcapi = _testcapi
+
+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_checkexact(self):
+        # Test PyTuple_CheckExact()
+        check = _testlimitedcapi.tuple_checkexact
+
+        self.assertTrue(check((1, 2)))
+        self.assertTrue(check(()))
+        self.assertFalse(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_new(self):
+        # Test PyTuple_New()
+        tuple_new = _testlimitedcapi.tuple_new
+        size = _testlimitedcapi.tuple_size
+        checknull = _testcapi._check_tuple_item_is_NULL
+
+        tup1 = tuple_new(0)
+        self.assertEqual(tup1, ())
+        self.assertEqual(size(tup1), 0)
+        self.assertIs(type(tup1), tuple)
+        tup2 = tuple_new(1)
+        self.assertIs(type(tup2), tuple)
+        self.assertEqual(size(tup2), 1)
+        self.assertIsNot(tup2, tup1)
+        self.assertTrue(checknull(tup2, 0))
+
+        self.assertRaises(SystemError, tuple_new, -1)
+        self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN)
+        self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX)
+
+    def test_tuple_pack(self):
+        # Test PyTuple_Pack()
+        pack = _testlimitedcapi.tuple_pack
+
+        self.assertEqual(pack(0), ())
+        self.assertEqual(pack(1, [1]), ([1],))
+        self.assertEqual(pack(2, [1], [2]), ([1], [2]))
+
+        self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN)
+        self.assertRaises(SystemError, pack, -1)
+        self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX)
+
+        # CRASHES pack(1, NULL)
+        # CRASHES pack(2, [1])
+
+    def test_tuple_size(self):
+        # Test PyTuple_Size()
+        size = _testlimitedcapi.tuple_size
+
+        self.assertEqual(size(()), 0)
+        self.assertEqual(size((1, 2)), 2)
+        self.assertEqual(size(TupleSubclass((1, 2))), 2)
+
+        self.assertRaises(SystemError, size, [])
+        self.assertRaises(SystemError, size, 42)
+        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(()), 0)
+        self.assertEqual(size((1, 2)), 2)
+        self.assertEqual(size(TupleSubclass((1, 2))), 2)
+
+    def test_tuple_getitem(self):
+        # Test PyTuple_GetItem()
+        getitem = _testlimitedcapi.tuple_getitem
+
+        tup = ([1], [2], [3])
+        self.assertEqual(getitem(tup, 0), [1])
+        self.assertEqual(getitem(tup, 2), [3])
+
+        tup2 = TupleSubclass(([1], [2], [3]))
+        self.assertEqual(getitem(tup2, 0), [1])
+        self.assertEqual(getitem(tup2, 2), [3])
+
+        self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN)
+        self.assertRaises(IndexError, getitem, tup, -1)
+        self.assertRaises(IndexError, getitem, tup, len(tup))
+        self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MAX)
+        self.assertRaises(SystemError, getitem, [1, 2, 3], 1)
+        self.assertRaises(SystemError, getitem, 42, 1)
+
+        # CRASHES getitem(NULL, 0)
+
+    def test_tuple_get_item(self):
+        # Test PyTuple_GET_ITEM()
+        get_item = _testcapi.tuple_get_item
+
+        tup = ([1], [2], [3])
+        self.assertEqual(get_item(tup, 0), [1])
+        self.assertEqual(get_item(tup, 2), [3])
+
+        tup2 = TupleSubclass(([1], [2], [3]))
+        self.assertEqual(get_item(tup2, 0), [1])
+        self.assertEqual(get_item(tup2, 2), [3])
+
+        # CRASHES get_item(NULL, 0)
+
+    def test_tuple_getslice(self):
+        # Test PyTuple_GetSlice()
+        getslice = _testlimitedcapi.tuple_getslice
+
+        # empty
+        tup = ([1], [2], [3])
+        self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ())
+        self.assertEqual(getslice(tup, -1, 0), ())
+        self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ())
+        self.assertEqual(getslice(tup, 1, 1), ())
+        self.assertEqual(getslice(tup, 2, 1), ())
+        tup = TupleSubclass(([1], [2], [3]))
+        self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ())
+        self.assertEqual(getslice(tup, -1, 0), ())
+        self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ())
+        self.assertEqual(getslice(tup, 1, 1), ())
+        self.assertEqual(getslice(tup, 2, 1), ())
+
+        # slice
+        tup = ([1], [2], [3], [4])
+        self.assertEqual(getslice(tup, 1, 3), ([2], [3]))
+        tup = TupleSubclass(([1], [2], [3], [4]))
+        self.assertEqual(getslice(tup, 1, 3), ([2], [3]))
+
+        # whole
+        tup = ([1], [2], [3])
+        self.assertEqual(getslice(tup, 0, 3), tup)
+        self.assertEqual(getslice(tup, 0, 100), tup)
+        self.assertEqual(getslice(tup, -100, 100), tup)
+        tup = TupleSubclass(([1], [2], [3]))
+        self.assertEqual(getslice(tup, 0, 3), tup)
+        self.assertEqual(getslice(tup, 0, 100), tup)
+        self.assertEqual(getslice(tup, -100, 100), tup)
+
+        self.assertRaises(SystemError, getslice, [[1], [2], [3]], 0, 0)
+        self.assertRaises(SystemError, getslice, 42, 0, 0)
+
+        # CRASHES getslice(NULL, 0, 0)
+
+    def test_tuple_setitem(self):
+        # Test PyTuple_SetItem()
+        setitem = _testlimitedcapi.tuple_setitem
+        checknull = _testcapi._check_tuple_item_is_NULL
+
+        tup = ([1], [2])
+        self.assertEqual(setitem(tup, 0, []), ([], [2]))
+        self.assertEqual(setitem(tup, 1, []), ([1], []))
+
+        tup2 = setitem(tup, 1, NULL)
+        self.assertTrue(checknull(tup2, 1))
+
+        tup2 = TupleSubclass(([1], [2]))
+        self.assertRaises(SystemError, setitem, tup2, 0, [])
+
+        self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, [])
+        self.assertRaises(IndexError, setitem, tup, -1, [])
+        self.assertRaises(IndexError, setitem, tup, len(tup), [])
+        self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, [])
+        self.assertRaises(SystemError, setitem, [1], 0, [])
+        self.assertRaises(SystemError, setitem, 42, 0, [])
+
+        # CRASHES setitem(NULL, 0, [])
+
+    def test_tuple_set_item(self):
+        # Test PyTuple_SET_ITEM()
+        set_item = _testcapi.tuple_set_item
+        checknull = _testcapi._check_tuple_item_is_NULL
+
+        tup = ([1], [2])
+        self.assertEqual(set_item(tup, 0, []), ([], [2]))
+        self.assertEqual(set_item(tup, 1, []), ([1], []))
+
+        tup2 = set_item(tup, 1, NULL)
+        self.assertTrue(checknull(tup2, 1))
+
+        tup2 = TupleSubclass(([1], [2]))
+        self.assertIs(set_item(tup2, 0, []), tup2)
+        self.assertEqual(tup2, ([], [2]))
+
+        # CRASHES set_item(tup, -1, [])
+        # CRASHES set_item(tup, len(tup), [])
+        # CRASHES set_item([1], 0, [])
+        # CRASHES set_item(NULL, 0, [])
+
+    def test__tuple_resize(self):
+        # Test _PyTuple_Resize()
+        resize = _testcapi._tuple_resize
+        checknull = _testcapi._check_tuple_item_is_NULL
+
+        a = ()
+        b = resize(a, 0, False)
+        self.assertEqual(len(a), 0)
+        self.assertEqual(len(b), 0)
+        b = resize(a, 2, False)
+        self.assertEqual(len(a), 0)
+        self.assertEqual(len(b), 2)
+        self.assertTrue(checknull(b, 0))
+        self.assertTrue(checknull(b, 1))
+
+        a = ([1], [2], [3])
+        b = resize(a, 3)
+        self.assertEqual(b, a)
+        b = resize(a, 2)
+        self.assertEqual(b, a[:2])
+        b = resize(a, 5)
+        self.assertEqual(len(b), 5)
+        self.assertEqual(b[:3], a)
+        self.assertTrue(checknull(b, 3))
+        self.assertTrue(checknull(b, 4))
+
+        a = ()
+        self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX)
+        self.assertRaises(SystemError, resize, a, -1)
+        self.assertRaises(SystemError, resize, a, PY_SSIZE_T_MIN)
+        # refcount > 1
+        a = (1, 2, 3)
+        self.assertRaises(SystemError, resize, a, 3, False)
+        self.assertRaises(SystemError, resize, a, 0, False)
+        # non-tuple
+        self.assertRaises(SystemError, resize, [1, 2, 3], 0, False)
+        self.assertRaises(SystemError, resize, NULL, 0, False)
+
+if __name__ == "__main__":
+    unittest.main()
index 95dde8c0edadbe3e9c857d8828ac9efb909c0d25..23ea4e1dbceb84377564d983d1295645ee3bbb39 100644 (file)
 #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 *
+tuple_copy(PyObject *tuple)
+{
+    Py_ssize_t size = PyTuple_GET_SIZE(tuple);
+    PyObject *newtuple = PyTuple_New(size);
+    if (!newtuple) {
+        return NULL;
+    }
+    for (Py_ssize_t n = 0; n < size; n++) {
+        PyTuple_SET_ITEM(newtuple, n, Py_XNewRef(PyTuple_GET_ITEM(tuple, n)));
+    }
+    return newtuple;
+}
+
+static PyObject *
+tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *obj, *value, *newtuple;
+    Py_ssize_t i;
+    if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) {
+        return NULL;
+    }
+    NULLABLE(value);
+    if (PyTuple_CheckExact(obj)) {
+        newtuple = tuple_copy(obj);
+        if (!newtuple) {
+            return NULL;
+        }
+
+        PyObject *val = PyTuple_GET_ITEM(newtuple, i);
+        PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value));
+        Py_DECREF(val);
+        return newtuple;
+    }
+    else {
+        NULLABLE(obj);
+
+        PyObject *val = PyTuple_GET_ITEM(obj, i);
+        PyTuple_SET_ITEM(obj, i, Py_XNewRef(value));
+        Py_DECREF(val);
+        return Py_XNewRef(obj);
+    }
+}
+
+static PyObject *
+_tuple_resize(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *tup;
+    Py_ssize_t newsize;
+    int new = 1;
+    if (!PyArg_ParseTuple(args, "On|p", &tup, &newsize, &new)) {
+        return NULL;
+    }
+    if (new) {
+        tup = tuple_copy(tup);
+        if (!tup) {
+            return NULL;
+        }
+    }
+    else {
+        NULLABLE(tup);
+        Py_XINCREF(tup);
+    }
+    int r = _PyTuple_Resize(&tup, newsize);
+    if (r == -1) {
+        assert(tup == NULL);
+        return NULL;
+    }
+    return tup;
+}
+
+static PyObject *
+_check_tuple_item_is_NULL(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *obj;
+    Py_ssize_t i;
+    if (!PyArg_ParseTuple(args, "On", &obj, &i)) {
+        return NULL;
+    }
+    return PyLong_FromLong(PyTuple_GET_ITEM(obj, i) == NULL);
+}
+
+static PyObject *
+tuple_check(PyObject* Py_UNUSED(module), PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyTuple_Check(obj));
+}
+
+static PyObject *
+tuple_checkexact(PyObject* Py_UNUSED(module), PyObject *obj)
+{
+    NULLABLE(obj);
+    return PyLong_FromLong(PyTuple_CheckExact(obj));
+}
+
+static PyObject *
+tuple_new(PyObject* Py_UNUSED(module), PyObject *len)
+{
+    return PyTuple_New(PyLong_AsSsize_t(len));
+}
+
+static PyObject *
+tuple_pack(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *arg1 = NULL, *arg2 = NULL;
+    Py_ssize_t size;
+
+    if (!PyArg_ParseTuple(args, "n|OO", &size, &arg1, &arg2)) {
+        return NULL;
+    }
+    if (arg1) {
+        NULLABLE(arg1);
+        if (arg2) {
+            NULLABLE(arg2);
+            return PyTuple_Pack(size, arg1, arg2);
+        }
+        return PyTuple_Pack(size, arg1);
+    }
+    return PyTuple_Pack(size);
+}
+
+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 *
+tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *obj, *value, *newtuple = NULL;
+    Py_ssize_t i;
+    if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) {
+        return NULL;
+    }
+    NULLABLE(value);
+    if (PyTuple_CheckExact(obj)) {
+        Py_ssize_t size = PyTuple_Size(obj);
+        newtuple = PyTuple_New(size);
+        if (!newtuple) {
+            return NULL;
+        }
+        for (Py_ssize_t n = 0; n < size; n++) {
+            if (PyTuple_SetItem(newtuple, n,
+                                Py_XNewRef(PyTuple_GetItem(obj, n))) == -1) {
+                Py_DECREF(newtuple);
+                return NULL;
+            }
+        }
+
+        if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) {
+            Py_DECREF(newtuple);
+            return NULL;
+        }
+        return newtuple;
+    }
+    else {
+        NULLABLE(obj);
+
+        if (PyTuple_SetItem(obj, i, Py_XNewRef(value)) == -1) {
+            return NULL;
+        }
+        return Py_XNewRef(obj);
+    }
+}
+
+
 static PyMethodDef test_methods[] = {
+    {"tuple_get_size", tuple_get_size, METH_O},
+    {"tuple_get_item", tuple_get_item, METH_VARARGS},
+    {"tuple_set_item", tuple_set_item, METH_VARARGS},
+    {"_tuple_resize", _tuple_resize, METH_VARARGS},
+    {"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS},
+    /* Limited C API */
+    {"tuple_check", tuple_check, METH_O},
+    {"tuple_checkexact", tuple_checkexact, METH_O},
+    {"tuple_new", tuple_new, METH_O},
+    {"tuple_pack", tuple_pack, METH_VARARGS},
+    {"tuple_size", tuple_size, METH_O},
+    {"tuple_getitem", tuple_getitem, METH_VARARGS},
+    {"tuple_getslice", tuple_getslice, METH_VARARGS},
+    {"tuple_setitem", tuple_setitem, METH_VARARGS},
     {NULL},
 };
 
 int
 _PyTestCapi_Init_Tuple(PyObject *m)
 {
-    if (PyModule_AddFunctions(m, test_methods) < 0){
+    if (PyModule_AddFunctions(m, test_methods) < 0) {
         return -1;
     }