test_struct.py 24.3 KB
Newer Older
1
from test.test_support import TestFailed, verbose, verify, vereq
2
import test.test_support
3
import struct
4
import array
5
import warnings
6

7 8 9
import sys
ISBIGENDIAN = sys.byteorder == "big"
del sys
10
verify((struct.pack('=i', 1)[0] == 0) == ISBIGENDIAN,
11 12
       "bigendian determination appears wrong")

13 14 15 16 17
try:
    import _struct
except ImportError:
    PY_STRUCT_RANGE_CHECKING = 0
    PY_STRUCT_OVERFLOW_MASKING = 1
18
    PY_STRUCT_FLOAT_COERCE = 2
19
else:
20 21 22
    PY_STRUCT_RANGE_CHECKING = getattr(_struct, '_PY_STRUCT_RANGE_CHECKING', 0)
    PY_STRUCT_OVERFLOW_MASKING = getattr(_struct, '_PY_STRUCT_OVERFLOW_MASKING', 0)
    PY_STRUCT_FLOAT_COERCE = getattr(_struct, '_PY_STRUCT_FLOAT_COERCE', 0)
23

24
def string_reverse(s):
25
    return s[::-1]
26 27 28 29 30 31 32

def bigendian_to_native(value):
    if ISBIGENDIAN:
        return value
    else:
        return string_reverse(value)

33 34
def simple_err(func, *args):
    try:
35
        func(*args)
36
    except struct.error:
37
        pass
38
    else:
39 40
        raise TestFailed("%s%s did not raise struct.error" % (
            func.__name__, args))
41

42 43
def any_err(func, *args):
    try:
44
        func(*args)
45
    except (struct.error, TypeError):
46 47
        pass
    else:
48 49
        raise TestFailed("%s%s did not raise error" % (
            func.__name__, args))
50

51 52
def with_warning_restore(func):
    def _with_warning_restore(*args, **kw):
53
        with test.test_support.catch_warning():
54 55 56
            # Grrr, we need this function to warn every time.  Without removing
            # the warningregistry, running test_tarfile then test_struct would fail
            # on 64-bit platforms.
57
            globals = func.__globals__
58 59 60 61 62
            if '__warningregistry__' in globals:
                del globals['__warningregistry__']
            warnings.filterwarnings("error", r"""^struct.*""", DeprecationWarning)
            warnings.filterwarnings("error", r""".*format requires.*""",
                                    DeprecationWarning)
63 64 65
            return func(*args, **kw)
    return _with_warning_restore

66 67
def deprecated_err(func, *args):
    try:
68 69 70 71 72
        func(*args)
    except (struct.error, TypeError):
        pass
    except DeprecationWarning:
        if not PY_STRUCT_OVERFLOW_MASKING:
73 74
            raise TestFailed("%s%s expected to raise struct.error" % (
                func.__name__, args))
75
    else:
76 77
        raise TestFailed("%s%s did not raise error" % (
            func.__name__, args))
78 79
deprecated_err = with_warning_restore(deprecated_err)

80

81
simple_err(struct.calcsize, 'Z')
82 83

sz = struct.calcsize('i')
84
if sz * 3 != struct.calcsize('iii'):
85
    raise TestFailed('inconsistent sizes')
86

87 88
fmt = 'cbxxxxxxhhhhiillffdt'
fmt3 = '3c3b18x12h6i6l6f3d3t'
89 90
sz = struct.calcsize(fmt)
sz3 = struct.calcsize(fmt3)
91
if sz * 3 != sz3:
92 93
    raise TestFailed('inconsistent sizes (3*%r -> 3*%d = %d, %r -> %d)' % (
        fmt, sz, 3*sz, fmt3, sz3))
94 95 96 97

simple_err(struct.pack, 'iii', 3)
simple_err(struct.pack, 'i', 3, 3, 3)
simple_err(struct.pack, 'i', 'foo')
98
simple_err(struct.pack, 'P', 'foo')
99 100 101 102 103
simple_err(struct.unpack, 'd', 'flap')
s = struct.pack('ii', 1, 2)
simple_err(struct.unpack, 'iii', s)
simple_err(struct.unpack, 'i', s)

104
c = str8(b'a')
105
b = 1
106 107 108 109 110
h = 255
i = 65535
l = 65536
f = 3.1415
d = 3.1415
111
t = True
112

113
for prefix in ('', '@', '<', '>', '=', '!'):
114
    for format in ('xcbhilfdt', 'xcBHILfdt'):
115 116
        format = prefix + format
        if verbose:
117
            print("trying:", format)
118 119
        s = struct.pack(format, c, b, h, i, l, f, d, t)
        cp, bp, hp, ip, lp, fp, dp, tp = struct.unpack(format, s)
120
        if (cp != c or bp != b or hp != h or ip != i or lp != l or
121
            int(100 * fp) != int(100 * f) or int(100 * dp) != int(100 * d) or
122
                        tp != t):
123
            # ^^^ calculate only to two decimal places
124 125
            raise TestFailed("unpack/pack not transitive (%s, %s)" % (
                str(format), str((cp, bp, hp, ip, lp, fp, dp, tp))))
126

127
# Test some of the new features in detail
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

# (format, argument, big-endian result, little-endian result, asymmetric)
tests = [
    ('c', 'a', 'a', 'a', 0),
    ('xc', 'a', '\0a', '\0a', 0),
    ('cx', 'a', 'a\0', 'a\0', 0),
    ('s', 'a', 'a', 'a', 0),
    ('0s', 'helloworld', '', '', 1),
    ('1s', 'helloworld', 'h', 'h', 1),
    ('9s', 'helloworld', 'helloworl', 'helloworl', 1),
    ('10s', 'helloworld', 'helloworld', 'helloworld', 0),
    ('11s', 'helloworld', 'helloworld\0', 'helloworld\0', 1),
    ('20s', 'helloworld', 'helloworld'+10*'\0', 'helloworld'+10*'\0', 1),
    ('b', 7, '\7', '\7', 0),
    ('b', -7, '\371', '\371', 0),
    ('B', 7, '\7', '\7', 0),
    ('B', 249, '\371', '\371', 0),
    ('h', 700, '\002\274', '\274\002', 0),
    ('h', -700, '\375D', 'D\375', 0),
    ('H', 700, '\002\274', '\274\002', 0),
    ('H', 0x10000-700, '\375D', 'D\375', 0),
    ('i', 70000000, '\004,\035\200', '\200\035,\004', 0),
    ('i', -70000000, '\373\323\342\200', '\200\342\323\373', 0),
151 152
    ('I', 70000000, '\004,\035\200', '\200\035,\004', 0),
    ('I', 0x100000000-70000000, '\373\323\342\200', '\200\342\323\373', 0),
153 154
    ('l', 70000000, '\004,\035\200', '\200\035,\004', 0),
    ('l', -70000000, '\373\323\342\200', '\200\342\323\373', 0),
155 156
    ('L', 70000000, '\004,\035\200', '\200\035,\004', 0),
    ('L', 0x100000000-70000000, '\373\323\342\200', '\200\342\323\373', 0),
157 158 159 160 161 162
    ('f', 2.0, '@\000\000\000', '\000\000\000@', 0),
    ('d', 2.0, '@\000\000\000\000\000\000\000',
               '\000\000\000\000\000\000\000@', 0),
    ('f', -2.0, '\300\000\000\000', '\000\000\000\300', 0),
    ('d', -2.0, '\300\000\000\000\000\000\000\000',
               '\000\000\000\000\000\000\000\300', 0),
163 164 165 166 167
        ('t', 0, '\0', '\0', 0),
        ('t', 3, '\1', '\1', 1),
        ('t', True, '\1', '\1', 0),
        ('t', [], '\0', '\0', 1),
        ('t', (1,), '\1', '\1', 1),
168 169 170
]

for fmt, arg, big, lil, asy in tests:
171 172
    big = bytes(big, "latin-1")
    lil = bytes(lil, "latin-1")
173
    if verbose:
174
        print("%r %r %r %r" % (fmt, arg, big, lil))
175
    for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil),
176
                        ('='+fmt, ISBIGENDIAN and big or lil)]:
177 178
        res = struct.pack(xfmt, arg)
        if res != exp:
179 180
            raise TestFailed("pack(%r, %r) -> %r # expected %r" % (
                fmt, arg, res, exp))
181 182
        n = struct.calcsize(xfmt)
        if n != len(res):
183 184
            raise TestFailed("calcsize(%r) -> %d # expected %d" % (
                xfmt, n, len(res)))
185
        rev = struct.unpack(xfmt, res)[0]
186 187 188
        if isinstance(arg, str):
            # Strings are returned as str8 since you can't know the encoding of
            # the string when packed.
189
            arg = str8(arg, 'latin1')
190
        if rev != arg and not asy:
191 192
            raise TestFailed("unpack(%r, %r) -> (%r,) # expected (%r,)" % (
                fmt, res, rev, arg))
193

194
###########################################################################
195
# Simple native q/Q tests.
196 197 198 199 200 201 202 203

has_native_qQ = 1
try:
    struct.pack("q", 5)
except struct.error:
    has_native_qQ = 0

if verbose:
204
    print("Platform has native q/Q?", has_native_qQ and "Yes." or "No.")
205

206
any_err(struct.pack, "Q", -1)   # can't pack -1 as unsigned regardless
207 208 209
simple_err(struct.pack, "q", "a")  # can't pack string as 'q' regardless
simple_err(struct.pack, "Q", "a")  # ditto, but 'Q'

210
def test_native_qQ():
211
    nbytes = struct.calcsize('q')
212 213 214 215
    # The expected values here are in big-endian format, primarily because
    # I'm on a little-endian machine and so this is the clearest way (for
    # me) to force the code to get exercised.
    for format, input, expected in (
216 217 218 219 220 221 222
            ('q', -1, '\xff' * nbytes),
            ('q', 0, '\x00' * nbytes),
            ('Q', 0, '\x00' * nbytes),
            ('q', 1, '\x00' * (nbytes-1) + '\x01'),
            ('Q', (1 << (8*nbytes))-1, '\xff' * nbytes),
            ('q', (1 << (8*nbytes-1))-1, '\x7f' + '\xff' * (nbytes - 1))):
        expected = bytes(expected, "latin-1")
223
        got = struct.pack(format, input)
224 225
        native_expected = bigendian_to_native(expected)
        verify(got == native_expected,
226
               "%r-pack of %r gave %r, not %r" %
227
                    (format, input, got, native_expected))
228 229 230 231
        retrieved = struct.unpack(format, got)[0]
        verify(retrieved == input,
               "%r-unpack of %r gave %r, not %r" %
                    (format, got, retrieved, input))
232 233 234 235

if has_native_qQ:
    test_native_qQ()

236 237
###########################################################################
# Standard integer tests (bBhHiIlLqQ).
238 239 240

import binascii

241 242 243
class IntTester:

    # XXX Most std integer modes fail to test for out-of-range.
244 245 246 247
    # The "i" and "l" codes appear to range-check OK on 32-bit boxes, but
    # fail to check correctly on some 64-bit ones (Tru64 Unix + Compaq C
    # reported by Mark Favas).
    BUGGY_RANGE_CHECK = "bBhHiIlL"
248 249 250 251 252 253 254 255 256 257 258 259

    def __init__(self, formatpair, bytesize):
        assert len(formatpair) == 2
        self.formatpair = formatpair
        for direction in "<>!=":
            for code in formatpair:
                format = direction + code
                verify(struct.calcsize(format) == bytesize)
        self.bytesize = bytesize
        self.bitsize = bytesize * 8
        self.signed_code, self.unsigned_code = formatpair
        self.unsigned_min = 0
260 261 262
        self.unsigned_max = 2**self.bitsize - 1
        self.signed_min = -(2**(self.bitsize-1))
        self.signed_max = 2**(self.bitsize-1) - 1
263 264 265 266 267

    def test_one(self, x, pack=struct.pack,
                          unpack=struct.unpack,
                          unhexlify=binascii.unhexlify):
        if verbose:
268
            print("trying std", self.formatpair, "on", x, "==", hex(x))
269 270 271 272 273

        # Try signed.
        code = self.signed_code
        if self.signed_min <= x <= self.signed_max:
            # Try big-endian.
274
            expected = int(x)
275
            if x < 0:
276
                expected += 1 << self.bitsize
277
                assert expected > 0
278
            expected = hex(expected)[2:] # chop "0x"
279 280 281
            if len(expected) & 1:
                expected = "0" + expected
            expected = unhexlify(expected)
282
            expected = b"\x00" * (self.bytesize - len(expected)) + expected
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

            # Pack work?
            format = ">" + code
            got = pack(format, x)
            verify(got == expected,
                   "'%s'-pack of %r gave %r, not %r" %
                    (format, x, got, expected))

            # Unpack work?
            retrieved = unpack(format, got)[0]
            verify(x == retrieved,
                   "'%s'-unpack of %r gave %r, not %r" %
                    (format, got, retrieved, x))

            # Adding any byte should cause a "too big" error.
298
            any_err(unpack, format, b'\x01' + got)
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316

            # Try little-endian.
            format = "<" + code
            expected = string_reverse(expected)

            # Pack work?
            got = pack(format, x)
            verify(got == expected,
                   "'%s'-pack of %r gave %r, not %r" %
                    (format, x, got, expected))

            # Unpack work?
            retrieved = unpack(format, got)[0]
            verify(x == retrieved,
                   "'%s'-unpack of %r gave %r, not %r" %
                    (format, got, retrieved, x))

            # Adding any byte should cause a "too big" error.
317
            any_err(unpack, format, b'\x01' + got)
318 319 320

        else:
            # x is out of range -- verify pack realizes that.
321
            if not PY_STRUCT_RANGE_CHECKING and code in self.BUGGY_RANGE_CHECK:
322
                if verbose:
323
                    print("Skipping buggy range check for code", code)
324
            else:
325 326
                deprecated_err(pack, ">" + code, x)
                deprecated_err(pack, "<" + code, x)
327 328 329 330 331 332

        # Much the same for unsigned.
        code = self.unsigned_code
        if self.unsigned_min <= x <= self.unsigned_max:
            # Try big-endian.
            format = ">" + code
333
            expected = int(x)
334
            expected = hex(expected)[2:] # chop "0x"
335 336 337
            if len(expected) & 1:
                expected = "0" + expected
            expected = unhexlify(expected)
338
            expected = b"\x00" * (self.bytesize - len(expected)) + expected
339 340 341 342 343 344 345 346 347 348 349 350 351 352

            # Pack work?
            got = pack(format, x)
            verify(got == expected,
                   "'%s'-pack of %r gave %r, not %r" %
                    (format, x, got, expected))

            # Unpack work?
            retrieved = unpack(format, got)[0]
            verify(x == retrieved,
                   "'%s'-unpack of %r gave %r, not %r" %
                    (format, got, retrieved, x))

            # Adding any byte should cause a "too big" error.
353
            any_err(unpack, format, b'\x01' + got)
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

            # Try little-endian.
            format = "<" + code
            expected = string_reverse(expected)

            # Pack work?
            got = pack(format, x)
            verify(got == expected,
                   "'%s'-pack of %r gave %r, not %r" %
                    (format, x, got, expected))

            # Unpack work?
            retrieved = unpack(format, got)[0]
            verify(x == retrieved,
                   "'%s'-unpack of %r gave %r, not %r" %
                    (format, got, retrieved, x))

            # Adding any byte should cause a "too big" error.
372
            any_err(unpack, format, b'\x01' + got)
373 374 375

        else:
            # x is out of range -- verify pack realizes that.
376
            if not PY_STRUCT_RANGE_CHECKING and code in self.BUGGY_RANGE_CHECK:
377
                if verbose:
378
                    print("Skipping buggy range check for code", code)
379
            else:
380 381
                deprecated_err(pack, ">" + code, x)
                deprecated_err(pack, "<" + code, x)
382 383 384 385 386 387 388

    def run(self):
        from random import randrange

        # Create all interesting powers of 2.
        values = []
        for exp in range(self.bitsize + 3):
389
            values.append(1 << exp)
390 391 392

        # Add some random values.
        for i in range(self.bitsize):
393
            val = 0
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
            for j in range(self.bytesize):
                val = (val << 8) | randrange(256)
            values.append(val)

        # Try all those, and their negations, and +-1 from them.  Note
        # that this tests all power-of-2 boundaries in range, and a few out
        # of range, plus +-(2**n +- 1).
        for base in values:
            for val in -base, base:
                for incr in -1, 0, 1:
                    x = val + incr
                    try:
                        x = int(x)
                    except OverflowError:
                        pass
                    self.test_one(x)

        # Some error cases.
        for direction in "<>":
            for code in self.formatpair:
                for badobject in "a string", 3+42j, randrange:
                    any_err(struct.pack, direction + code, badobject)

for args in [("bB", 1),
             ("hH", 2),
             ("iI", 4),
             ("lL", 4),
             ("qQ", 8)]:
    t = IntTester(*args)
    t.run()
424 425 426 427 428 429 430


###########################################################################
# The p ("Pascal string") code.

def test_p_code():
    for code, input, expected, expectedback in [
431 432 433 434 435 436 437 438
            ('p','abc', '\x00', str8()),
            ('1p', 'abc', '\x00', str8()),
            ('2p', 'abc', '\x01a', str8(b'a')),
            ('3p', 'abc', '\x02ab', str8(b'ab')),
            ('4p', 'abc', '\x03abc', str8(b'abc')),
            ('5p', 'abc', '\x03abc\x00', str8(b'abc')),
            ('6p', 'abc', '\x03abc\x00\x00', str8(b'abc')),
            ('1000p', 'x'*1000, '\xff' + 'x'*999, str8(b'x'*255))]:
439
        expected = bytes(expected, "latin-1")
440 441 442 443 444 445 446 447 448 449
        got = struct.pack(code, input)
        if got != expected:
            raise TestFailed("pack(%r, %r) == %r but expected %r" %
                             (code, input, got, expected))
        (got,) = struct.unpack(code, got)
        if got != expectedback:
            raise TestFailed("unpack(%r, %r) == %r but expected %r" %
                             (code, input, got, expectedback))

test_p_code()
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495


###########################################################################
# SF bug 705836.  "<f" and ">f" had a severe rounding bug, where a carry
# from the low-order discarded bits could propagate into the exponent
# field, causing the result to be wrong by a factor of 2.

def test_705836():
    import math

    for base in range(1, 33):
        # smaller <- largest representable float less than base.
        delta = 0.5
        while base - delta / 2.0 != base:
            delta /= 2.0
        smaller = base - delta
        # Packing this rounds away a solid string of trailing 1 bits.
        packed = struct.pack("<f", smaller)
        unpacked = struct.unpack("<f", packed)[0]
        # This failed at base = 2, 4, and 32, with unpacked = 1, 2, and
        # 16, respectively.
        verify(base == unpacked)
        bigpacked = struct.pack(">f", smaller)
        verify(bigpacked == string_reverse(packed),
               ">f pack should be byte-reversal of <f pack")
        unpacked = struct.unpack(">f", bigpacked)[0]
        verify(base == unpacked)

    # Largest finite IEEE single.
    big = (1 << 24) - 1
    big = math.ldexp(big, 127 - 23)
    packed = struct.pack(">f", big)
    unpacked = struct.unpack(">f", packed)[0]
    verify(big == unpacked)

    # The same, but tack on a 1 bit so it rounds up to infinity.
    big = (1 << 25) - 1
    big = math.ldexp(big, 127 - 24)
    try:
        packed = struct.pack(">f", big)
    except OverflowError:
        pass
    else:
        TestFailed("expected OverflowError")

test_705836()
496

497 498 499
###########################################################################
# SF bug 1229380. No struct.pack exception for some out of range integers

500 501 502
def test_1229380():
    import sys
    for endian in ('', '>', '<'):
503 504
        for fmt in ('B', 'H', 'I', 'L'):
            deprecated_err(struct.pack, endian + fmt, -1)
505

506 507
        deprecated_err(struct.pack, endian + 'B', 300)
        deprecated_err(struct.pack, endian + 'H', 70000)
508

509 510
        deprecated_err(struct.pack, endian + 'I', sys.maxint * 4)
        deprecated_err(struct.pack, endian + 'L', sys.maxint * 4)
511 512 513 514

if PY_STRUCT_RANGE_CHECKING:
    test_1229380()

515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
###########################################################################
# SF bug 1530559. struct.pack raises TypeError where it used to convert.

def check_float_coerce(format, number):
    if PY_STRUCT_FLOAT_COERCE == 2:
        # Test for pre-2.5 struct module
        packed = struct.pack(format, number)
        floored = struct.unpack(format, packed)[0]
        if floored != int(number):
            raise TestFailed("did not correcly coerce float to int")
        return
    try:
        func(*args)
    except (struct.error, TypeError):
        if PY_STRUCT_FLOAT_COERCE:
            raise TestFailed("expected DeprecationWarning for float coerce")
    except DeprecationWarning:
        if not PY_STRUCT_FLOAT_COERCE:
            raise TestFailed("expected to raise struct.error for float coerce")
    else:
        raise TestFailed("did not raise error for float coerce")

check_float_coerce = with_warning_restore(deprecated_err)

def test_1530559():
    for endian in ('', '>', '<'):
        for fmt in ('B', 'H', 'I', 'L', 'b', 'h', 'i', 'l'):
            check_float_coerce(endian + fmt, 1.0)
            check_float_coerce(endian + fmt, 1.5)

test_1530559()
546 547

###########################################################################
548
# Packing and unpacking to/from memory views.
549 550 551 552 553 554 555 556

# Copied and modified from unittest.
def assertRaises(excClass, callableObj, *args, **kwargs):
    try:
        callableObj(*args, **kwargs)
    except excClass:
        return
    else:
557
        raise TestFailed("%s not raised." % excClass)
558 559

def test_unpack_from():
560
    test_string = b'abcd01234'
561 562
    fmt = '4s'
    s = struct.Struct(fmt)
563
    for cls in (str, str8, bytes): # XXX + memoryview
564 565
        if verbose:
            print("test_unpack_from using", cls.__name__)
566
        data = cls(test_string)
567 568 569 570 571 572 573
        if not isinstance(data, (str8, bytes)):
            bytes_data = str8(data, 'latin1')
        else:
            bytes_data = data
        vereq(s.unpack_from(data), (str8(b'abcd'),))
        vereq(s.unpack_from(data, 2), (str8(b'cd01'),))
        vereq(s.unpack_from(data, 4), (str8(b'0123'),))
574
        for i in range(6):
575
            vereq(s.unpack_from(data, i), (bytes_data[i:i+4],))
576
        for i in range(6, len(test_string) + 1):
577
            simple_err(s.unpack_from, data, i)
578
    for cls in (str, str8, bytes): # XXX + memoryview
579
        data = cls(test_string)
580 581 582
        vereq(struct.unpack_from(fmt, data), (str8(b'abcd'),))
        vereq(struct.unpack_from(fmt, data, 2), (str8(b'cd01'),))
        vereq(struct.unpack_from(fmt, data, 4), (str8(b'0123'),))
583
        for i in range(6):
584
            vereq(struct.unpack_from(fmt, data, i), (bytes_data[i:i+4],))
585
        for i in range(6, len(test_string) + 1):
586 587
            simple_err(struct.unpack_from, fmt, data, i)

588
def test_pack_into():
589 590
    test_string = b'Reykjavik rocks, eow!'
    writable_buf = array.array('b', b' '*100)
591 592 593 594
    fmt = '21s'
    s = struct.Struct(fmt)

    # Test without offset
595
    s.pack_into(writable_buf, 0, test_string)
596
    from_buf = writable_buf.tostring()[:len(test_string)]
597
    vereq(from_buf, test_string)
598 599

    # Test with offset.
600
    s.pack_into(writable_buf, 10, test_string)
601
    from_buf = writable_buf.tostring()[:len(test_string)+10]
602
    vereq(from_buf, test_string[:10] + test_string)
603 604

    # Go beyond boundaries.
605
    small_buf = array.array('b', b' '*10)
606 607
    assertRaises(struct.error, s.pack_into, small_buf, 0, test_string)
    assertRaises(struct.error, s.pack_into, small_buf, 2, test_string)
608

609
def test_pack_into_fn():
610 611
    test_string = b'Reykjavik rocks, eow!'
    writable_buf = array.array('b', b' '*100)
612
    fmt = '21s'
613
    pack_into = lambda *args: struct.pack_into(fmt, *args)
614

615 616
    # Test without offset.
    pack_into(writable_buf, 0, test_string)
617
    from_buf = writable_buf.tostring()[:len(test_string)]
618
    vereq(from_buf, test_string)
619 620

    # Test with offset.
621
    pack_into(writable_buf, 10, test_string)
622
    from_buf = writable_buf.tostring()[:len(test_string)+10]
623
    vereq(from_buf, test_string[:10] + test_string)
624 625

    # Go beyond boundaries.
626
    small_buf = array.array('b', b' '*10)
627 628 629
    assertRaises(struct.error, pack_into, small_buf, 0, test_string)
    assertRaises(struct.error, pack_into, small_buf, 2, test_string)

630
def test_unpack_with_memoryview():
631
    # SF bug 1563759: struct.unpack doens't support buffer protocol objects
632
    data1 = array.array('B', b'\x12\x34\x56\x78')
633
    data2 = memoryview(b'\x12\x34\x56\x78') # XXX b'......XXXX......', 6, 4
634 635 636
    for data in [data1, data2]:
        value, = struct.unpack('>I', data)
        vereq(value, 0x12345678)
637

638
# Test methods to pack and unpack from memoryviews rather than strings.
639
test_unpack_from()
640 641
test_pack_into()
test_pack_into_fn()
642
test_unpack_with_memoryview()
643 644

def test_bool():
645 646 647
    for prefix in tuple("<>!=")+('',):
        false = (), [], [], '', 0
        true = [1], 'test', 5, -1, 0xffffffff+1, 0xffffffff/2
648

649 650
        falseFormat = prefix + 't' * len(false)
        if verbose:
651
            print('trying bool pack/unpack on', false, 'using format', falseFormat)
652 653
        packedFalse = struct.pack(falseFormat, *false)
        unpackedFalse = struct.unpack(falseFormat, packedFalse)
654

655 656
        trueFormat = prefix + 't' * len(true)
        if verbose:
657
            print('trying bool pack/unpack on', true, 'using format', trueFormat)
658 659
        packedTrue = struct.pack(trueFormat, *true)
        unpackedTrue = struct.unpack(trueFormat, packedTrue)
660

661 662 663 664
        if len(true) != len(unpackedTrue):
            raise TestFailed('unpacked true array is not of same size as input')
        if len(false) != len(unpackedFalse):
            raise TestFailed('unpacked false array is not of same size as input')
665

666 667 668 669 670 671
        for t in unpackedFalse:
            if t is not False:
                raise TestFailed('%r did not unpack as False' % t)
        for t in unpackedTrue:
            if t is not True:
                raise TestFailed('%r did not unpack as false' % t)
672

673
        if prefix and verbose:
674
            print('trying size of bool with format %r' % (prefix+'t'))
675
        packed = struct.pack(prefix+'t', 1)
676

677 678
        if len(packed) != struct.calcsize(prefix+'t'):
            raise TestFailed('packed length is not equal to calculated size')
679

680 681 682
        if len(packed) != 1 and prefix:
            raise TestFailed('encoded bool is not one byte: %r' % packed)
        elif not prefix and verbose:
683
            print('size of bool in native format is %i' % (len(packed)))
684

685 686
        for c in b'\x01\x7f\xff\x0f\xf0':
            if struct.unpack('>t', bytes([c]))[0] is not True:
687
                raise TestFailed('%c did not unpack as True' % c)
688 689

test_bool()