diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py new file mode 100644 index 000000000000000..57bc82ce6cc099d --- /dev/null +++ b/Lib/test/test_capi/test_list.py @@ -0,0 +1,226 @@ +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_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"]) + + def test_list_check(self): + self.assertTrue(PyList_Check([2, 3, 5])) + #self.assertFalse(PyList_Check(NULL)) + + def test_list_checkexact(self): + self.assertTrue(PyList_CheckExact([2, 3, 5])) + #self.assertFalse(check(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) + + def test_list_size(self): + 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, 3, 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, 3) + + # 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") + self.assertEqual(PyList_GetSlice(lst, -100, 0), []) + self.assertEqual(PyList_GetSlice(lst, 0, 0), []) + + self.assertEqual(PyList_GetSlice(lst, 1, 3), list("bc")) + 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) + + self.assertEqual(PyList_GetSlice(lst, 100, 0), []) + + # 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"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index c73522b8ecf426d..e8191059d02800b 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -159,7 +159,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/list.c _testcapi/set.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/list.c b/Modules/_testcapi/list.c new file mode 100644 index 000000000000000..a9bea40dd29d24d --- /dev/null +++ b/Modules/_testcapi/list.c @@ -0,0 +1,177 @@ +// clinic/list.c.h uses internal pycore_abstract.h API +#define PYTESTCAPI_NEED_INTERNAL_API + +#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; +} diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 4fa77a8844ee4d6..1a3df5d72f39024 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -40,6 +40,7 @@ int _PyTestCapi_Init_Watchers(PyObject *module); int _PyTestCapi_Init_Long(PyObject *module); int _PyTestCapi_Init_Float(PyObject *module); int _PyTestCapi_Init_Dict(PyObject *module); +int _PyTestCapi_Init_List(PyObject *module); int _PyTestCapi_Init_Set(PyObject *module); int _PyTestCapi_Init_Structmember(PyObject *module); int _PyTestCapi_Init_Exceptions(PyObject *module); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index fc9dc746095b72b..9c1a03da51ea6c4 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3935,6 +3935,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 ae97c34eed50e04..215ab2fbd940f51 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -102,6 +102,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index c3ad2564edd6860..4825c086f784af9 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -36,6 +36,9 @@ Source Files + + Source Files + Source Files