1# -*- coding: utf-8 -*-
2import io
3import struct
4import ctypes
5
6import pytest
7
8import env  # noqa: F401
9
10from pybind11_tests import buffers as m
11from pybind11_tests import ConstructorStats
12
13np = pytest.importorskip("numpy")
14
15
16def test_from_python():
17    with pytest.raises(RuntimeError) as excinfo:
18        m.Matrix(np.array([1, 2, 3]))  # trying to assign a 1D array
19    assert str(excinfo.value) == "Incompatible buffer format!"
20
21    m3 = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32)
22    m4 = m.Matrix(m3)
23
24    for i in range(m4.rows()):
25        for j in range(m4.cols()):
26            assert m3[i, j] == m4[i, j]
27
28    cstats = ConstructorStats.get(m.Matrix)
29    assert cstats.alive() == 1
30    del m3, m4
31    assert cstats.alive() == 0
32    assert cstats.values() == ["2x3 matrix"]
33    assert cstats.copy_constructions == 0
34    # assert cstats.move_constructions >= 0  # Don't invoke any
35    assert cstats.copy_assignments == 0
36    assert cstats.move_assignments == 0
37
38
39# https://foss.heptapod.net/pypy/pypy/-/issues/2444
40def test_to_python():
41    mat = m.Matrix(5, 4)
42    assert memoryview(mat).shape == (5, 4)
43
44    assert mat[2, 3] == 0
45    mat[2, 3] = 4.0
46    mat[3, 2] = 7.0
47    assert mat[2, 3] == 4
48    assert mat[3, 2] == 7
49    assert struct.unpack_from("f", mat, (3 * 4 + 2) * 4) == (7,)
50    assert struct.unpack_from("f", mat, (2 * 4 + 3) * 4) == (4,)
51
52    mat2 = np.array(mat, copy=False)
53    assert mat2.shape == (5, 4)
54    assert abs(mat2).sum() == 11
55    assert mat2[2, 3] == 4 and mat2[3, 2] == 7
56    mat2[2, 3] = 5
57    assert mat2[2, 3] == 5
58
59    cstats = ConstructorStats.get(m.Matrix)
60    assert cstats.alive() == 1
61    del mat
62    pytest.gc_collect()
63    assert cstats.alive() == 1
64    del mat2  # holds a mat reference
65    pytest.gc_collect()
66    assert cstats.alive() == 0
67    assert cstats.values() == ["5x4 matrix"]
68    assert cstats.copy_constructions == 0
69    # assert cstats.move_constructions >= 0  # Don't invoke any
70    assert cstats.copy_assignments == 0
71    assert cstats.move_assignments == 0
72
73
74def test_inherited_protocol():
75    """SquareMatrix is derived from Matrix and inherits the buffer protocol"""
76
77    matrix = m.SquareMatrix(5)
78    assert memoryview(matrix).shape == (5, 5)
79    assert np.asarray(matrix).shape == (5, 5)
80
81
82def test_pointer_to_member_fn():
83    for cls in [m.Buffer, m.ConstBuffer, m.DerivedBuffer]:
84        buf = cls()
85        buf.value = 0x12345678
86        value = struct.unpack("i", bytearray(buf))[0]
87        assert value == 0x12345678
88
89
90def test_readonly_buffer():
91    buf = m.BufferReadOnly(0x64)
92    view = memoryview(buf)
93    assert view[0] == b"d" if env.PY2 else 0x64
94    assert view.readonly
95    with pytest.raises(TypeError):
96        view[0] = b"\0" if env.PY2 else 0
97
98
99def test_selective_readonly_buffer():
100    buf = m.BufferReadOnlySelect()
101
102    memoryview(buf)[0] = b"d" if env.PY2 else 0x64
103    assert buf.value == 0x64
104
105    io.BytesIO(b"A").readinto(buf)
106    assert buf.value == ord(b"A")
107
108    buf.readonly = True
109    with pytest.raises(TypeError):
110        memoryview(buf)[0] = b"\0" if env.PY2 else 0
111    with pytest.raises(TypeError):
112        io.BytesIO(b"1").readinto(buf)
113
114
115def test_ctypes_array_1d():
116    char1d = (ctypes.c_char * 10)()
117    int1d = (ctypes.c_int * 15)()
118    long1d = (ctypes.c_long * 7)()
119
120    for carray in (char1d, int1d, long1d):
121        info = m.get_buffer_info(carray)
122        assert info.itemsize == ctypes.sizeof(carray._type_)
123        assert info.size == len(carray)
124        assert info.ndim == 1
125        assert info.shape == [info.size]
126        assert info.strides == [info.itemsize]
127        assert not info.readonly
128
129
130def test_ctypes_array_2d():
131    char2d = ((ctypes.c_char * 10) * 4)()
132    int2d = ((ctypes.c_int * 15) * 3)()
133    long2d = ((ctypes.c_long * 7) * 2)()
134
135    for carray in (char2d, int2d, long2d):
136        info = m.get_buffer_info(carray)
137        assert info.itemsize == ctypes.sizeof(carray[0]._type_)
138        assert info.size == len(carray) * len(carray[0])
139        assert info.ndim == 2
140        assert info.shape == [len(carray), len(carray[0])]
141        assert info.strides == [info.itemsize * len(carray[0]), info.itemsize]
142        assert not info.readonly
143
144
145@pytest.mark.skipif(
146    "env.PYPY and env.PY2", reason="PyPy2 bytes buffer not reported as readonly"
147)
148def test_ctypes_from_buffer():
149    test_pystr = b"0123456789"
150    for pyarray in (test_pystr, bytearray(test_pystr)):
151        pyinfo = m.get_buffer_info(pyarray)
152
153        if pyinfo.readonly:
154            cbytes = (ctypes.c_char * len(pyarray)).from_buffer_copy(pyarray)
155            cinfo = m.get_buffer_info(cbytes)
156        else:
157            cbytes = (ctypes.c_char * len(pyarray)).from_buffer(pyarray)
158            cinfo = m.get_buffer_info(cbytes)
159
160        assert cinfo.size == pyinfo.size
161        assert cinfo.ndim == pyinfo.ndim
162        assert cinfo.shape == pyinfo.shape
163        assert cinfo.strides == pyinfo.strides
164        assert not cinfo.readonly
165