Skip to content

gh-111495: Add tests for PyTuple C API #111606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
160 changes: 160 additions & 0 deletions Lib/test/test_capi/test_tuple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import unittest
import sys
from collections import namedtuple
from test.support import import_helper
_testcapi = import_helper.import_module('_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 = _testcapi.tuple_check
self.assertTrue(check((1, 2)))
self.assertTrue(check(()))
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 = _testcapi.tuple_checkexact
self.assertTrue(check((1, 2)))
self.assertTrue(check(()))
self.assertFalse(check(TupleSubclass((1, 2))))
self.assertFalse(check({1: 2}))
self.assertFalse(check(object()))

# CRASHES check(NULL)

def test_tuple_new(self):
# Test PyTuple_New()
tuple_new = _testcapi.tuple_new
tup = tuple_new(0)
self.assertEqual(tup, ())
self.assertIs(type(tup), tuple)
tup2 = tuple_new(1)
self.assertIsNot(tup2, tup)
self.assertRaises(SystemError, tuple_new, NULL)
self.assertRaises(SystemError, tuple_new, -1)


def test_tuple_pack(self):
# Test PyTuple_Pack()
pass

def test_tuple_size(self):
# Test PyTuple_Size()
size = _testcapi.tuple_size
self.assertEqual(size((1, 2)), 2)
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(()), 0)
self.assertEqual(size((1, 2)), 2)
self.assertEqual(size(TupleSubclass((1, 2))), 2)
# CRASHES size(object())
# CRASHES size(23)
# CRASHES size({})
# CRASHES size(UserList())
# CRASHES size(NULL)


def test_tuple_getitem(self):
# Test PyTuple_GetItem()
getitem = _testcapi.tuple_getitem
tup = (1, 2, 3)
self.assertEqual(getitem(tup, 0), 1)
self.assertEqual(getitem(tup, len(tup)-1), 3)
self.assertRaises(IndexError, getitem, tup, -1)
self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN)
self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MAX)
self.assertRaises(IndexError, getitem, tup, len(tup))
self.assertRaises(SystemError, getitem, 42, 1)

# # CRASHES getitem(NULL, 1)

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)

# CRASHES for get_item(tup, -1)
# CRASHES for get_item(tup, PY_SSIZE_T_MIN)
# CRASHES for get_item(tup, PY_SSIZE_T_MAX)
# CRASHES for out of index: get_item(tup, 3)
# CRASHES get_item(21, 2)
# CRASHES get_item(Null,1)

def test_tuple_getslice(self):
# Test PyTuple_GetSlice()
getslice = _testcapi.tuple_getslice
tup = (1,2,3)

# empty
self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ())
self.assertEqual(getslice(tup, -1, 0), ())
self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ())

# slice
self.assertEqual(getslice(tup, 1, 3), (2, 3))

# whole
self.assertEqual(getslice(tup, 0, len(tup)), tup)
self.assertEqual(getslice(tup, 0, 100), tup)
self.assertEqual(getslice(tup, -100, 100), tup)

self.assertRaises(TypeError, tup, 'a', '2')

# CRASHES getslice(NULL, 0, 0)


def test_tuple_setitem(self):
# Test PyTuple_SetItem()
setitem = _testcapi.tuple_setitem
tup = (1, 2, 3)
self.assertRaises(SystemError, setitem, tup, 1, 0)
self.assertRaises(SystemError, setitem, {}, 0, 5)
self.assertRaises(SystemError, setitem, tup, PY_SSIZE_T_MIN, 5)
self.assertRaises(SystemError, setitem, tup, PY_SSIZE_T_MAX, 5)
self.assertRaises(SystemError, setitem, tup, -1, 5)
self.assertRaises(SystemError, setitem, tup, len(tup) , 5)
self.assertRaises(TypeError, setitem, 23, 'a', 5)
self.assertRaises(TypeError, setitem, tup, 1.5, 10)

# CRASHES setitem(NULL, 'a', 5)

def test_tuple_set_item(self):
# Test PyTuple_SET_ITEM()
set_item = _testcapi.tuple_set_item
tup = (1, 2, 3)
set_item(tup, 1, (1, 2))
self.assertEqual(tup, (1, (1, 2), 3))

# CRASHES for set_item([1], PY_SSIZE_T_MIN, 5)
# CRASHES for set_item([1], PY_SSIZE_T_MAX, 5)
# CRASHES for set_item([1], -1, 5)
# CRASHES for set_item([], 0, 1)
# CRASHES for set_item(NULL, 0, 1)

def test_tuple_resize(self):
# Test PyTuple_Resize()
pass
133 changes: 133 additions & 0 deletions Modules/_testcapi/tuple.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,140 @@
#include "util.h"


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 *obj)
{
return PyTuple_New(PyLong_AsSsize_t(obj));
}

static PyObject *
tuple_pack(PyObject *Py_UNUSED(module), PyObject *args)
{
Py_RETURN_NONE;
}

static PyObject *
tuple_size(PyObject *Py_UNUSED(module), PyObject *obj)
{
NULLABLE(obj);
RETURN_SIZE(PyTuple_Size(obj));
}

static PyObject *
tuple_get_size(PyObject *Py_UNUSED(module), PyObject *obj)
{
NULLABLE(obj);
RETURN_SIZE(PyTuple_GET_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_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_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;
Py_ssize_t i;
if ( !PyArg_ParseTuple(args, "OnO", &obj, &i, &value)){
return NULL;
}
NULLABLE(obj);
NULLABLE(value);
RETURN_INT(PyTuple_SetItem(obj, i, Py_XNewRef(value)));

}

static PyObject *
tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *obj, *value;
Py_ssize_t i;
if ( !PyArg_ParseTuple(args, "OnO", &obj, &i, &value)){
return NULL;
}
NULLABLE(obj);
NULLABLE(value);
PyTuple_SET_ITEM(obj, i, Py_XNewRef(value));
Py_RETURN_NONE;

}

static PyObject *
tuple_resize(PyObject *Py_UNUSED(module), PyObject *args)
{
PyObject *obj;
Py_ssize_t newsize;
if ( !PyArg_ParseTuple(args, "On", &obj, &newsize)){
return NULL;
}
NULLABLE(obj);
RETURN_INT(_PyTuple_Resize(obj, newsize));

}



static PyMethodDef test_methods[] = {
{"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_get_size", tuple_get_size, METH_O},
{"tuple_getitem", tuple_getitem, METH_VARARGS},
{"tuple_get_item", tuple_get_item, METH_VARARGS},
{"tuple_getslice", tuple_getslice, METH_VARARGS},
{"tuple_setitem", tuple_setitem, METH_VARARGS},
{"tuple_set_item", tuple_set_item, METH_VARARGS},
{"tuple_resize", tuple_resize, METH_VARARGS},
{NULL},
};

Expand Down