Expand test coverage for struct.pack with native integer packing;

reorganize the test_struct module to remove duplicated code and tests.
This commit is contained in:
Mark Dickinson 2009-07-07 14:15:45 +00:00
parent ca6b5f36f4
commit bb3895cfc6
1 changed files with 104 additions and 167 deletions

View File

@ -11,7 +11,17 @@ from test.test_support import TestFailed, verbose, run_unittest
import sys import sys
ISBIGENDIAN = sys.byteorder == "big" ISBIGENDIAN = sys.byteorder == "big"
IS32BIT = sys.maxsize == 0x7fffffff IS32BIT = sys.maxsize == 0x7fffffff
del sys
integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q'
# Native 'q' packing isn't available on systems that don't have the C
# long long type.
try:
struct.pack('q', 5)
except struct.error:
HAVE_LONG_LONG = False
else:
HAVE_LONG_LONG = True
try: try:
import _struct import _struct
@ -58,7 +68,6 @@ def deprecated_err(func, *args):
raise TestFailed, "%s%s did not raise error" % ( raise TestFailed, "%s%s did not raise error" % (
func.__name__, args) func.__name__, args)
class StructTest(unittest.TestCase): class StructTest(unittest.TestCase):
@with_warning_restore @with_warning_restore
@ -183,91 +192,89 @@ class StructTest(unittest.TestCase):
if rev != arg: if rev != arg:
self.assertTrue(asy) self.assertTrue(asy)
def test_native_qQ(self): def test_calcsize(self):
# can't pack -1 as unsigned regardless expected_size = {
self.assertRaises((struct.error, OverflowError), struct.pack, "Q", -1) 'b': 1, 'B': 1,
# can't pack string as 'q' regardless 'h': 2, 'H': 2,
self.assertRaises(struct.error, struct.pack, "q", "a") 'i': 4, 'I': 4,
# ditto, but 'Q' 'l': 4, 'L': 4,
self.assertRaises(struct.error, struct.pack, "Q", "a") 'q': 8, 'Q': 8,
}
try: # standard integer sizes
struct.pack("q", 5) for code in integer_codes:
except struct.error: for byteorder in ('=', '<', '>', '!'):
# does not have native q/Q format = byteorder+code
pass size = struct.calcsize(format)
else: self.assertEqual(size, expected_size[code])
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)
self.assertEqual(got, native_expected)
retrieved = struct.unpack(format, got)[0]
self.assertEqual(retrieved, input)
def test_standard_integers(self): # native integer sizes, except 'q' and 'Q'
# Standard integer tests (bBhHiIlLqQ). for format_pair in ('bB', 'hH', 'iI', 'lL'):
for byteorder in ['', '@']:
signed_size = struct.calcsize(byteorder + format_pair[0])
unsigned_size = struct.calcsize(byteorder + format_pair[1])
self.assertEqual(signed_size, unsigned_size)
# bounds for native integer sizes
self.assertTrue(struct.calcsize('b')==1)
self.assertTrue(2 <= struct.calcsize('h'))
self.assertTrue(4 <= struct.calcsize('l'))
self.assertTrue(struct.calcsize('h') <= struct.calcsize('i'))
self.assertTrue(struct.calcsize('i') <= struct.calcsize('l'))
# tests for native 'q' and 'Q' when applicable
if HAVE_LONG_LONG:
self.assertEqual(struct.calcsize('q'), struct.calcsize('Q'))
self.assertTrue(8 <= struct.calcsize('q'))
self.assertTrue(struct.calcsize('l') <= struct.calcsize('q'))
def test_integers(self):
# Integer tests (bBhHiIlLqQ).
import binascii import binascii
class IntTester(unittest.TestCase): class IntTester(unittest.TestCase):
def __init__(self, format):
def __init__(self, formatpair, bytesize):
super(IntTester, self).__init__(methodName='test_one') super(IntTester, self).__init__(methodName='test_one')
self.assertEqual(len(formatpair), 2) self.format = format
self.formatpair = formatpair self.code = format[-1]
for direction in "<>!=": self.direction = format[:-1]
for code in formatpair: if not self.direction in ('', '@', '=', '<', '>', '!'):
format = direction + code raise ValueError("unrecognized packing direction: %s" %
self.assertEqual(struct.calcsize(format), bytesize) self.direction)
self.bytesize = bytesize self.bytesize = struct.calcsize(format)
self.bitsize = bytesize * 8 self.bitsize = self.bytesize * 8
self.signed_code, self.unsigned_code = formatpair if self.code in tuple('bhilq'):
self.unsigned_min = 0 self.signed = True
self.unsigned_max = 2L**self.bitsize - 1 self.min_value = -(2L**(self.bitsize-1))
self.signed_min = -(2L**(self.bitsize-1)) self.max_value = 2L**(self.bitsize-1) - 1
self.signed_max = 2L**(self.bitsize-1) - 1 elif self.code in tuple('BHILQ'):
self.signed = False
self.min_value = 0
self.max_value = 2L**self.bitsize - 1
else:
raise ValueError("unrecognized format code: %s" %
self.code)
def test_one(self, x, pack=struct.pack, def test_one(self, x, pack=struct.pack,
unpack=struct.unpack, unpack=struct.unpack,
unhexlify=binascii.unhexlify): unhexlify=binascii.unhexlify):
# Try signed.
code = self.signed_code format = self.format
if self.signed_min <= x <= self.signed_max: if self.min_value <= x <= self.max_value:
# Try big-endian.
expected = long(x) expected = long(x)
if x < 0: if self.signed and x < 0:
expected += 1L << self.bitsize expected += 1L << self.bitsize
self.assertTrue(expected > 0) self.assertTrue(expected >= 0)
expected = hex(expected)[2:-1] # chop "0x" and trailing 'L' expected = '%x' % expected
if len(expected) & 1: if len(expected) & 1:
expected = "0" + expected expected = "0" + expected
expected = unhexlify(expected) expected = unhexlify(expected)
expected = "\x00" * (self.bytesize - len(expected)) + expected expected = ("\x00" * (self.bytesize - len(expected)) +
expected)
# Pack work? if (self.direction == '<' or
format = ">" + code self.direction in ('', '@', '=') and not ISBIGENDIAN):
got = pack(format, x) expected = string_reverse(expected)
self.assertEqual(got, expected) self.assertEqual(len(expected), self.bytesize)
# Unpack work?
retrieved = unpack(format, got)[0]
self.assertEqual(x, retrieved)
# Adding any byte should cause a "too big" error.
self.assertRaises((struct.error, TypeError), unpack, format,
'\x01' + got)
# Try little-endian.
format = "<" + code
expected = string_reverse(expected)
# Pack work? # Pack work?
got = pack(format, x) got = pack(format, x)
@ -280,56 +287,9 @@ class StructTest(unittest.TestCase):
# Adding any byte should cause a "too big" error. # Adding any byte should cause a "too big" error.
self.assertRaises((struct.error, TypeError), unpack, format, self.assertRaises((struct.error, TypeError), unpack, format,
'\x01' + got) '\x01' + got)
else: else:
# x is out of range -- verify pack realizes that. # x is out of range -- verify pack realizes that.
deprecated_err(pack, ">" + code, x) deprecated_err(pack, format, x)
deprecated_err(pack, "<" + code, x)
# Much the same for unsigned.
code = self.unsigned_code
if self.unsigned_min <= x <= self.unsigned_max:
# Try big-endian.
format = ">" + code
expected = long(x)
expected = hex(expected)[2:-1] # chop "0x" and trailing 'L'
if len(expected) & 1:
expected = "0" + expected
expected = unhexlify(expected)
expected = "\x00" * (self.bytesize - len(expected)) + expected
# Pack work?
got = pack(format, x)
self.assertEqual(got, expected)
# Unpack work?
retrieved = unpack(format, got)[0]
self.assertEqual(x, retrieved)
# Adding any byte should cause a "too big" error.
self.assertRaises((struct.error, TypeError), unpack, format,
'\x01' + got)
# Try little-endian.
format = "<" + code
expected = string_reverse(expected)
# Pack work?
got = pack(format, x)
self.assertEqual(got, expected)
# Unpack work?
retrieved = unpack(format, got)[0]
self.assertEqual(x, retrieved)
# Adding any byte should cause a "too big" error.
self.assertRaises((struct.error, TypeError), unpack, format,
'\x01' + got)
else:
# x is out of range -- verify pack realizes that.
deprecated_err(pack, ">" + code, x)
deprecated_err(pack, "<" + code, x)
def run(self): def run(self):
from random import randrange from random import randrange
@ -346,18 +306,19 @@ class StructTest(unittest.TestCase):
val = (val << 8) | randrange(256) val = (val << 8) | randrange(256)
values.append(val) values.append(val)
# Try all those, and their negations, and +-1 from them. Note # Values absorbed from other tests
# that this tests all power-of-2 boundaries in range, and a few out values.extend([300, 700000, sys.maxint*4])
# of range, plus +-(2**n +- 1).
# 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 base in values:
for val in -base, base: for val in -base, base:
for incr in -1, 0, 1: for incr in -1, 0, 1:
x = val + incr x = val + incr
try: self.test_one(int(x))
x = int(x) self.test_one(long(x))
except OverflowError:
pass
self.test_one(x)
# Some error cases. # Some error cases.
class NotAnIntNS(object): class NotAnIntNS(object):
@ -374,21 +335,21 @@ class StructTest(unittest.TestCase):
def __long__(self): def __long__(self):
return -163L return -163L
for direction in "<>": for badobject in ("a string", 3+42j, randrange,
for code in self.formatpair: NotAnIntNS(), NotAnIntOS()):
for badobject in ("a string", 3+42j, randrange, self.assertRaises(struct.error,
NotAnIntNS(), NotAnIntOS()): struct.pack, format,
self.assertRaises(struct.error, badobject)
struct.pack, direction + code,
badobject)
for args in [("bB", 1), byteorders = '', '@', '=', '<', '>', '!'
("hH", 2), for code in integer_codes:
("iI", 4), for byteorder in byteorders:
("lL", 4), if (byteorder in ('', '@') and code in ('q', 'Q') and
("qQ", 8)]: not HAVE_LONG_LONG):
t = IntTester(*args) continue
t.run() format = byteorder+code
t = IntTester(format)
t.run()
def test_p_code(self): def test_p_code(self):
# Test p ("Pascal string") code. # Test p ("Pascal string") code.
@ -441,37 +402,13 @@ class StructTest(unittest.TestCase):
big = math.ldexp(big, 127 - 24) big = math.ldexp(big, 127 - 24)
self.assertRaises(OverflowError, struct.pack, ">f", big) self.assertRaises(OverflowError, struct.pack, ">f", big)
if PY_STRUCT_RANGE_CHECKING:
def test_1229380(self):
# SF bug 1229380. No struct.pack exception for some out of
# range integers
import sys
for endian in ('', '>', '<'):
for cls in (int, long):
for fmt in ('B', 'H', 'I', 'L', 'Q'):
deprecated_err(struct.pack, endian + fmt, cls(-1))
deprecated_err(struct.pack, endian + 'B', cls(300))
deprecated_err(struct.pack, endian + 'H', cls(70000))
deprecated_err(struct.pack, endian + 'I', sys.maxint * 4L)
deprecated_err(struct.pack, endian + 'L', sys.maxint * 4L)
deprecated_err(struct.pack, endian + 'Q', 2**64)
def test_1530559(self): def test_1530559(self):
# SF bug 1530559. struct.pack raises TypeError where it used to convert. # SF bug 1530559. struct.pack raises TypeError where it used to convert.
for endian in ('', '>', '<'): for endian in ('', '>', '<'):
for fmt in ('B', 'H', 'I', 'L', 'Q', 'b', 'h', 'i', 'l', 'q'): for fmt in integer_codes:
self.check_float_coerce(endian + fmt, 1.0) self.check_float_coerce(endian + fmt, 1.0)
self.check_float_coerce(endian + fmt, 1.5) self.check_float_coerce(endian + fmt, 1.5)
@unittest.skipUnless(PY_STRUCT_OVERFLOW_MASKING,
"only applies when overflow masking enabled")
def test_issue4228(self):
# Packing a long may yield either 32 or 64 bits
x = struct.pack('L', -1)[:4]
self.assertEqual(x, '\xff'*4)
def test_unpack_from(self): def test_unpack_from(self):
test_string = 'abcd01234' test_string = 'abcd01234'
fmt = '4s' fmt = '4s'