diff --git a/Lib/test/test_capi/test_buffer.py b/Lib/test/test_capi/test_buffer.py
new file mode 100644
index 000000000000000..665a22ca0a3e514
--- /dev/null
+++ b/Lib/test/test_capi/test_buffer.py
@@ -0,0 +1,94 @@
+import unittest
+from test.support import import_helper
+
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+_testbuffer = import_helper.import_module('_testbuffer')
+from _testbuffer import ndarray, ND_WRITABLE, ND_FORTRAN
+
+
+class CAPITest(unittest.TestCase):
+ def test_object_copydata(self):
+ # Test PyObject_CopyData()
+ copydata = _testlimitedcapi.object_copydata
+
+ dest = bytearray(b'xyz')
+ self.assertEqual(copydata(dest, b'abc'), 0)
+ self.assertEqual(dest, b'abc')
+
+ dest = bytearray(5)
+ self.assertEqual(copydata(dest, memoryview(b'abc')), 0)
+ self.assertEqual(dest, b'abc\0\0')
+
+ dest = bytearray()
+ self.assertEqual(copydata(dest, b''), 0)
+ self.assertEqual(dest, b'')
+
+ # Non-contiguous source
+ dest = bytearray(4)
+ self.assertEqual(copydata(dest, memoryview(b'abcdefgh')[::2]), 0)
+ self.assertEqual(dest, b'aceg')
+
+ # Non-contiguous destination
+ base = bytearray(b'........')
+ self.assertEqual(copydata(memoryview(base)[::2], b'wxyz'), 0)
+ self.assertEqual(base, b'w.x.y.z.')
+
+ # Destination too small
+ dest = bytearray(b'xy')
+ self.assertRaises(BufferError, copydata, dest, b'abc')
+ self.assertEqual(dest, b'xy')
+
+ # Read-only destination
+ self.assertRaises(BufferError, copydata, b'xyz', b'abc')
+
+ self.assertRaises(TypeError, copydata, bytearray(3), 'abc')
+ self.assertRaises(TypeError, copydata, [], b'')
+ self.assertRaises(TypeError, copydata, 42, b'')
+
+ # CRASHES copydata(NULL, b'abc')
+ # CRASHES copydata(bytearray(3), NULL)
+
+ def test_object_copydata_multidim(self):
+ # Test PyObject_CopyData() with multi-dimensional buffers
+ copydata = _testlimitedcapi.object_copydata
+ items = list(range(12))
+ c_src = ndarray(items, shape=[3, 4], format='B')
+ f_src = ndarray(items, shape=[3, 4], format='B', flags=ND_FORTRAN)
+
+ for src in c_src, f_src:
+ for flags in ND_WRITABLE, ND_WRITABLE|ND_FORTRAN:
+ with self.subTest(src=src.flags, dest=flags):
+ dest = ndarray([0]*12, shape=[3, 4], format='B',
+ flags=flags)
+ self.assertEqual(copydata(dest, src), 0)
+ self.assertEqual(dest.tolist(), src.tolist())
+
+ # Multi-byte items
+ src = ndarray([1, -2, 3, -4, 5, -6], shape=[2, 3], format='i')
+ dest = ndarray([0]*6, shape=[2, 3], format='i',
+ flags=ND_WRITABLE|ND_FORTRAN)
+ self.assertEqual(copydata(dest, src), 0)
+ self.assertEqual(dest.tolist(), [[1, -2, 3], [-4, 5, -6]])
+
+ # Non-contiguous source
+ src = c_src[::2, ::2]
+ dest = ndarray([0]*4, shape=[2, 2], format='B', flags=ND_WRITABLE)
+ self.assertEqual(copydata(dest, src), 0)
+ self.assertEqual(dest.tolist(), [[0, 2], [8, 10]])
+
+ def test_buffer_fillcontiguousstrides(self):
+ # Test PyBuffer_FillContiguousStrides()
+ fill = _testlimitedcapi.buffer_fillcontiguousstrides
+
+ self.assertEqual(fill((2, 3, 4), 8, 'C'), (96, 32, 8))
+ self.assertEqual(fill((2, 3, 4), 8, 'F'), (8, 16, 48))
+ self.assertEqual(fill((5,), 2, 'C'), (2,))
+ self.assertEqual(fill((5,), 2, 'F'), (2,))
+ self.assertEqual(fill((), 4, 'C'), ())
+ self.assertEqual(fill((), 4, 'F'), ())
+ self.assertEqual(fill((2, 0, 3), 4, 'C'), (0, 12, 4))
+ self.assertEqual(fill((2, 0, 3), 4, 'F'), (4, 8, 0))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Misc/NEWS.d/next/Tests/2026-11-01-11-50-00.gh-issue-160688.Xp4kQw.rst b/Misc/NEWS.d/next/Tests/2026-11-01-11-50-00.gh-issue-160688.Xp4kQw.rst
new file mode 100644
index 000000000000000..e535e38cbcf0a18
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-11-01-11-50-00.gh-issue-160688.Xp4kQw.rst
@@ -0,0 +1,2 @@
+Add tests for the C API functions :c:func:`PyObject_CopyData` and
+:c:func:`PyBuffer_FillContiguousStrides`.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index d8cd8de10dfe084..a410b0b2bff3b87 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -174,7 +174,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 _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.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/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c _testcapi/fpcast.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/buffer.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c _testlimitedcapi/build.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index 1aee3bc9bf3c1df..43ef2e3d06ae1b8 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -13,6 +13,9 @@ module_exec(PyObject *mod)
if (_PyTestLimitedCAPI_Init_Abstract(mod) < 0) {
return -1;
}
+ if (_PyTestLimitedCAPI_Init_Buffer(mod) < 0) {
+ return -1;
+ }
if (_PyTestLimitedCAPI_Init_ByteArray(mod) < 0) {
return -1;
}
diff --git a/Modules/_testlimitedcapi/buffer.c b/Modules/_testlimitedcapi/buffer.c
new file mode 100644
index 000000000000000..3d19feea1974201
--- /dev/null
+++ b/Modules/_testlimitedcapi/buffer.c
@@ -0,0 +1,86 @@
+#include "pyconfig.h" // Py_GIL_DISABLED
+#ifdef Py_GIL_DISABLED
+# define Py_TARGET_ABI3T 0x030f0000
+#else
+ // Need limited C API 3.11 for the buffer protocol
+# define Py_LIMITED_API 0x030b0000
+#endif
+
+#include "parts.h"
+#include "util.h"
+
+
+static PyObject *
+object_copydata(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *dest, *src;
+ if (!PyArg_ParseTuple(args, "OO", &dest, &src)) {
+ return NULL;
+ }
+ RETURN_INT(PyObject_CopyData(dest, src));
+}
+
+static PyObject *
+buffer_fillcontiguousstrides(PyObject *Py_UNUSED(module), PyObject *args)
+{
+ PyObject *shape_obj;
+ int itemsize, order;
+ if (!PyArg_ParseTuple(args, "O!iC", &PyTuple_Type, &shape_obj,
+ &itemsize, &order))
+ {
+ return NULL;
+ }
+ Py_ssize_t size = PyTuple_Size(shape_obj);
+ if (size < 0) {
+ return NULL;
+ }
+ int nd = (int)size;
+ Py_ssize_t *shape = PyMem_Malloc(size * sizeof(Py_ssize_t));
+ Py_ssize_t *strides = PyMem_Malloc(size * sizeof(Py_ssize_t));
+ if (shape == NULL || strides == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ for (int k = 0; k < nd; k++) {
+ shape[k] = PyLong_AsSsize_t(PyTuple_GetItem(shape_obj, k));
+ if (shape[k] == -1 && PyErr_Occurred()) {
+ goto error;
+ }
+ }
+
+ PyBuffer_FillContiguousStrides(nd, shape, strides, itemsize, (char)order);
+
+ PyObject *result = PyTuple_New(size);
+ if (result == NULL) {
+ goto error;
+ }
+ for (int k = 0; k < nd; k++) {
+ PyObject *item = PyLong_FromSsize_t(strides[k]);
+ if (item == NULL) {
+ Py_DECREF(result);
+ goto error;
+ }
+ PyTuple_SetItem(result, k, item);
+ }
+ PyMem_Free(shape);
+ PyMem_Free(strides);
+ return result;
+
+error:
+ PyMem_Free(shape);
+ PyMem_Free(strides);
+ return NULL;
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"object_copydata", object_copydata, METH_VARARGS},
+ {"buffer_fillcontiguousstrides", buffer_fillcontiguousstrides, METH_VARARGS},
+ {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Buffer(PyObject *m)
+{
+ return PyModule_AddFunctions(m, test_methods);
+}
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 1e9d140899cecfd..9d34ce087f749d7 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -25,6 +25,7 @@
#endif
int _PyTestLimitedCAPI_Init_Abstract(PyObject *module);
+int _PyTestLimitedCAPI_Init_Buffer(PyObject *module);
int _PyTestLimitedCAPI_Init_ByteArray(PyObject *module);
int _PyTestLimitedCAPI_Init_Bytes(PyObject *module);
int _PyTestLimitedCAPI_Init_Capsule(PyObject *module);
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 1a40cd0d2271f5f..e85d69bb2d780b9 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -95,6 +95,7 @@
+
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index 1b13da0ebca7ba2..1bd170ad2381705 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -10,6 +10,7 @@
+