Skip to content

Commit 85e4186

Browse files
committed
gh-160688: Add tests for PyObject_CopyData() and PyBuffer_FillContiguousStrides()
1 parent c436e8d commit 85e4186

7 files changed

Lines changed: 187 additions & 1 deletion

File tree

‎Lib/test/test_capi/test_buffer.py‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import unittest
2+
from test.support import import_helper
3+
4+
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
5+
_testbuffer = import_helper.import_module('_testbuffer')
6+
from _testbuffer import ndarray, ND_WRITABLE, ND_FORTRAN
7+
8+
9+
class CAPITest(unittest.TestCase):
10+
def test_object_copydata(self):
11+
# Test PyObject_CopyData()
12+
copydata = _testlimitedcapi.object_copydata
13+
14+
dest = bytearray(b'xyz')
15+
self.assertEqual(copydata(dest, b'abc'), 0)
16+
self.assertEqual(dest, b'abc')
17+
18+
dest = bytearray(5)
19+
self.assertEqual(copydata(dest, memoryview(b'abc')), 0)
20+
self.assertEqual(dest, b'abc\0\0')
21+
22+
dest = bytearray()
23+
self.assertEqual(copydata(dest, b''), 0)
24+
self.assertEqual(dest, b'')
25+
26+
# Non-contiguous source
27+
dest = bytearray(4)
28+
self.assertEqual(copydata(dest, memoryview(b'abcdefgh')[::2]), 0)
29+
self.assertEqual(dest, b'aceg')
30+
31+
# Non-contiguous destination
32+
base = bytearray(b'........')
33+
self.assertEqual(copydata(memoryview(base)[::2], b'wxyz'), 0)
34+
self.assertEqual(base, b'w.x.y.z.')
35+
36+
# Destination too small
37+
dest = bytearray(b'xy')
38+
self.assertRaises(BufferError, copydata, dest, b'abc')
39+
self.assertEqual(dest, b'xy')
40+
41+
# Read-only destination
42+
self.assertRaises(BufferError, copydata, b'xyz', b'abc')
43+
44+
self.assertRaises(TypeError, copydata, bytearray(3), 'abc')
45+
self.assertRaises(TypeError, copydata, [], b'')
46+
self.assertRaises(TypeError, copydata, 42, b'')
47+
48+
# CRASHES copydata(NULL, b'abc')
49+
# CRASHES copydata(bytearray(3), NULL)
50+
51+
def test_object_copydata_multidim(self):
52+
# Test PyObject_CopyData() with multi-dimensional buffers
53+
copydata = _testlimitedcapi.object_copydata
54+
items = list(range(12))
55+
c_src = ndarray(items, shape=[3, 4], format='B')
56+
f_src = ndarray(items, shape=[3, 4], format='B', flags=ND_FORTRAN)
57+
58+
for src in c_src, f_src:
59+
for flags in ND_WRITABLE, ND_WRITABLE|ND_FORTRAN:
60+
with self.subTest(src=src.flags, dest=flags):
61+
dest = ndarray([0]*12, shape=[3, 4], format='B',
62+
flags=flags)
63+
self.assertEqual(copydata(dest, src), 0)
64+
self.assertEqual(dest.tolist(), src.tolist())
65+
66+
# Multi-byte items
67+
src = ndarray([1, -2, 3, -4, 5, -6], shape=[2, 3], format='i')
68+
dest = ndarray([0]*6, shape=[2, 3], format='i',
69+
flags=ND_WRITABLE|ND_FORTRAN)
70+
self.assertEqual(copydata(dest, src), 0)
71+
self.assertEqual(dest.tolist(), [[1, -2, 3], [-4, 5, -6]])
72+
73+
# Non-contiguous source
74+
src = c_src[::2, ::2]
75+
dest = ndarray([0]*4, shape=[2, 2], format='B', flags=ND_WRITABLE)
76+
self.assertEqual(copydata(dest, src), 0)
77+
self.assertEqual(dest.tolist(), [[0, 2], [8, 10]])
78+
79+
def test_buffer_fillcontiguousstrides(self):
80+
# Test PyBuffer_FillContiguousStrides()
81+
fill = _testlimitedcapi.buffer_fillcontiguousstrides
82+
83+
self.assertEqual(fill((2, 3, 4), 8, 'C'), (96, 32, 8))
84+
self.assertEqual(fill((2, 3, 4), 8, 'F'), (8, 16, 48))
85+
self.assertEqual(fill((5,), 2, 'C'), (2,))
86+
self.assertEqual(fill((5,), 2, 'F'), (2,))
87+
self.assertEqual(fill((), 4, 'C'), ())
88+
self.assertEqual(fill((), 4, 'F'), ())
89+
self.assertEqual(fill((2, 0, 3), 4, 'C'), (0, 12, 4))
90+
self.assertEqual(fill((2, 0, 3), 4, 'F'), (4, 8, 0))
91+
92+
93+
if __name__ == "__main__":
94+
unittest.main()

‎Modules/Setup.stdlib.in‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
175175
@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
176176
@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
177-
@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
177+
@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
178178
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
179179
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
180180

‎Modules/_testlimitedcapi.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ module_exec(PyObject *mod)
1313
if (_PyTestLimitedCAPI_Init_Abstract(mod) < 0) {
1414
return -1;
1515
}
16+
if (_PyTestLimitedCAPI_Init_Buffer(mod) < 0) {
17+
return -1;
18+
}
1619
if (_PyTestLimitedCAPI_Init_ByteArray(mod) < 0) {
1720
return -1;
1821
}

‎Modules/_testlimitedcapi/buffer.c‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "pyconfig.h" // Py_GIL_DISABLED
2+
#ifdef Py_GIL_DISABLED
3+
# define Py_TARGET_ABI3T 0x030f0000
4+
#else
5+
// Need limited C API 3.11 for the buffer protocol
6+
# define Py_LIMITED_API 0x030b0000
7+
#endif
8+
9+
#include "parts.h"
10+
#include "util.h"
11+
12+
13+
static PyObject *
14+
object_copydata(PyObject *Py_UNUSED(module), PyObject *args)
15+
{
16+
PyObject *dest, *src;
17+
if (!PyArg_ParseTuple(args, "OO", &dest, &src)) {
18+
return NULL;
19+
}
20+
RETURN_INT(PyObject_CopyData(dest, src));
21+
}
22+
23+
static PyObject *
24+
buffer_fillcontiguousstrides(PyObject *Py_UNUSED(module), PyObject *args)
25+
{
26+
PyObject *shape_obj;
27+
int itemsize, order;
28+
if (!PyArg_ParseTuple(args, "O!iC", &PyTuple_Type, &shape_obj,
29+
&itemsize, &order))
30+
{
31+
return NULL;
32+
}
33+
Py_ssize_t size = PyTuple_Size(shape_obj);
34+
if (size < 0) {
35+
return NULL;
36+
}
37+
int nd = (int)size;
38+
Py_ssize_t *shape = PyMem_Malloc(size * sizeof(Py_ssize_t));
39+
Py_ssize_t *strides = PyMem_Malloc(size * sizeof(Py_ssize_t));
40+
if (shape == NULL || strides == NULL) {
41+
PyErr_NoMemory();
42+
goto error;
43+
}
44+
for (int k = 0; k < nd; k++) {
45+
shape[k] = PyLong_AsSsize_t(PyTuple_GetItem(shape_obj, k));
46+
if (shape[k] == -1 && PyErr_Occurred()) {
47+
goto error;
48+
}
49+
}
50+
51+
PyBuffer_FillContiguousStrides(nd, shape, strides, itemsize, (char)order);
52+
53+
PyObject *result = PyTuple_New(size);
54+
if (result == NULL) {
55+
goto error;
56+
}
57+
for (int k = 0; k < nd; k++) {
58+
PyObject *item = PyLong_FromSsize_t(strides[k]);
59+
if (item == NULL) {
60+
Py_DECREF(result);
61+
goto error;
62+
}
63+
PyTuple_SetItem(result, k, item);
64+
}
65+
PyMem_Free(shape);
66+
PyMem_Free(strides);
67+
return result;
68+
69+
error:
70+
PyMem_Free(shape);
71+
PyMem_Free(strides);
72+
return NULL;
73+
}
74+
75+
76+
static PyMethodDef test_methods[] = {
77+
{"object_copydata", object_copydata, METH_VARARGS},
78+
{"buffer_fillcontiguousstrides", buffer_fillcontiguousstrides, METH_VARARGS},
79+
{NULL},
80+
};
81+
82+
int
83+
_PyTestLimitedCAPI_Init_Buffer(PyObject *m)
84+
{
85+
return PyModule_AddFunctions(m, test_methods);
86+
}

‎Modules/_testlimitedcapi/parts.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#endif
2626

2727
int _PyTestLimitedCAPI_Init_Abstract(PyObject *module);
28+
int _PyTestLimitedCAPI_Init_Buffer(PyObject *module);
2829
int _PyTestLimitedCAPI_Init_ByteArray(PyObject *module);
2930
int _PyTestLimitedCAPI_Init_Bytes(PyObject *module);
3031
int _PyTestLimitedCAPI_Init_Capsule(PyObject *module);

‎PCbuild/_testlimitedcapi.vcxproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<ItemGroup>
9696
<ClCompile Include="..\Modules\_testlimitedcapi.c" />
9797
<ClCompile Include="..\Modules\_testlimitedcapi\abstract.c" />
98+
<ClCompile Include="..\Modules\_testlimitedcapi\buffer.c" />
9899
<ClCompile Include="..\Modules\_testlimitedcapi\bytearray.c" />
99100
<ClCompile Include="..\Modules\_testlimitedcapi\bytes.c" />
100101
<ClCompile Include="..\Modules\_testlimitedcapi\capsule.c" />

‎PCbuild/_testlimitedcapi.vcxproj.filters‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</ItemGroup>
1111
<ItemGroup>
1212
<ClCompile Include="..\Modules\_testlimitedcapi\abstract.c" />
13+
<ClCompile Include="..\Modules\_testlimitedcapi\buffer.c" />
1314
<ClCompile Include="..\Modules\_testlimitedcapi\bytearray.c" />
1415
<ClCompile Include="..\Modules\_testlimitedcapi\bytes.c" />
1516
<ClCompile Include="..\Modules\_testlimitedcapi\capsule.c" />

0 commit comments

Comments
 (0)