Skip to content

Commit 83f6ec5

Browse files
committed
Test buffer protocol against NumPy
1 parent ea8c424 commit 83f6ec5

File tree

1 file changed

+81
-45
lines changed

1 file changed

+81
-45
lines changed

tests/test_buffers.py

+81-45
Original file line numberDiff line numberDiff line change
@@ -241,115 +241,151 @@ def test_buffer_exception():
241241
assert "for context" in str(excinfo.value.__cause__)
242242

243243

244-
def test_to_pybuffer():
245-
mat = m.Matrix(5, 4)
244+
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
245+
def test_c_contiguous_to_pybuffer(type):
246+
if type == "pybind11":
247+
mat = m.Matrix(5, 4)
248+
elif type == "numpy":
249+
mat = np.empty((5, 4), dtype=np.float32)
250+
else:
251+
raise ValueError(f"Unknown parametrization {type}")
246252

247253
info = m.get_py_buffer(mat, m.PyBUF_SIMPLE)
248254
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
249-
assert info.len == mat.rows() * mat.cols() * info.itemsize
255+
assert info.len == 5 * 4 * info.itemsize
250256
assert info.ndim == 0 # See discussion on PR #5407.
251257
assert info.shape is None
252258
assert info.strides is None
253259
assert info.suboffsets is None
254260
assert not info.readonly
255261
info = m.get_py_buffer(mat, m.PyBUF_ND)
256262
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
257-
assert info.len == mat.rows() * mat.cols() * info.itemsize
263+
assert info.len == 5 * 4 * info.itemsize
258264
assert info.ndim == 2
259265
assert info.shape == [5, 4]
260266
assert info.strides is None
261267
assert info.suboffsets is None
262268
assert not info.readonly
263269
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
264270
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
265-
assert info.len == mat.rows() * mat.cols() * info.itemsize
271+
assert info.len == 5 * 4 * info.itemsize
266272
assert info.ndim == 2
267273
assert info.shape == [5, 4]
268274
assert info.strides == [4 * info.itemsize, info.itemsize]
269275
assert info.suboffsets is None
270276
assert not info.readonly
271277
info = m.get_py_buffer(mat, m.PyBUF_INDIRECT)
272278
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
273-
assert info.len == mat.rows() * mat.cols() * info.itemsize
279+
assert info.len == 5 * 4 * info.itemsize
274280
assert info.ndim == 2
275281
assert info.shape == [5, 4]
276282
assert info.strides == [4 * info.itemsize, info.itemsize]
277283
assert info.suboffsets is None # Should be filled in here, but we don't use it.
278284
assert not info.readonly
279285

286+
287+
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
288+
def test_fortran_contiguous_to_pybuffer(type):
289+
if type == "pybind11":
290+
mat = m.FortranMatrix(5, 4)
291+
elif type == "numpy":
292+
mat = np.empty((5, 4), dtype=np.float32, order="F")
293+
else:
294+
raise ValueError(f"Unknown parametrization {type}")
295+
280296
# A Fortran-shaped buffer can only be accessed at PyBUF_STRIDES level or higher.
281-
mat = m.FortranMatrix(5, 4)
282297
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
283298
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
284-
assert info.len == mat.rows() * mat.cols() * info.itemsize
299+
assert info.len == 5 * 4 * info.itemsize
285300
assert info.ndim == 2
286301
assert info.shape == [5, 4]
287302
assert info.strides == [info.itemsize, 5 * info.itemsize]
288303
assert info.suboffsets is None
289304
assert not info.readonly
290305
info = m.get_py_buffer(mat, m.PyBUF_INDIRECT)
291306
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
292-
assert info.len == mat.rows() * mat.cols() * info.itemsize
307+
assert info.len == 5 * 4 * info.itemsize
293308
assert info.ndim == 2
294309
assert info.shape == [5, 4]
295310
assert info.strides == [info.itemsize, 5 * info.itemsize]
296311
assert info.suboffsets is None # Should be filled in here, but we don't use it.
297312
assert not info.readonly
298313

299-
mat = m.DiscontiguousMatrix(5, 4, 2, 3)
314+
315+
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
316+
def test_discontiguous_to_pybuffer(type):
317+
if type == "pybind11":
318+
mat = m.DiscontiguousMatrix(5, 4, 2, 3)
319+
elif type == "numpy":
320+
mat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3]
321+
else:
322+
raise ValueError(f"Unknown parametrization {type}")
323+
300324
info = m.get_py_buffer(mat, m.PyBUF_STRIDES)
301325
assert info.itemsize == ctypes.sizeof(ctypes.c_float)
302-
assert info.len == mat.rows() * mat.cols() * info.itemsize
326+
assert info.len == 5 * 4 * info.itemsize
303327
assert info.ndim == 2
304328
assert info.shape == [5, 4]
305329
assert info.strides == [2 * 4 * 3 * info.itemsize, 3 * info.itemsize]
306330
assert info.suboffsets is None
307331
assert not info.readonly
308332

309333

310-
def test_to_pybuffer_contiguity():
334+
@pytest.mark.parametrize("type", ["pybind11", "numpy"])
335+
def test_to_pybuffer_contiguity(type):
311336
def check_strides(mat):
312337
# The full block is memset to 0, so fill it with non-zero in real spots.
313-
expected = np.arange(1, mat.rows() * mat.cols() + 1).reshape(
314-
(mat.rows(), mat.cols())
315-
)
316-
for i in range(mat.rows()):
317-
for j in range(mat.cols()):
338+
expected = np.arange(1, 5 * 4 + 1).reshape((5, 4))
339+
for i in range(5):
340+
for j in range(4):
318341
mat[i, j] = expected[i, j]
319342
# If all strides are correct, the exposed buffer should match the input.
320343
np.testing.assert_array_equal(np.array(mat), expected)
321344

322-
mat = m.Matrix(5, 4)
323-
check_strides(mat)
345+
if type == "pybind11":
346+
cmat = m.Matrix(5, 4) # C contiguous.
347+
fmat = m.FortranMatrix(5, 4) # Fortran contiguous.
348+
dmat = m.DiscontiguousMatrix(5, 4, 2, 3) # Not contiguous.
349+
expected_exception = BufferError
350+
elif type == "numpy":
351+
cmat = np.empty((5, 4), dtype=np.float32) # C contiguous.
352+
fmat = np.empty((5, 4), dtype=np.float32, order="F") # Fortran contiguous.
353+
dmat = np.empty((5 * 2, 4 * 3), dtype=np.float32)[::2, ::3] # Not contiguous.
354+
# NumPy incorrectly raises ValueError; when the minimum NumPy requirement is
355+
# above the version that fixes https://github.com/numpy/numpy/issues/3634 then
356+
# BufferError can be used everywhere.
357+
expected_exception = (BufferError, ValueError)
358+
else:
359+
raise ValueError(f"Unknown parametrization {type}")
360+
361+
check_strides(cmat)
324362
# Should work in C-contiguous mode, but not Fortran order.
325-
m.get_py_buffer(mat, m.PyBUF_C_CONTIGUOUS)
326-
m.get_py_buffer(mat, m.PyBUF_ANY_CONTIGUOUS)
327-
with pytest.raises(BufferError):
328-
m.get_py_buffer(mat, m.PyBUF_F_CONTIGUOUS)
363+
m.get_py_buffer(cmat, m.PyBUF_C_CONTIGUOUS)
364+
m.get_py_buffer(cmat, m.PyBUF_ANY_CONTIGUOUS)
365+
with pytest.raises(expected_exception):
366+
m.get_py_buffer(cmat, m.PyBUF_F_CONTIGUOUS)
329367

330-
mat = m.FortranMatrix(5, 4)
331-
check_strides(mat)
368+
check_strides(fmat)
332369
# These flags imply C-contiguity, so won't work.
333-
with pytest.raises(BufferError):
334-
m.get_py_buffer(mat, m.PyBUF_SIMPLE)
335-
with pytest.raises(BufferError):
336-
m.get_py_buffer(mat, m.PyBUF_ND)
370+
with pytest.raises(expected_exception):
371+
m.get_py_buffer(fmat, m.PyBUF_SIMPLE)
372+
with pytest.raises(expected_exception):
373+
m.get_py_buffer(fmat, m.PyBUF_ND)
337374
# Should work in Fortran-contiguous mode, but not C order.
338-
with pytest.raises(BufferError):
339-
m.get_py_buffer(mat, m.PyBUF_C_CONTIGUOUS)
340-
m.get_py_buffer(mat, m.PyBUF_ANY_CONTIGUOUS)
341-
m.get_py_buffer(mat, m.PyBUF_F_CONTIGUOUS)
375+
with pytest.raises(expected_exception):
376+
m.get_py_buffer(fmat, m.PyBUF_C_CONTIGUOUS)
377+
m.get_py_buffer(fmat, m.PyBUF_ANY_CONTIGUOUS)
378+
m.get_py_buffer(fmat, m.PyBUF_F_CONTIGUOUS)
342379

343-
mat = m.DiscontiguousMatrix(5, 4, 2, 3)
344-
check_strides(mat)
380+
check_strides(dmat)
345381
# Should never work.
346-
with pytest.raises(BufferError):
347-
m.get_py_buffer(mat, m.PyBUF_SIMPLE)
348-
with pytest.raises(BufferError):
349-
m.get_py_buffer(mat, m.PyBUF_ND)
350-
with pytest.raises(BufferError):
351-
m.get_py_buffer(mat, m.PyBUF_C_CONTIGUOUS)
352-
with pytest.raises(BufferError):
353-
m.get_py_buffer(mat, m.PyBUF_ANY_CONTIGUOUS)
354-
with pytest.raises(BufferError):
355-
m.get_py_buffer(mat, m.PyBUF_F_CONTIGUOUS)
382+
with pytest.raises(expected_exception):
383+
m.get_py_buffer(dmat, m.PyBUF_SIMPLE)
384+
with pytest.raises(expected_exception):
385+
m.get_py_buffer(dmat, m.PyBUF_ND)
386+
with pytest.raises(expected_exception):
387+
m.get_py_buffer(dmat, m.PyBUF_C_CONTIGUOUS)
388+
with pytest.raises(expected_exception):
389+
m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
390+
with pytest.raises(expected_exception):
391+
m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)

0 commit comments

Comments
 (0)