-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
gh-111495: Add tests for PyTuple C API #118757
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
Changes from 5 commits
0e590c4
ddf0af7
571da31
69d9ead
3260ef4
b5b4664
1d53b0f
3248144
8451a4d
b2d0eac
a977efa
95d1544
14b3594
f9554bc
e79616f
26fc5a2
5047d6d
a2ea4c4
72aa1ed
f10160c
8a6d636
266de84
190e88a
d25eae8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
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 | ||
|
||
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.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((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 = TupleSubclass((1, 2, 3)) | ||
self.assertEqual(getitem(tup, 0), 1) | ||
self.assertEqual(getitem(tup, 2), 3) | ||
|
||
tup = (1, 2, 3) | ||
self.assertEqual(getitem(tup, 0), 1) | ||
self.assertEqual(getitem(tup, 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) | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# CRASHES getitem(NULL, 1) | ||
|
||
def test_tuple_get_item(self): | ||
# Test PyTuple_GET_ITEM() | ||
get_item = _testcapi.tuple_get_item | ||
|
||
tup = TupleSubclass((1, 2, 3)) | ||
self.assertEqual(get_item(tup, 0), 1) | ||
self.assertEqual(get_item(tup, 2), 3) | ||
|
||
tup = (1, 2, 3) | ||
self.assertEqual(get_item(tup, 0), 1) | ||
self.assertEqual(get_item(tup, 2), 3) | ||
|
||
def test_tuple_getslice(self): | ||
# Test PyTuple_GetSlice() | ||
getslice = _testlimitedcapi.tuple_getslice | ||
|
||
# empty | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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, 0, 0), ()) | ||
self.assertEqual(getslice(tup, 3, 0), ()) | ||
tup = TupleSubclass((1, 2, 3)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can add check_tuple_getslice(), all it once with a tuple, and then call it with the subclass. Instead of copying/pasting the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To many hassle, in my opinion. For subclass we only need few basic tests, just to be sure that they are accepted. |
||
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, 0, 0), ()) | ||
self.assertEqual(getslice(tup, 3, 0), ()) | ||
|
||
# slice | ||
tup = (1, 2, 3) | ||
self.assertEqual(getslice(tup, 1, 3), (2, 3)) | ||
tup = TupleSubclass((1, 2, 3)) | ||
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, 1) | ||
self.assertRaises(SystemError, getslice, 42, 0, 1) | ||
# CRASHES getslice(NULL, 0, 0) | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_tuple_setitem(self): | ||
# Test PyTuple_SetItem() | ||
setitem = _testlimitedcapi.tuple_setitem | ||
|
||
tup = (0, 0) | ||
self.assertEqual(setitem(tup, 0, 1), (1, 0)) | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.assertEqual(setitem(tup, 1, 1), (0, 1)) | ||
|
||
self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) | ||
self.assertRaises(IndexError, setitem, tup, -1, 1) | ||
self.assertRaises(IndexError, setitem, tup, len(tup), 1) | ||
self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, 1) | ||
self.assertRaises(SystemError, setitem, [1], 0, 1) | ||
self.assertRaises(SystemError, setitem, 42, 0, 1) | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# CRASHES setitem(NULL, 1, 5) | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_tuple_set_item(self): | ||
# Test PyTuple_SET_ITEM() | ||
set_item = _testcapi.tuple_set_item | ||
|
||
tup = (0, 0) | ||
self.assertEqual(set_item(tup, 0, 1), (1, 0)) | ||
self.assertEqual(set_item(tup, 1, 1), (0, 1)) | ||
|
||
def test_tuple_resize(self): | ||
# Test PyTuple_Resize() | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
resize = _testcapi.tuple_resize | ||
size = _testlimitedcapi.tuple_size | ||
tup = resize(0) | ||
self.assertEqual(size(tup), 0) | ||
tup = resize(2) | ||
self.assertEqual(size(tup), 2) | ||
self.assertRaises(MemoryError, resize, PY_SSIZE_T_MAX) | ||
self.assertRaises(SystemError, resize, -1) | ||
self.assertRaises(SystemError, resize, PY_SSIZE_T_MIN) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,14 +2,80 @@ | |
#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_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(obj); | ||
NULLABLE(value); | ||
if (obj) { | ||
Py_ssize_t size = PyTuple_Size(obj); | ||
serhiy-storchaka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
newtuple = PyTuple_New(size); | ||
if (!newtuple) { | ||
return NULL; | ||
} | ||
for (Py_ssize_t n = 0; n < size; n++) { | ||
PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))); | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can move this code in a "tuple_copy()" function and reuse it in _tuple_resize(). |
||
} | ||
else { | ||
newtuple = obj; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to copy the tuple in both cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you copy an instance of a tuple subclass? Or non-tuple? We need to test this API with tuple subclasses and non-tuples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't you have to INCREF obj here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not here, but before returning result. I fixed this in other way. |
||
} | ||
PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return newtuple; | ||
} | ||
|
||
static PyObject * | ||
tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) | ||
{ | ||
Py_ssize_t newsize; | ||
if (!PyArg_ParseTuple(args, "n", &newsize)) { | ||
return NULL; | ||
} | ||
PyObject *obj = PyTuple_New(0); | ||
int r = _PyTuple_Resize(&obj, newsize); | ||
if (r == -1) { | ||
return NULL; | ||
skirpichev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return 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}, | ||
{NULL}, | ||
}; | ||
|
||
int | ||
_PyTestCapi_Init_Tuple(PyObject *m) | ||
{ | ||
if (PyModule_AddFunctions(m, test_methods) < 0){ | ||
if (PyModule_AddFunctions(m, test_methods) < 0) { | ||
return -1; | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.