Skip to content

gh-111495: Add tests on PyTuple C APIs #122833

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 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions Lib/test/test_capi/test_tuple.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
81 changes: 76 additions & 5 deletions Modules/_testcapi/tuple.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
3 changes: 3 additions & 0 deletions Modules/_testlimitedcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions Modules/_testlimitedcapi/parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
128 changes: 128 additions & 0 deletions Modules/_testlimitedcapi/tuple.c
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions PCbuild/_testlimitedcapi.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\pyos.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\set.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\sys.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions PCbuild/_testlimitedcapi.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\pyos.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\set.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\sys.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\testcapi_long.h" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
Expand Down
Loading