diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py new file mode 100644 index 00000000000000..de3cb82c1716d3 --- /dev/null +++ b/Lib/test/test_capi/test_list.py @@ -0,0 +1,233 @@ +import unittest +from collections import UserList +import _testcapi + + +NULL = None +PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN +PY_SSIZE_T_MAX = _testcapi.PY_SSIZE_T_MAX + +PyList_Check = _testcapi.list_check +PyList_CheckExact = _testcapi.list_checkexact +PyList_Size = _testcapi.list_size +PyList_GetItem = _testcapi.list_getitem +PyList_SetItem = _testcapi.list_setitem +PyList_Insert = _testcapi.list_insert +PyList_Append = _testcapi.list_append +PyList_Sort = _testcapi.list_sort +PyList_AsTuple = _testcapi.list_astuple +PyList_Reverse = _testcapi.list_reverse +PyList_GetSlice = _testcapi.list_getslice +PyList_SetSlice = _testcapi.list_setslice + + +class ListSubclass(list): + pass + + +class CAPITest(unittest.TestCase): + + def test_list_check(self): + self.assertTrue(PyList_Check([2, 3, 5])) + # CRASHES self.assertFalse(PyList_Check(NULL)) + + def test_list_checkexact(self): + self.assertTrue(PyList_CheckExact([2, 3, 5])) + # CRASHES self.assertFalse(PyList_CheckExact(NULL)) + + def test_list_new(self): + expected = [None, None, None] + + PyList_New = _testcapi.list_new + lst = PyList_New(3) + self.assertEqual(lst, expected) + self.assertIs(type(lst), list) + lst2 = PyList_New(3) + self.assertIsNot(lst2, lst) + + with self.assertRaises(SystemError): + PyList_New(-1) + + def test_list_size(self): + self.assertEqual(PyList_Size([]), 0) + self.assertEqual(PyList_Size([2, 3, 5]), 3) + + def test_list_getitem(self): + lst = list("abc") + self.assertEqual(PyList_GetItem(lst, 0), "a") + self.assertEqual(PyList_GetItem(lst, 1), "b") + self.assertEqual(PyList_GetItem(lst, 2), "c") + + for invalid_index in (PY_SSIZE_T_MIN, -1, len(lst), 10, PY_SSIZE_T_MAX): + with self.subTest(invalid_index=invalid_index): + self.assertRaises(IndexError, PyList_GetItem, lst, invalid_index) + + lst2 = ListSubclass(lst) + self.assertEqual(PyList_GetItem(lst2, 1), "b") + self.assertRaises(IndexError, PyList_GetItem, lst2, len(lst2)) + + # CRASHES PyList_GetItem(NULL, 0) + + def test_list_setitem(self): + lst = list("abc") + PyList_SetItem(lst, 1, "X") + self.assertEqual(lst, ["a", "X", "c"]) + self.assertRaises(IndexError, PyList_SetItem, lst, 3, "Y") + + lst2 = ListSubclass(list("abc")) + PyList_SetItem(lst2, 1, "X") + self.assertEqual(lst2, ["a", "X", "c"]) + + # CRASHES PyList_SetItem(list("abc"), 0, NULL) + # CRASHES PyList_SetItem(NULL, 0, 'x') + + def test_list_insert(self): + lst = list("abc") + PyList_Insert(lst, 1, "X") + self.assertEqual(lst, ["a", "X", "b", "c"]) + PyList_Insert(lst, -1, "Y") + self.assertEqual(lst, ["a", "X", "b", "Y", "c"]) + PyList_Insert(lst, 100, "Z") + self.assertEqual(lst, ["a", "X", "b", "Y", "c", "Z"]) + PyList_Insert(lst, -100, "0") + self.assertEqual(lst, ["0", "a", "X", "b", "Y", "c", "Z"]) + + lst2 = ListSubclass(list("abc")) + PyList_Insert(lst2, 1, "X") + self.assertEqual(lst2, ["a", "X", "b", "c"]) + + with self.assertRaises(SystemError): + PyList_Insert(list("abc"), 0, NULL) + + # CRASHES PyList_Insert(NULL, 0, 'x') + + def test_list_append(self): + lst = [] + PyList_Append(lst, "a") + self.assertEqual(lst, ["a"]) + PyList_Append(lst, "b") + self.assertEqual(lst, ["a", "b"]) + + lst2 = ListSubclass() + PyList_Append(lst2, "X") + self.assertEqual(lst2, ["X"]) + + self.assertRaises(SystemError, PyList_Append, [], NULL) + + # CRASHES PyList_Append(NULL, 'a') + + def test_list_sort(self): + lst = [4, 6, 7, 3, 1, 5, 9, 2, 0, 8] + PyList_Sort(lst) + self.assertEqual(lst, list(range(10))) + + lst2 = ListSubclass([4, 6, 7, 3, 1, 5, 9, 2, 0, 8]) + PyList_Sort(lst2) + self.assertEqual(lst2, list(range(10))) + + with self.assertRaises(SystemError): + PyList_Sort(NULL) + + def test_list_astuple(self): + self.assertEqual(PyList_AsTuple([]), ()) + self.assertEqual(PyList_AsTuple([2, 5, 10]), (2, 5, 10)) + + with self.assertRaises(SystemError): + PyList_AsTuple(NULL) + + def test_list_reverse(self): + def list_reverse(lst): + self.assertEqual(PyList_Reverse(lst), 0) + return lst + + self.assertEqual(list_reverse([]), []) + self.assertEqual(list_reverse([2, 5, 10]), [10, 5, 2]) + + with self.assertRaises(SystemError): + PyList_Reverse(NULL) + + def test_list_getslice(self): + lst = list("abcdef") + + # empty + self.assertEqual(PyList_GetSlice(lst, -100, 0), []) + self.assertEqual(PyList_GetSlice(lst, 0, 0), []) + self.assertEqual(PyList_GetSlice(lst, 3, 0), []) + + # slice + self.assertEqual(PyList_GetSlice(lst, 1, 3), list("bc")) + + # whole + self.assertEqual(PyList_GetSlice(lst, 0, len(lst)), lst) + self.assertEqual(PyList_GetSlice(lst, 0, 100), lst) + self.assertEqual(PyList_GetSlice(lst, -100, 100), lst) + + # CRASHES PyList_GetSlice(NULL, 0, 0) + + def test_list_setslice(self): + def set_slice(lst, low, high, value): + lst = lst.copy() + self.assertEqual(PyList_SetSlice(lst, low, high, value), 0) + return lst + + # insert items + self.assertEqual(set_slice([], 0, 0, list("abc")), list("abc")) + lst = list("abc") + self.assertEqual(set_slice(lst, 0, 0, ["X"]), list("Xabc")) + self.assertEqual(set_slice(lst, 1, 1, list("XY")), list("aXYbc")) + self.assertEqual(set_slice(lst, len(lst), len(lst), ["X"]), list("abcX")) + self.assertEqual(set_slice(lst, 100, 100, ["X"]), list("abcX")) + + # replace items + lst = list("abc") + self.assertEqual(set_slice(lst, -100, 1, list("X")), list("Xbc")) + self.assertEqual(set_slice(lst, 1, 2, list("X")), list("aXc")) + self.assertEqual(set_slice(lst, 1, 3, list("XY")), list("aXY")) + self.assertEqual(set_slice(lst, 0, 3, list("XYZ")), list("XYZ")) + + # delete items + lst = list("abcdef") + self.assertEqual(set_slice(lst, 0, len(lst), []), []) + self.assertEqual(set_slice(lst, -100, 100, []), []) + self.assertEqual(set_slice(lst, 1, 5, []), list("af")) + self.assertEqual(set_slice(lst, 3, len(lst), []), list("abc")) + + # delete items with NULL + lst = list("abcdef") + self.assertEqual(set_slice(lst, 0, len(lst), NULL), []) + self.assertEqual(set_slice(lst, 3, len(lst), NULL), list("abc")) + + # CRASHES PyList_SetSlice(NULL, 0, 0, ["x"]) + + def test_not_list_objects(self): + for obj in ( + 123, + UserList([2, 3, 5]), + (2, 3, 5), # tuple + object(), + ): + with self.subTest(obj=obj): + self.assertFalse(PyList_Check(obj)) + self.assertFalse(PyList_CheckExact(obj)) + with self.assertRaises(SystemError): + PyList_Size(obj) + with self.assertRaises(SystemError): + PyList_SetItem(obj, 0, "x") + with self.assertRaises(SystemError): + PyList_Insert(obj, 0, "x") + with self.assertRaises(SystemError): + PyList_Append(obj, "x") + with self.assertRaises(SystemError): + PyList_Sort(obj) + with self.assertRaises(SystemError): + PyList_AsTuple(obj) + with self.assertRaises(SystemError): + PyList_Reverse(obj) + with self.assertRaises(SystemError): + PyList_GetSlice(obj, 0, 1) + with self.assertRaises(SystemError): + PyList_SetSlice(obj, 0, 1, ["x"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c index 9329ddc3bddab4..a81b0ef8c27cca 100644 --- a/Modules/_testcapi/list.c +++ b/Modules/_testcapi/list.c @@ -1,17 +1,170 @@ #include "parts.h" #include "util.h" +static PyObject * +list_check(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyList_Check(obj)); +} + +static PyObject * +list_checkexact(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyList_CheckExact(obj)); +} + +static PyObject * +list_new(PyObject *self, PyObject *args) +{ + Py_ssize_t n; + if (!PyArg_ParseTuple(args, "n", &n)) { + return NULL; + } + + PyObject *list = PyList_New(n); + if (list == NULL) { + return NULL; + } + + for (Py_ssize_t i=0; i < n; i++) { + PyList_SET_ITEM(list, i, Py_NewRef(Py_None)); + } + return list; +} + +static PyObject * +list_size(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + RETURN_SIZE(PyList_Size(obj)); +} + +static PyObject * +list_getitem(PyObject *self, PyObject *args) +{ + PyObject *list; + Py_ssize_t index; + if (!PyArg_ParseTuple(args, "On", &list, &index)) { + return NULL; + } + NULLABLE(list); + + PyObject *value = PyList_GetItem(list, index); + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } + return Py_NewRef(PyExc_IndexError); + } + return Py_NewRef(value); +} + +static PyObject * +list_setitem(PyObject *self, PyObject *args) +{ + PyObject *list, *item; + Py_ssize_t index; + if (!PyArg_ParseTuple(args, "OnO", &list, &index, &item)) { + return NULL; + } + NULLABLE(list); + NULLABLE(item); + RETURN_INT(PyList_SetItem(list, index, item)); +} + +static PyObject * +list_insert(PyObject *self, PyObject *args) +{ + PyObject *list, *item; + Py_ssize_t index; + if (!PyArg_ParseTuple(args, "OnO", &list, &index, &item)) { + return NULL; + } + NULLABLE(list); + NULLABLE(item); + RETURN_INT(PyList_Insert(list, index, item)); +} + +static PyObject * +list_append(PyObject *self, PyObject *args) +{ + PyObject *list, *item; + if (!PyArg_ParseTuple(args, "OO", &list, &item)) { + return NULL; + } + NULLABLE(list); + NULLABLE(item); + RETURN_INT(PyList_Append(list, item)); +} + +static PyObject * +list_sort(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + RETURN_INT(PyList_Sort(obj)); +} + +static PyObject * +list_astuple(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + return PyList_AsTuple(obj); +} + +static PyObject * +list_reverse(PyObject *self, PyObject *obj) +{ + NULLABLE(obj); + RETURN_INT(PyList_Reverse(obj)); +} + +static PyObject * +list_getslice(PyObject *self, PyObject *args) +{ + PyObject *list; + Py_ssize_t low, high; + if (!PyArg_ParseTuple(args, "Onn", &list, &low, &high)) { + return NULL; + } + NULLABLE(list); + return PyList_GetSlice(list, low, high); +} + +static PyObject * +list_setslice(PyObject *self, PyObject *args) +{ + PyObject *list, *value; + Py_ssize_t low, high; + if (!PyArg_ParseTuple(args, "OnnO", &list, &low, &high, &value)) { + return NULL; + } + NULLABLE(list); + NULLABLE(value); + RETURN_INT(PyList_SetSlice(list, low, high, value)); +} + static PyMethodDef test_methods[] = { + {"list_check", list_check, METH_O}, + {"list_checkexact", list_checkexact, METH_O}, + {"list_new", list_new, METH_VARARGS}, + {"list_size", list_size, METH_O}, + {"list_getitem", list_getitem, METH_VARARGS}, + {"list_setitem", list_setitem, METH_VARARGS}, + {"list_insert", list_insert, METH_VARARGS}, + {"list_append", list_append, METH_VARARGS}, + {"list_sort", list_sort, METH_O}, + {"list_astuple", list_astuple, METH_O}, + {"list_reverse", list_reverse, METH_O}, + {"list_getslice", list_getslice, METH_VARARGS}, + {"list_setslice", list_setslice, METH_VARARGS}, {NULL}, }; int _PyTestCapi_Init_List(PyObject *m) { - if (PyModule_AddFunctions(m, test_methods) < 0){ - return -1; - } - - return 0; + return PyModule_AddFunctions(m, test_methods); } diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 9af414e10d0160..bdeacbf4b2a452 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -44,6 +44,7 @@ int _PyTestCapi_Init_Float(PyObject *module); int _PyTestCapi_Init_Complex(PyObject *module); int _PyTestCapi_Init_Numbers(PyObject *module); int _PyTestCapi_Init_Dict(PyObject *module); +int _PyTestCapi_Init_List(PyObject *module); int _PyTestCapi_Init_Set(PyObject *module); int _PyTestCapi_Init_List(PyObject *module); int _PyTestCapi_Init_Tuple(PyObject *module); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 300025ce3705fe..05ef02a7c24f4e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3947,6 +3947,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Dict(m) < 0) { return NULL; } + if (_PyTestCapi_Init_List(m) < 0) { + return NULL; + } if (_PyTestCapi_Init_Set(m) < 0) { return NULL; } diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 2d8a652a84b92d..048d839448face 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -104,6 +104,7 @@ +