buffmt.pyx 9.68 KB
Newer Older
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
1 2
from __future__ import unicode_literals

3 4 5 6 7 8 9
# Tests buffer format string parsing.

__test__ = {}
def testcase(func):
    __test__[func.__name__] = func.__doc__
    return func

10
from libc cimport stdlib
11 12 13 14 15 16 17 18 19 20 21

def little_endian():
    cdef unsigned int n = 1
    return (<unsigned char*>&n)[0] != 0

if little_endian():
    current_endian = '<'
    other_endian = '>'
else:
    current_endian = '>'
    other_endian = '<'
22

Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
23
cdef struct align_of_float_helper:
24
    char ch
25
    float d
26 27 28
cdef struct align_of_int_helper:
    char ch
    int i
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
29
float_align = sizeof(align_of_float_helper) - sizeof(float)
30
int_align = sizeof(align_of_int_helper) - sizeof(int)
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
31 32
if float_align != 4 or sizeof(float) != 4:
    raise RuntimeError("Alignment or size of float is %d on this system, please report to cython-dev for a testcase fix" % float_align)
33 34
if int_align != 4 or sizeof(int) != 4:
    raise RuntimeError("Alignment or size of int is %d on this system, please report to cython-dev for a testcase fix" % int_align)
35

36

37 38 39 40 41
cdef class MockBuffer:
    cdef Py_ssize_t zero
    cdef Py_ssize_t minusone
    cdef object format
    cdef object itemsize
42

43
    def __init__(self, format, itemsize):
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
44
        self.format = unicode(format).encode(u"ASCII")
45 46 47
        self.itemsize = itemsize
        self.zero = 0
        self.minusone = -1
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
    def __getbuffer__(self, Py_buffer* info, int flags):
        info.buf = NULL
        info.strides = &self.zero
        info.suboffsets = &self.minusone
        info.shape = &self.zero
        info.ndim = 1
        info.format = self.format
        info.itemsize = self.itemsize

@testcase
def _int(fmt):
    """
    >>> _int("i")

    >>> _int("b")
    Traceback (most recent call last):
       ...
66
    ValueError: Buffer dtype mismatch, expected 'int' but got 'signed char'
67

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    >>> _int("if")
    Traceback (most recent call last):
       ...
    ValueError: Buffer dtype mismatch, expected end but got 'float'

    >>> _int("$$")
    Traceback (most recent call last):
       ...
    ValueError: Does not understand character buffer dtype format string ('$')
    """
    cdef object[int] buf = MockBuffer(fmt, sizeof(int))

@testcase
def _ulong(fmt):
    """
    >>> _ulong("L")
    """
    cdef object[unsigned long] buf = MockBuffer(fmt, sizeof(unsigned long))

@testcase
def wrongsize():
    """
    >>> wrongsize()
    Traceback (most recent call last):
       ...
93
    ValueError: Item size of buffer (1 byte) does not match size of 'float' (4 bytes)
94

95
    """
96
    cdef object[float] buf = MockBuffer("f", 1)
97 98 99 100 101 102 103 104 105 106 107 108 109

@testcase
def _obj(fmt):
    """
    >>> _obj("O")
    >>> _obj("i")
    Traceback (most recent call last):
       ...
    ValueError: Buffer dtype mismatch, expected 'Python object' but got 'int'
    """
    cdef object[object] buf = MockBuffer(fmt, sizeof(void*))


Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
110 111 112
cdef struct ComplexFloat:
    float real
    float imag
113 114 115 116 117 118 119

ctypedef struct Char3Int:
    char a
    int b
    int c
    int d

Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
120
cdef struct CharIntCFloat:
121 122
    char a
    int b
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
123 124
    ComplexFloat c
    float d
125 126 127 128

cdef struct UnpackedStruct1:
    char a
    int b
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
129 130
    ComplexFloat c
    float c2
131 132 133
    Char3Int d

ctypedef struct UnpackedStruct2:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
134
    CharIntCFloat a
135 136 137
    Char3Int b

ctypedef struct UnpackedStruct3:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
138
    CharIntCFloat a
139 140 141 142 143 144
    char b
    int c, d, e

cdef struct UnpackedStruct4:
    char a
    int b
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
145 146
    ComplexFloat c
    float c2
147 148 149 150 151 152 153
    char d
    int e, f, g

@testcase
def char3int(fmt):
    """
    >>> char3int("ciii")
154
    >>> char3int("c1i1i1i")
155 156
    >>> char3int("c3i")
    >>> char3int("ci2i")
157

158
    >>> char3int("c@i@2i")
159

160 161 162 163 164 165 166 167 168 169 170
    Extra pad bytes (assuming int size is 4 or more)
    >>> char3int("cxiii")
    >>> char3int("c3xiii")
    >>> char3int("cxxxiii")

    Standard alignment (assming int size is 4)
    >>> char3int("=c3xiii")
    >>> char3int("=ciii")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 1 but 4 expected
171

172
    >>> char3int("=cxxx@iii")
173

174
    Error:
175 176 177 178 179
    >>> char3int("cii")
    Traceback (most recent call last):
       ...
    ValueError: Buffer dtype mismatch, expected 'int' but got end in 'Char3Int.d'
    """
180
    cdef object obj = MockBuffer(fmt, sizeof(Char3Int))
181 182
    cdef object[Char3Int, ndim=1] buf = obj

Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
183
@testcase
184 185 186
def unpacked_struct(fmt):
    """
    Native formats:
187 188 189 190 191 192 193
    >>> unpacked_struct("ciZffciii")
    >>> unpacked_struct("@ci3fc3i")
    >>> unpacked_struct("@ciZffci2i")
    >>> unpacked_struct("ciZffT{ciii}")
    >>> unpacked_struct("cT{ifffc2i}i")
    >>> unpacked_struct("ciZffc3T{i}")
    >>> unpacked_struct("T{c}T{T{iZffT{ci}}}2T{T{i}}")
194 195 196 197
    """

    assert (sizeof(UnpackedStruct1) == sizeof(UnpackedStruct2)
            == sizeof(UnpackedStruct3) == sizeof(UnpackedStruct4))
198
    cdef object obj = MockBuffer(fmt, sizeof(UnpackedStruct1))
199 200 201 202 203 204
    cdef object[UnpackedStruct1, ndim=1] buf1 = obj
    cdef object[UnpackedStruct2, ndim=1] buf2 = obj
    cdef object[UnpackedStruct3, ndim=1] buf3 = obj
    cdef object[UnpackedStruct4, ndim=1] buf4 = obj

cdef struct ComplexTest:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
205
    ComplexFloat a, b, c
206 207 208 209

@testcase
def complex_test(fmt):
    """
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
210 211 212 213
    >>> complex_test("ZfZfZf")
    >>> complex_test("3Zf")
    >>> complex_test("6f")
    >>> complex_test("3T{Zf}")
214

Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
215
    >>> complex_test("fZfZff")
216 217
    Traceback (most recent call last):
       ...
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
218
    ValueError: Buffer dtype mismatch, expected 'float' but got 'complex float' in 'ComplexFloat.imag'
219

220
    """
221
    cdef object obj = MockBuffer(fmt, sizeof(ComplexTest))
222
    cdef object[ComplexTest] buf1 = obj
223

224 225 226 227 228 229 230 231 232 233 234 235 236

@testcase
def alignment_string(fmt, exc=None):
    """
    >>> alignment_string("@i")
    >>> alignment_string("%si" % current_endian)
    >>> alignment_string("%si" % other_endian, "X-endian buffer not supported on X-endian compiler")
    >>> alignment_string("=i")
    """
    cdef object[int] buf
    try:
        buf = MockBuffer(fmt, sizeof(int))
    except ValueError, e:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
237
        msg = unicode(e).replace("Big", "X").replace("Little", "X").replace("big", "X").replace("little", "X")
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
        if msg != exc:
            print msg
            print "  is not equal to"
            print exc
        return
    if exc:
        print "fail"


@testcase
def int_and_long_are_same():
    """
    >>> int_and_long_are_same()
    """
    cdef object[int] intarr
    cdef object[long] longarr
    if sizeof(int) == sizeof(long):
        intarr = MockBuffer("l", sizeof(int))
        longarr = MockBuffer("i", sizeof(int))

cdef struct MixedComplex:
259
    double real
260 261 262 263 264 265
    float imag

@testcase
def mixed_complex_struct():
    """
    Triggering a specific execution path for this case.
266

267 268 269
    >>> mixed_complex_struct()
    Traceback (most recent call last):
        ...
270
    ValueError: Buffer dtype mismatch, expected 'double' but got 'complex double' in 'MixedComplex.real'
271
    """
272 273
    cdef object[MixedComplex] buf = MockBuffer("Zd",
        sizeof(MixedComplex))
274

275 276 277 278 279

cdef packed struct PackedSubStruct:
    char x
    int y

280 281 282 283
cdef struct UnpackedSubStruct:
    char x
    int y

284 285 286 287
cdef packed struct PackedStruct:
    char a
    int b
    PackedSubStruct sub
288

289 290 291 292 293 294 295 296 297 298
cdef struct PartiallyPackedStruct:
    char a
    int b
    PackedSubStruct sub

cdef packed struct PartiallyPackedStruct2:
    char a
    UnpackedSubStruct sub
    char b
    int c
299 300 301 302 303

@testcase
def packed_struct(fmt):
    """
    Assuming int is four bytes:
304

305
    >>> packed_struct("^cici")
306
    >>> packed_struct("=cici")
307

308 309
    However aligned access won't work:

310 311 312
    >>> packed_struct("^c@i^ci")
    Traceback (most recent call last):
        ...
313
    ValueError: Buffer dtype mismatch; next field is at offset 4 but 1 expected
314

315 316 317 318 319 320 321 322
    >>> packed_struct("@cici")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 4 but 1 expected

    """
    cdef object[PackedStruct] buf = MockBuffer(fmt, sizeof(PackedStruct))

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
@testcase
def partially_packed_struct(fmt):
    """
    Assuming int is four bytes:

    >>> partially_packed_struct("^c@i^ci")
    >>> partially_packed_struct("@ci^ci")
    >>> partially_packed_struct("^c@i=ci")
    >>> partially_packed_struct("@ci=ci")
    >>> partially_packed_struct("ci^ci")
    >>> partially_packed_struct("ci=ci")

    Incorrectly aligned accesses won't work:

    >>> partially_packed_struct("^cici")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 1 but 4 expected

    >>> partially_packed_struct("=cibi")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 1 but 4 expected

    """
    cdef object[PartiallyPackedStruct] buf = MockBuffer(
        fmt, sizeof(PartiallyPackedStruct))

@testcase
def partially_packed_struct_2(fmt):
    """
    Assuming int is four bytes:

    >>> partially_packed_struct_2("^ccxxxici")
    >>> partially_packed_struct_2("^ccxxxi^ci")
    >>> partially_packed_struct_2("c=cxxxi^ci")
    >>> partially_packed_struct_2("c^cxxxi^ci")
    >>> partially_packed_struct_2("c^cxxxi=ci")
    >>> partially_packed_struct_2("ccxxx^i@c^i")

    Incorrectly aligned accesses won't work:

    >>> partially_packed_struct_2("ccxxxici")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 8 but 5 expected
    
    >>> partially_packed_struct_2("ccici")
    Traceback (most recent call last):
        ...
    ValueError: Buffer dtype mismatch; next field is at offset 4 but 5 expected

    """
    cdef object[PartiallyPackedStruct2] buf = MockBuffer(
        fmt, sizeof(PartiallyPackedStruct2))


380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
cdef packed struct PackedStructWithCharArrays:
    float a
    int b
    char[5] c
    char[3] d


@testcase
def packed_struct_with_strings(fmt):
    """
    >>> packed_struct_with_strings("T{f:a:i:b:5s:c:3s:d:}")
    """
    cdef object[PackedStructWithCharArrays] buf = MockBuffer(
        fmt, sizeof(PackedStructWithCharArrays))


396 397
# TODO: empty struct
# TODO: Incomplete structs
398
# TODO: mixed structs