diff options
Diffstat (limited to 'Lib/test/test_struct.py')
| -rw-r--r-- | Lib/test/test_struct.py | 497 |
1 files changed, 215 insertions, 282 deletions
diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index b7c2127..5a63135 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -3,28 +3,23 @@ import array import unittest import struct import inspect -import warnings -from test.test_support import run_unittest, check_warnings, _check_py3k_warnings - - -from functools import wraps -from test.test_support import TestFailed, verbose, run_unittest +from test.test_support import run_unittest, check_warnings, check_py3k_warnings import sys ISBIGENDIAN = sys.byteorder == "big" IS32BIT = sys.maxsize == 0x7fffffff +integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q' + testmod_filename = os.path.splitext(__file__)[0] + '.py' +# Native 'q' packing isn't available on systems that don't have the C +# long long type. try: - import _struct -except ImportError: - PY_STRUCT_RANGE_CHECKING = 0 - PY_STRUCT_OVERFLOW_MASKING = 1 - PY_STRUCT_FLOAT_COERCE = 2 + struct.pack('q', 5) +except struct.error: + HAVE_LONG_LONG = False else: - 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) + HAVE_LONG_LONG = True def string_reverse(s): return "".join(reversed(s)) @@ -35,56 +30,20 @@ def bigendian_to_native(value): else: return string_reverse(value) -def with_warning_restore(func): - @wraps(func) - def decorator(*args, **kw): - with warnings.catch_warnings(): - # We need this function to warn every time, so stick an - # unqualifed 'always' at the head of the filter list - warnings.simplefilter("always") - warnings.filterwarnings("error", category=DeprecationWarning) - return func(*args, **kw) - return decorator - -@with_warning_restore -def deprecated_err(func, *args): - try: - func(*args) - except (struct.error, TypeError): - pass - except DeprecationWarning: - if not PY_STRUCT_OVERFLOW_MASKING: - raise TestFailed, "%s%s expected to raise DeprecationWarning" % ( - func.__name__, args) - else: - raise TestFailed, "%s%s did not raise error" % ( - func.__name__, args) - - class StructTest(unittest.TestCase): - @with_warning_restore def check_float_coerce(self, format, number): - # SF bug 1530559. struct.pack raises TypeError where it used to convert. - if PY_STRUCT_FLOAT_COERCE == 2: - # Test for pre-2.5 struct module - with check_warnings((".*integer argument expected, got float", - DeprecationWarning)) as w: - packed = struct.pack(format, number) - floored = struct.unpack(format, packed)[0] - self.assertEqual(floored, int(number), - "did not correcly coerce float to int") - return - try: - struct.pack(format, number) - except (struct.error, TypeError): - if PY_STRUCT_FLOAT_COERCE: - self.fail("expected DeprecationWarning for float coerce") - except DeprecationWarning: - if not PY_STRUCT_FLOAT_COERCE: - self.fail("expected to raise struct.error for float coerce") - else: - self.fail("did not raise error for float coerce") + # SF bug 1530559. struct.pack raises TypeError where it used + # to convert. + with check_warnings((".*integer argument expected, got float", + DeprecationWarning)) as w: + got = struct.pack(format, number) + lineno = inspect.currentframe().f_lineno - 1 + self.assertEqual(w.filename, testmod_filename) + self.assertEqual(w.lineno, lineno) + self.assertEqual(len(w.warnings), 1) + expected = struct.pack(format, int(number)) + self.assertEqual(got, expected) def test_isbigendian(self): self.assertEqual((struct.pack('=i', 1)[0] == chr(0)), ISBIGENDIAN) @@ -103,36 +62,13 @@ class StructTest(unittest.TestCase): self.assertRaises(struct.error, struct.pack, 'iii', 3) self.assertRaises(struct.error, struct.pack, 'i', 3, 3, 3) - self.assertRaises(struct.error, struct.pack, 'i', 'foo') - self.assertRaises(struct.error, struct.pack, 'P', 'foo') + self.assertRaises((TypeError, struct.error), struct.pack, 'i', 'foo') + self.assertRaises((TypeError, struct.error), struct.pack, 'P', 'foo') self.assertRaises(struct.error, struct.unpack, 'd', 'flap') s = struct.pack('ii', 1, 2) self.assertRaises(struct.error, struct.unpack, 'iii', s) self.assertRaises(struct.error, struct.unpack, 'i', s) - def test_warnings_stacklevel(self): - # Python versions between 2.6 and 2.6.5 were producing - # warning messages at the wrong stacklevel. - def inner(fn, *args): - return inspect.currentframe().f_lineno, fn(*args) - - def check_warning_stacklevel(fn, *args): - with warnings.catch_warnings(record=True) as w: - # "always" to make sure __warningregistry__ isn't affected - warnings.simplefilter("always") - lineno, result = inner(fn, *args) - for warn in w: - self.assertEqual(warn.lineno, lineno) - - # out of range warnings - check_warning_stacklevel(struct.pack, '<L', -1) - check_warning_stacklevel(struct.pack, 'L', -1) - check_warning_stacklevel(struct.pack, '<h', 65536) - check_warning_stacklevel(struct.pack, '<l', 2**100) - - # float warnings - check_warning_stacklevel(struct.pack, 'L', 3.1) - def test_transitiveness(self): c = 'a' b = 1 @@ -208,147 +144,91 @@ class StructTest(unittest.TestCase): self.assertEqual(struct.calcsize(xfmt), len(res)) rev = struct.unpack(xfmt, res)[0] if rev != arg: - self.assert_(asy) - - def test_native_qQ(self): - # can't pack -1 as unsigned regardless - self.assertRaises((struct.error, TypeError), struct.pack, "Q", -1) - # can't pack string as 'q' regardless - self.assertRaises(struct.error, struct.pack, "q", "a") - # ditto, but 'Q' - self.assertRaises(struct.error, struct.pack, "Q", "a") - - try: - struct.pack("q", 5) - except struct.error: - # does not have native q/Q - pass - else: - 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): - # Standard integer tests (bBhHiIlLqQ). + self.assertTrue(asy) + + def test_calcsize(self): + expected_size = { + 'b': 1, 'B': 1, + 'h': 2, 'H': 2, + 'i': 4, 'I': 4, + 'l': 4, 'L': 4, + 'q': 8, 'Q': 8, + } + + # standard integer sizes + for code in integer_codes: + for byteorder in ('=', '<', '>', '!'): + format = byteorder+code + size = struct.calcsize(format) + self.assertEqual(size, expected_size[code]) + + # native integer sizes, except 'q' and 'Q' + 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.assertEqual(struct.calcsize('b'), 1) + self.assertLessEqual(2, struct.calcsize('h')) + self.assertLessEqual(4, struct.calcsize('l')) + self.assertLessEqual(struct.calcsize('h'), struct.calcsize('i')) + self.assertLessEqual(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.assertLessEqual(8, struct.calcsize('q')) + self.assertLessEqual(struct.calcsize('l'), struct.calcsize('q')) + + def test_integers(self): + # Integer tests (bBhHiIlLqQ). import binascii class IntTester(unittest.TestCase): - - # 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): - self.assertEqual(len(formatpair), 2) - self.formatpair = formatpair - for direction in "<>!=": - for code in formatpair: - format = direction + code - self.assertEqual(struct.calcsize(format), bytesize) - self.bytesize = bytesize - self.bitsize = bytesize * 8 - self.signed_code, self.unsigned_code = formatpair - self.unsigned_min = 0 - self.unsigned_max = 2L**self.bitsize - 1 - self.signed_min = -(2L**(self.bitsize-1)) - self.signed_max = 2L**(self.bitsize-1) - 1 + def __init__(self, format): + super(IntTester, self).__init__(methodName='test_one') + self.format = format + self.code = format[-1] + self.direction = format[:-1] + if not self.direction in ('', '@', '=', '<', '>', '!'): + raise ValueError("unrecognized packing direction: %s" % + self.direction) + self.bytesize = struct.calcsize(format) + self.bitsize = self.bytesize * 8 + if self.code in tuple('bhilq'): + self.signed = True + self.min_value = -(2L**(self.bitsize-1)) + self.max_value = 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, unpack=struct.unpack, unhexlify=binascii.unhexlify): - # Try signed. - code = self.signed_code - if self.signed_min <= x <= self.signed_max: - # Try big-endian. - expected = long(x) - if x < 0: - expected += 1L << self.bitsize - self.assert_(expected > 0) - 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? - format = ">" + code - 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. - if not PY_STRUCT_RANGE_CHECKING and code in self.BUGGY_RANGE_CHECK: - if verbose: - print "Skipping buggy range check for code", code - else: - deprecated_err(pack, ">" + code, 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 + format = self.format + if self.min_value <= x <= self.max_value: expected = long(x) - expected = hex(expected)[2:-1] # chop "0x" and trailing 'L' + if self.signed and x < 0: + expected += 1L << self.bitsize + self.assertGreaterEqual(expected, 0) + expected = '%x' % expected 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) + expected = ("\x00" * (self.bytesize - len(expected)) + + expected) + if (self.direction == '<' or + self.direction in ('', '@', '=') and not ISBIGENDIAN): + expected = string_reverse(expected) + self.assertEqual(len(expected), self.bytesize) # Pack work? got = pack(format, x) @@ -361,15 +241,10 @@ class StructTest(unittest.TestCase): # 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. - if not PY_STRUCT_RANGE_CHECKING and code in self.BUGGY_RANGE_CHECK: - if verbose: - print "Skipping buggy range check for code", code - else: - deprecated_err(pack, ">" + code, x) - deprecated_err(pack, "<" + code, x) + self.assertRaises((OverflowError, ValueError, struct.error), + pack, format, x) def run(self): from random import randrange @@ -386,34 +261,104 @@ class StructTest(unittest.TestCase): 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). + # Values absorbed from other tests + values.extend([300, 700000, sys.maxint*4]) + + # 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) + self.test_one(int(x)) + self.test_one(long(x)) # Some error cases. - for direction in "<>": - for code in self.formatpair: - for badobject in "a string", 3+42j, randrange: - self.assertRaises((struct.error, TypeError), - struct.pack, direction + code, - badobject) - - for args in [("bB", 1), - ("hH", 2), - ("iI", 4), - ("lL", 4), - ("qQ", 8)]: - t = IntTester(*args) - t.run() + class NotAnIntNS(object): + def __int__(self): + return 42 + + def __long__(self): + return 1729L + + class NotAnIntOS: + def __int__(self): + return 85 + + def __long__(self): + return -163L + + # Objects with an '__index__' method should be allowed + # to pack as integers. That is assuming the implemented + # '__index__' method returns and 'int' or 'long'. + class Indexable(object): + def __init__(self, value): + self._value = value + + def __index__(self): + return self._value + + # If the '__index__' method raises a type error, then + # '__int__' should be used with a deprecation warning. + class BadIndex(object): + def __index__(self): + raise TypeError + + def __int__(self): + return 42 + + self.assertRaises((TypeError, struct.error), + struct.pack, self.format, + "a string") + self.assertRaises((TypeError, struct.error), + struct.pack, self.format, + randrange) + with check_warnings(("integer argument expected, " + "got non-integer", DeprecationWarning)): + with self.assertRaises((TypeError, struct.error)): + struct.pack(self.format, 3+42j) + + # an attempt to convert a non-integer (with an + # implicit conversion via __int__) should succeed, + # with a DeprecationWarning + for nonint in NotAnIntNS(), NotAnIntOS(), BadIndex(): + with check_warnings((".*integer argument expected, got non" + "-integer", DeprecationWarning)) as w: + got = struct.pack(self.format, nonint) + lineno = inspect.currentframe().f_lineno - 1 + self.assertEqual(w.filename, testmod_filename) + self.assertEqual(w.lineno, lineno) + self.assertEqual(len(w.warnings), 1) + expected = struct.pack(self.format, int(nonint)) + self.assertEqual(got, expected) + + # Check for legitimate values from '__index__'. + for obj in (Indexable(0), Indexable(10), Indexable(17), + Indexable(42), Indexable(100), Indexable(127)): + try: + struct.pack(format, obj) + except: + self.fail("integer code pack failed on object " + "with '__index__' method") + + # Check for bogus values from '__index__'. + for obj in (Indexable('a'), Indexable(u'b'), Indexable(None), + Indexable({'a': 1}), Indexable([1, 2, 3])): + self.assertRaises((TypeError, struct.error), + struct.pack, self.format, + obj) + + byteorders = '', '@', '=', '<', '>', '!' + for code in integer_codes: + for byteorder in byteorders: + if (byteorder in ('', '@') and code in ('q', 'Q') and + not HAVE_LONG_LONG): + continue + format = byteorder+code + t = IntTester(format) + t.run() def test_p_code(self): # Test p ("Pascal string") code. @@ -466,36 +411,13 @@ class StructTest(unittest.TestCase): big = math.ldexp(big, 127 - 24) 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'): - 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) - - def XXXtest_1530559(self): - # XXX This is broken: see the bug report + def test_1530559(self): # SF bug 1530559. struct.pack raises TypeError where it used to convert. for endian in ('', '>', '<'): - for fmt in ('B', 'H', 'I', 'L', 'b', 'h', 'i', 'l'): + for fmt in integer_codes: self.check_float_coerce(endian + fmt, 1.0) self.check_float_coerce(endian + fmt, 1.5) - def test_issue4228(self): - # Packing a long may yield either 32 or 64 bits - with check_warnings(quiet=True): - x = struct.pack('L', -1)[:4] - self.assertEqual(x, '\xff'*4) - def test_unpack_from(self, cls=str): data = cls('abcd01234') fmt = '4s' @@ -528,12 +450,15 @@ class StructTest(unittest.TestCase): # Go beyond boundaries. small_buf = array.array('c', ' '*10) - self.assertRaises(struct.error, s.pack_into, small_buf, 0, test_string) - self.assertRaises(struct.error, s.pack_into, small_buf, 2, test_string) + self.assertRaises((ValueError, struct.error), s.pack_into, small_buf, 0, + test_string) + self.assertRaises((ValueError, struct.error), s.pack_into, small_buf, 2, + test_string) # Test bogus offset (issue 3694) sb = small_buf - self.assertRaises(TypeError, struct.pack_into, b'1', sb, None) + self.assertRaises((TypeError, struct.error), struct.pack_into, b'', sb, + None) def test_pack_into_fn(self): test_string = 'Reykjavik rocks, eow!' @@ -553,11 +478,13 @@ class StructTest(unittest.TestCase): # Go beyond boundaries. small_buf = array.array('c', ' '*10) - self.assertRaises(struct.error, pack_into, small_buf, 0, test_string) - self.assertRaises(struct.error, pack_into, small_buf, 2, test_string) + self.assertRaises((ValueError, struct.error), pack_into, small_buf, 0, + test_string) + self.assertRaises((ValueError, struct.error), pack_into, small_buf, 2, + test_string) def test_unpack_with_buffer(self): - with _check_py3k_warnings(("buffer.. not supported in 3.x", + with check_py3k_warnings(("buffer.. not supported in 3.x", DeprecationWarning)): # SF bug 1563759: struct.unpack doesn't support buffer protocol objects data1 = array.array('B', '\x12\x34\x56\x78') @@ -569,6 +496,9 @@ class StructTest(unittest.TestCase): self.test_unpack_from(cls=buffer) def test_bool(self): + class ExplodingBool(object): + def __nonzero__(self): + raise IOError for prefix in tuple("<>!=")+('',): false = (), [], [], '', 0 true = [1], 'test', 5, -1, 0xffffffffL+1, 0xffffffff//2 @@ -597,18 +527,21 @@ class StructTest(unittest.TestCase): self.assertFalse(prefix, msg='encoded bool is not one byte: %r' %packed) - for c in '\x01\x7f\xff\x0f\xf0': - self.assertTrue(struct.unpack('>?', c)[0]) + self.assertRaises(IOError, struct.pack, prefix + '?', + ExplodingBool()) + + for c in [b'\x01', b'\x7f', b'\xff', b'\x0f', b'\xf0']: + self.assertTrue(struct.unpack('>?', c)[0]) - if IS32BIT: - def test_crasher(self): - self.assertRaises(MemoryError, struct.pack, "357913941c", "a") + @unittest.skipUnless(IS32BIT, "Specific to 32bit machines") + def test_crasher(self): + self.assertRaises(MemoryError, struct.pack, "357913941c", "a") def test_count_overflow(self): - hugecount = '{0}b'.format(sys.maxsize+1) + hugecount = '{}b'.format(sys.maxsize+1) self.assertRaises(struct.error, struct.calcsize, hugecount) - hugecount2 = '{0}b{1}H'.format(sys.maxsize//2, sys.maxsize//2) + hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2) self.assertRaises(struct.error, struct.calcsize, hugecount2) def test_main(): |
