from test
.test_support
import TestFailed
, verbose
, verify
ISBIGENDIAN
= sys
.byteorder
== "big"
verify((struct
.pack('=i', 1)[0] == chr(0)) == ISBIGENDIAN
,
"bigendian determination appears wrong")
def bigendian_to_native(value
):
return string_reverse(value
)
def simple_err(func
, *args
):
raise TestFailed
, "%s%s did not raise struct.error" % (
def any_err(func
, *args
):
except (struct
.error
, OverflowError, TypeError):
raise TestFailed
, "%s%s did not raise error" % (
simple_err(struct
.calcsize
, 'Z')
sz
= struct
.calcsize('i')
if sz
* 3 != struct
.calcsize('iii'):
raise TestFailed
, 'inconsistent sizes'
fmt
= 'cbxxxxxxhhhhiillffd'
fmt3
= '3c3b18x12h6i6l6f3d'
sz
= struct
.calcsize(fmt
)
sz3
= struct
.calcsize(fmt3
)
raise TestFailed
, 'inconsistent sizes (3*%r -> 3*%d = %d, %r -> %d)' % (
fmt
, sz
, 3*sz
, fmt3
, sz3
)
simple_err(struct
.pack
, 'iii', 3)
simple_err(struct
.pack
, 'i', 3, 3, 3)
simple_err(struct
.pack
, 'i', 'foo')
simple_err(struct
.pack
, 'P', 'foo')
simple_err(struct
.unpack
, 'd', 'flap')
s
= struct
.pack('ii', 1, 2)
simple_err(struct
.unpack
, 'iii', s
)
simple_err(struct
.unpack
, 'i', s
)
for prefix
in ('', '@', '<', '>', '=', '!'):
for format
in ('xcbhilfd', 'xcBHILfd'):
s
= struct
.pack(format
, c
, b
, h
, i
, l
, f
, d
)
cp
, bp
, hp
, ip
, lp
, fp
, dp
= struct
.unpack(format
, s
)
if (cp
!= c
or bp
!= b
or hp
!= h
or ip
!= i
or lp
!= l
or
int(100 * fp
) != int(100 * f
) or int(100 * dp
) != int(100 * d
)):
# ^^^ calculate only to two decimal places
raise TestFailed
, "unpack/pack not transitive (%s, %s)" % (
str(format
), str((cp
, bp
, hp
, ip
, lp
, fp
, dp
)))
# Test some of the new features in detail
# (format, argument, big-endian result, little-endian result, asymmetric)
('xc', 'a', '\0a', '\0a', 0),
('cx', 'a', 'a\0', 'a\0', 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, '\371', '\371', 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),
('I', 70000000L, '\004,\035\200', '\200\035,\004', 0),
('I', 0x100000000L
-70000000, '\373\323\342\200', '\200\342\323\373', 0),
('l', 70000000, '\004,\035\200', '\200\035,\004', 0),
('l', -70000000, '\373\323\342\200', '\200\342\323\373', 0),
('L', 70000000L, '\004,\035\200', '\200\035,\004', 0),
('L', 0x100000000L
-70000000, '\373\323\342\200', '\200\342\323\373', 0),
('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),
for fmt
, arg
, big
, lil
, asy
in tests
:
print "%r %r %r %r" % (fmt
, arg
, big
, lil
)
for (xfmt
, exp
) in [('>'+fmt
, big
), ('!'+fmt
, big
), ('<'+fmt
, lil
),
('='+fmt
, ISBIGENDIAN
and big
or lil
)]:
res
= struct
.pack(xfmt
, arg
)
raise TestFailed
, "pack(%r, %r) -> %r # expected %r" % (
n
= struct
.calcsize(xfmt
)
raise TestFailed
, "calcsize(%r) -> %d # expected %d" % (
rev
= struct
.unpack(xfmt
, res
)[0]
if rev
!= arg
and not asy
:
raise TestFailed
, "unpack(%r, %r) -> (%r,) # expected (%r,)" % (
###########################################################################
# Simple native q/Q tests.
print "Platform has native q/Q?", has_native_qQ
and "Yes." or "No."
any_err(struct
.pack
, "Q", -1) # can't pack -1 as unsigned regardless
simple_err(struct
.pack
, "q", "a") # can't pack string as 'q' regardless
simple_err(struct
.pack
, "Q", "a") # ditto, but 'Q'
bytes
= struct
.calcsize('q')
# 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 (
('q', -1, '\xff' * bytes
),
('q', 0, '\x00' * bytes
),
('Q', 0, '\x00' * bytes
),
('q', 1L, '\x00' * (bytes
-1) + '\x01'),
('Q', (1L << (8*bytes
))-1, '\xff' * bytes
),
('q', (1L << (8*bytes
-1))-1, '\x7f' + '\xff' * (bytes
- 1))):
got
= struct
.pack(format
, input)
native_expected
= bigendian_to_native(expected
)
verify(got
== native_expected
,
"%r-pack of %r gave %r, not %r" %
(format
, input, got
, native_expected
))
retrieved
= struct
.unpack(format
, got
)[0]
verify(retrieved
== input,
"%r-unpack of %r gave %r, not %r" %
(format
, got
, retrieved
, input))
###########################################################################
# Standard integer tests (bBhHiIlLqQ).
# XXX Most std integer modes fail to test for out-of-range.
# 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"
def __init__(self
, formatpair
, bytesize
):
assert len(formatpair
) == 2
self
.formatpair
= formatpair
format
= direction
+ code
verify(struct
.calcsize(format
) == bytesize
)
self
.bitsize
= bytesize
* 8
self
.signed_code
, self
.unsigned_code
= formatpair
self
.unsigned_max
= 2L**self
.bitsize
- 1
self
.signed_min
= -(2L**(self
.bitsize
-1))
self
.signed_max
= 2L**(self
.bitsize
-1) - 1
def test_one(self
, x
, pack
=struct
.pack
,
unhexlify
=binascii
.unhexlify
):
print "trying std", self
.formatpair
, "on", x
, "==", hex(x
)
if self
.signed_min
<= x
<= self
.signed_max
:
expected
+= 1L << self
.bitsize
expected
= hex(expected
)[2:-1] # chop "0x" and trailing 'L'
expected
= "0" + expected
expected
= unhexlify(expected
)
expected
= "\x00" * (self
.bytesize
- len(expected
)) + expected
"'%s'-pack of %r gave %r, not %r" %
(format
, x
, got
, expected
))
retrieved
= unpack(format
, got
)[0]
"'%s'-unpack of %r gave %r, not %r" %
(format
, got
, retrieved
, x
))
# Adding any byte should cause a "too big" error.
any_err(unpack
, format
, '\x01' + got
)
expected
= string_reverse(expected
)
"'%s'-pack of %r gave %r, not %r" %
(format
, x
, got
, expected
))
retrieved
= unpack(format
, got
)[0]
"'%s'-unpack of %r gave %r, not %r" %
(format
, got
, retrieved
, x
))
# Adding any byte should cause a "too big" error.
any_err(unpack
, format
, '\x01' + got
)
# x is out of range -- verify pack realizes that.
if code
in self
.BUGGY_RANGE_CHECK
:
print "Skipping buggy range check for code", code
any_err(pack
, ">" + code
, x
)
any_err(pack
, "<" + code
, x
)
# Much the same for unsigned.
code
= self
.unsigned_code
if self
.unsigned_min
<= x
<= self
.unsigned_max
:
expected
= hex(expected
)[2:-1] # chop "0x" and trailing 'L'
expected
= "0" + expected
expected
= unhexlify(expected
)
expected
= "\x00" * (self
.bytesize
- len(expected
)) + expected
"'%s'-pack of %r gave %r, not %r" %
(format
, x
, got
, expected
))
retrieved
= unpack(format
, got
)[0]
"'%s'-unpack of %r gave %r, not %r" %
(format
, got
, retrieved
, x
))
# Adding any byte should cause a "too big" error.
any_err(unpack
, format
, '\x01' + got
)
expected
= string_reverse(expected
)
"'%s'-pack of %r gave %r, not %r" %
(format
, x
, got
, expected
))
retrieved
= unpack(format
, got
)[0]
"'%s'-unpack of %r gave %r, not %r" %
(format
, got
, retrieved
, x
))
# Adding any byte should cause a "too big" error.
any_err(unpack
, format
, '\x01' + got
)
# x is out of range -- verify pack realizes that.
if code
in self
.BUGGY_RANGE_CHECK
:
print "Skipping buggy range check for code", code
any_err(pack
, ">" + code
, x
)
any_err(pack
, "<" + code
, x
)
from random
import randrange
# Create all interesting powers of 2.
for exp
in range(self
.bitsize
+ 3):
# Add some random values.
for i
in range(self
.bitsize
):
for j
in range(self
.bytesize
):
val
= (val
<< 8) |
randrange(256)
# 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 code
in self
.formatpair
:
for badobject
in "a string", 3+42j
, randrange
:
any_err(struct
.pack
, direction
+ code
, badobject
)
###########################################################################
# The p ("Pascal string") code.
for code
, input, expected
, expectedback
in [
('1p', 'abc', '\x00', ''),
('2p', 'abc', '\x01a', 'a'),
('3p', 'abc', '\x02ab', 'ab'),
('4p', 'abc', '\x03abc', 'abc'),
('5p', 'abc', '\x03abc\x00', 'abc'),
('6p', 'abc', '\x03abc\x00\x00', 'abc'),
('1000p', 'x'*1000, '\xff' + 'x'*999, 'x'*255)]:
got
= struct
.pack(code
, input)
raise TestFailed("pack(%r, %r) == %r but expected %r" %
(code
, input, got
, expected
))
(got
,) = struct
.unpack(code
, got
)
raise TestFailed("unpack(%r, %r) == %r but expected %r" %
(code
, input, got
, expectedback
))
###########################################################################
# 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.
for base
in range(1, 33):
# smaller <- largest representable float less than base.
while base
- delta
/ 2.0 != base
:
# 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
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]
# Largest finite IEEE single.
big
= math
.ldexp(big
, 127 - 23)
packed
= struct
.pack(">f", big
)
unpacked
= struct
.unpack(">f", packed
)[0]
# The same, but tack on a 1 bit so it rounds up to infinity.
big
= math
.ldexp(big
, 127 - 24)
packed
= struct
.pack(">f", big
)
TestFailed("expected OverflowError")