Skip to content

[3.13] gh-111495: Add tests for PyTuple C API (GH-118757) #123371

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

Merged
merged 1 commit into from
Aug 27, 2024
Merged
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
4 changes: 4 additions & 0 deletions Lib/test/test_capi/test_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ def python_hash_pointer(x):
# Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1)
self.assertEqual(hash_pointer(VOID_P_MAX), -2)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions Lib/test/test_capi/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,7 @@ def test_list_extend(self):

# CRASHES list_extend(NULL, [])
# CRASHES list_extend([], NULL)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions Lib/test/test_capi/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,7 @@ def test_set_next_entry(self):
with self.assertRaises(SystemError):
set_next(object(), 0)
# CRASHES: set_next(NULL, 0)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions Lib/test/test_capi/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ def test_time(self):
# Test PyTime_Time() and PyTime_TimeRaw()
self.check_clock(_testcapi.PyTime_Time, time.time)
self.check_clock(_testcapi.PyTime_TimeRaw, time.time)


if __name__ == "__main__":
unittest.main()
261 changes: 261 additions & 0 deletions Lib/test/test_capi/test_tuple.py
Original file line number Diff line number Diff line change
@@ -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 = 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_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()
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,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/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/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
Loading
Loading