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