1 import os 2 import array 3 import unittest 4 import struct 5 import inspect 6 from test import test_support as support 7 from test.test_support import (check_warnings, check_py3k_warnings) 8 9 import sys 10 ISBIGENDIAN = sys.byteorder == "big" 11 IS32BIT = sys.maxsize == 0x7fffffff 12 13 integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q' 14 15 testmod_filename = os.path.splitext(__file__)[0] + '.py' 16 # Native 'q' packing isn't available on systems that don't have the C 17 # long long type. 18 try: 19 struct.pack('q', 5) 20 except struct.error: 21 HAVE_LONG_LONG = False 22 else: 23 HAVE_LONG_LONG = True 24 25 def string_reverse(s): 26 return "".join(reversed(s)) 27 28 def bigendian_to_native(value): 29 if ISBIGENDIAN: 30 return value 31 else: 32 return string_reverse(value) 33 34 class StructTest(unittest.TestCase): 35 36 def check_float_coerce(self, format, number): 37 # SF bug 1530559. struct.pack raises TypeError where it used 38 # to convert. 39 with check_warnings((".*integer argument expected, got float", 40 DeprecationWarning)) as w: 41 got = struct.pack(format, number) 42 lineno = inspect.currentframe().f_lineno - 1 43 self.assertEqual(w.filename, testmod_filename) 44 self.assertEqual(w.lineno, lineno) 45 self.assertEqual(len(w.warnings), 1) 46 expected = struct.pack(format, int(number)) 47 self.assertEqual(got, expected) 48 49 def test_isbigendian(self): 50 self.assertEqual((struct.pack('=i', 1)[0] == chr(0)), ISBIGENDIAN) 51 52 def test_consistence(self): 53 self.assertRaises(struct.error, struct.calcsize, 'Z') 54 55 sz = struct.calcsize('i') 56 self.assertEqual(sz * 3, struct.calcsize('iii')) 57 58 fmt = 'cbxxxxxxhhhhiillffd?' 59 fmt3 = '3c3b18x12h6i6l6f3d3?' 60 sz = struct.calcsize(fmt) 61 sz3 = struct.calcsize(fmt3) 62 self.assertEqual(sz * 3, sz3) 63 64 self.assertRaises(struct.error, struct.pack, 'iii', 3) 65 self.assertRaises(struct.error, struct.pack, 'i', 3, 3, 3) 66 self.assertRaises((TypeError, struct.error), struct.pack, 'i', 'foo') 67 self.assertRaises((TypeError, struct.error), struct.pack, 'P', 'foo') 68 self.assertRaises(struct.error, struct.unpack, 'd', 'flap') 69 s = struct.pack('ii', 1, 2) 70 self.assertRaises(struct.error, struct.unpack, 'iii', s) 71 self.assertRaises(struct.error, struct.unpack, 'i', s) 72 73 def test_transitiveness(self): 74 c = 'a' 75 b = 1 76 h = 255 77 i = 65535 78 l = 65536 79 f = 3.1415 80 d = 3.1415 81 t = True 82 83 for prefix in ('', '@', '<', '>', '=', '!'): 84 for format in ('xcbhilfd?', 'xcBHILfd?'): 85 format = prefix + format 86 s = struct.pack(format, c, b, h, i, l, f, d, t) 87 cp, bp, hp, ip, lp, fp, dp, tp = struct.unpack(format, s) 88 self.assertEqual(cp, c) 89 self.assertEqual(bp, b) 90 self.assertEqual(hp, h) 91 self.assertEqual(ip, i) 92 self.assertEqual(lp, l) 93 self.assertEqual(int(100 * fp), int(100 * f)) 94 self.assertEqual(int(100 * dp), int(100 * d)) 95 self.assertEqual(tp, t) 96 97 def test_new_features(self): 98 # Test some of the new features in detail 99 # (format, argument, big-endian result, little-endian result, asymmetric) 100 tests = [ 101 ('c', 'a', 'a', 'a', 0), 102 ('xc', 'a', '\0a', '\0a', 0), 103 ('cx', 'a', 'a\0', 'a\0', 0), 104 ('s', 'a', 'a', 'a', 0), 105 ('0s', 'helloworld', '', '', 1), 106 ('1s', 'helloworld', 'h', 'h', 1), 107 ('9s', 'helloworld', 'helloworl', 'helloworl', 1), 108 ('10s', 'helloworld', 'helloworld', 'helloworld', 0), 109 ('11s', 'helloworld', 'helloworld\0', 'helloworld\0', 1), 110 ('20s', 'helloworld', 'helloworld'+10*'\0', 'helloworld'+10*'\0', 1), 111 ('b', 7, '\7', '\7', 0), 112 ('b', -7, '\371', '\371', 0), 113 ('B', 7, '\7', '\7', 0), 114 ('B', 249, '\371', '\371', 0), 115 ('h', 700, '\002\274', '\274\002', 0), 116 ('h', -700, '\375D', 'D\375', 0), 117 ('H', 700, '\002\274', '\274\002', 0), 118 ('H', 0x10000-700, '\375D', 'D\375', 0), 119 ('i', 70000000, '\004,\035\200', '\200\035,\004', 0), 120 ('i', -70000000, '\373\323\342\200', '\200\342\323\373', 0), 121 ('I', 70000000L, '\004,\035\200', '\200\035,\004', 0), 122 ('I', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), 123 ('l', 70000000, '\004,\035\200', '\200\035,\004', 0), 124 ('l', -70000000, '\373\323\342\200', '\200\342\323\373', 0), 125 ('L', 70000000L, '\004,\035\200', '\200\035,\004', 0), 126 ('L', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), 127 ('f', 2.0, '@\000\000\000', '\000\000\000@', 0), 128 ('d', 2.0, '@\000\000\000\000\000\000\000', 129 '\000\000\000\000\000\000\000@', 0), 130 ('f', -2.0, '\300\000\000\000', '\000\000\000\300', 0), 131 ('d', -2.0, '\300\000\000\000\000\000\000\000', 132 '\000\000\000\000\000\000\000\300', 0), 133 ('?', 0, '\0', '\0', 0), 134 ('?', 3, '\1', '\1', 1), 135 ('?', True, '\1', '\1', 0), 136 ('?', [], '\0', '\0', 1), 137 ('?', (1,), '\1', '\1', 1), 138 ] 139 140 for fmt, arg, big, lil, asy in tests: 141 for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), 142 ('='+fmt, ISBIGENDIAN and big or lil)]: 143 res = struct.pack(xfmt, arg) 144 self.assertEqual(res, exp) 145 self.assertEqual(struct.calcsize(xfmt), len(res)) 146 rev = struct.unpack(xfmt, res)[0] 147 if rev != arg: 148 self.assertTrue(asy) 149 150 def test_calcsize(self): 151 expected_size = { 152 'b': 1, 'B': 1, 153 'h': 2, 'H': 2, 154 'i': 4, 'I': 4, 155 'l': 4, 'L': 4, 156 'q': 8, 'Q': 8, 157 } 158 159 # standard integer sizes 160 for code in integer_codes: 161 for byteorder in ('=', '<', '>', '!'): 162 format = byteorder+code 163 size = struct.calcsize(format) 164 self.assertEqual(size, expected_size[code]) 165 166 # native integer sizes, except 'q' and 'Q' 167 for format_pair in ('bB', 'hH', 'iI', 'lL'): 168 for byteorder in ['', '@']: 169 signed_size = struct.calcsize(byteorder + format_pair[0]) 170 unsigned_size = struct.calcsize(byteorder + format_pair[1]) 171 self.assertEqual(signed_size, unsigned_size) 172 173 # bounds for native integer sizes 174 self.assertEqual(struct.calcsize('b'), 1) 175 self.assertLessEqual(2, struct.calcsize('h')) 176 self.assertLessEqual(4, struct.calcsize('l')) 177 self.assertLessEqual(struct.calcsize('h'), struct.calcsize('i')) 178 self.assertLessEqual(struct.calcsize('i'), struct.calcsize('l')) 179 180 # tests for native 'q' and 'Q' when applicable 181 if HAVE_LONG_LONG: 182 self.assertEqual(struct.calcsize('q'), struct.calcsize('Q')) 183 self.assertLessEqual(8, struct.calcsize('q')) 184 self.assertLessEqual(struct.calcsize('l'), struct.calcsize('q')) 185 186 def test_integers(self): 187 # Integer tests (bBhHiIlLqQ). 188 import binascii 189 190 class IntTester(unittest.TestCase): 191 def __init__(self, format): 192 super(IntTester, self).__init__(methodName='test_one') 193 self.format = format 194 self.code = format[-1] 195 self.direction = format[:-1] 196 if not self.direction in ('', '@', '=', '<', '>', '!'): 197 raise ValueError("unrecognized packing direction: %s" % 198 self.direction) 199 self.bytesize = struct.calcsize(format) 200 self.bitsize = self.bytesize * 8 201 if self.code in tuple('bhilq'): 202 self.signed = True 203 self.min_value = -(2L**(self.bitsize-1)) 204 self.max_value = 2L**(self.bitsize-1) - 1 205 elif self.code in tuple('BHILQ'): 206 self.signed = False 207 self.min_value = 0 208 self.max_value = 2L**self.bitsize - 1 209 else: 210 raise ValueError("unrecognized format code: %s" % 211 self.code) 212 213 def test_one(self, x, pack=struct.pack, 214 unpack=struct.unpack, 215 unhexlify=binascii.unhexlify): 216 217 format = self.format 218 if self.min_value <= x <= self.max_value: 219 expected = long(x) 220 if self.signed and x < 0: 221 expected += 1L << self.bitsize 222 self.assertGreaterEqual(expected, 0) 223 expected = '%x' % expected 224 if len(expected) & 1: 225 expected = "0" + expected 226 expected = unhexlify(expected) 227 expected = ("\x00" * (self.bytesize - len(expected)) + 228 expected) 229 if (self.direction == '<' or 230 self.direction in ('', '@', '=') and not ISBIGENDIAN): 231 expected = string_reverse(expected) 232 self.assertEqual(len(expected), self.bytesize) 233 234 # Pack work? 235 got = pack(format, x) 236 self.assertEqual(got, expected) 237 238 # Unpack work? 239 retrieved = unpack(format, got)[0] 240 self.assertEqual(x, retrieved) 241 242 # Adding any byte should cause a "too big" error. 243 self.assertRaises((struct.error, TypeError), unpack, format, 244 '\x01' + got) 245 else: 246 # x is out of range -- verify pack realizes that. 247 self.assertRaises((OverflowError, ValueError, struct.error), 248 pack, format, x) 249 250 def run(self): 251 from random import randrange 252 253 # Create all interesting powers of 2. 254 values = [] 255 for exp in range(self.bitsize + 3): 256 values.append(1L << exp) 257 258 # Add some random values. 259 for i in range(self.bitsize): 260 val = 0L 261 for j in range(self.bytesize): 262 val = (val << 8) | randrange(256) 263 values.append(val) 264 265 # Values absorbed from other tests 266 values.extend([300, 700000, sys.maxint*4]) 267 268 # Try all those, and their negations, and +-1 from 269 # them. Note that this tests all power-of-2 270 # boundaries in range, and a few out of range, plus 271 # +-(2**n +- 1). 272 for base in values: 273 for val in -base, base: 274 for incr in -1, 0, 1: 275 x = val + incr 276 self.test_one(int(x)) 277 self.test_one(long(x)) 278 279 # Some error cases. 280 class NotAnIntNS(object): 281 def __int__(self): 282 return 42 283 284 def __long__(self): 285 return 1729L 286 287 class NotAnIntOS: 288 def __int__(self): 289 return 85 290 291 def __long__(self): 292 return -163L 293 294 # Objects with an '__index__' method should be allowed 295 # to pack as integers. That is assuming the implemented 296 # '__index__' method returns and 'int' or 'long'. 297 class Indexable(object): 298 def __init__(self, value): 299 self._value = value 300 301 def __index__(self): 302 return self._value 303 304 # If the '__index__' method raises a type error, then 305 # '__int__' should be used with a deprecation warning. 306 class BadIndex(object): 307 def __index__(self): 308 raise TypeError 309 310 def __int__(self): 311 return 42 312 313 self.assertRaises((TypeError, struct.error), 314 struct.pack, self.format, 315 "a string") 316 self.assertRaises((TypeError, struct.error), 317 struct.pack, self.format, 318 randrange) 319 with check_warnings(("integer argument expected, " 320 "got non-integer", DeprecationWarning)): 321 with self.assertRaises((TypeError, struct.error)): 322 struct.pack(self.format, 3+42j) 323 324 # an attempt to convert a non-integer (with an 325 # implicit conversion via __int__) should succeed, 326 # with a DeprecationWarning 327 for nonint in NotAnIntNS(), NotAnIntOS(), BadIndex(): 328 with check_warnings((".*integer argument expected, got non" 329 "-integer", DeprecationWarning)) as w: 330 got = struct.pack(self.format, nonint) 331 lineno = inspect.currentframe().f_lineno - 1 332 self.assertEqual(w.filename, testmod_filename) 333 self.assertEqual(w.lineno, lineno) 334 self.assertEqual(len(w.warnings), 1) 335 expected = struct.pack(self.format, int(nonint)) 336 self.assertEqual(got, expected) 337 338 # Check for legitimate values from '__index__'. 339 for obj in (Indexable(0), Indexable(10), Indexable(17), 340 Indexable(42), Indexable(100), Indexable(127)): 341 try: 342 struct.pack(format, obj) 343 except: 344 self.fail("integer code pack failed on object " 345 "with '__index__' method") 346 347 # Check for bogus values from '__index__'. 348 for obj in (Indexable('a'), Indexable(u'b'), Indexable(None), 349 Indexable({'a': 1}), Indexable([1, 2, 3])): 350 self.assertRaises((TypeError, struct.error), 351 struct.pack, self.format, 352 obj) 353 354 byteorders = '', '@', '=', '<', '>', '!' 355 for code in integer_codes: 356 for byteorder in byteorders: 357 if (byteorder in ('', '@') and code in ('q', 'Q') and 358 not HAVE_LONG_LONG): 359 continue 360 format = byteorder+code 361 t = IntTester(format) 362 t.run() 363 364 def test_p_code(self): 365 # Test p ("Pascal string") code. 366 for code, input, expected, expectedback in [ 367 ('p','abc', '\x00', ''), 368 ('1p', 'abc', '\x00', ''), 369 ('2p', 'abc', '\x01a', 'a'), 370 ('3p', 'abc', '\x02ab', 'ab'), 371 ('4p', 'abc', '\x03abc', 'abc'), 372 ('5p', 'abc', '\x03abc\x00', 'abc'), 373 ('6p', 'abc', '\x03abc\x00\x00', 'abc'), 374 ('1000p', 'x'*1000, '\xff' + 'x'*999, 'x'*255)]: 375 got = struct.pack(code, input) 376 self.assertEqual(got, expected) 377 (got,) = struct.unpack(code, got) 378 self.assertEqual(got, expectedback) 379 380 def test_705836(self): 381 # SF bug 705836. "<f" and ">f" had a severe rounding bug, where a carry 382 # from the low-order discarded bits could propagate into the exponent 383 # field, causing the result to be wrong by a factor of 2. 384 import math 385 386 for base in range(1, 33): 387 # smaller <- largest representable float less than base. 388 delta = 0.5 389 while base - delta / 2.0 != base: 390 delta /= 2.0 391 smaller = base - delta 392 # Packing this rounds away a solid string of trailing 1 bits. 393 packed = struct.pack("<f", smaller) 394 unpacked = struct.unpack("<f", packed)[0] 395 # This failed at base = 2, 4, and 32, with unpacked = 1, 2, and 396 # 16, respectively. 397 self.assertEqual(base, unpacked) 398 bigpacked = struct.pack(">f", smaller) 399 self.assertEqual(bigpacked, string_reverse(packed)) 400 unpacked = struct.unpack(">f", bigpacked)[0] 401 self.assertEqual(base, unpacked) 402 403 # Largest finite IEEE single. 404 big = (1 << 24) - 1 405 big = math.ldexp(big, 127 - 23) 406 packed = struct.pack(">f", big) 407 unpacked = struct.unpack(">f", packed)[0] 408 self.assertEqual(big, unpacked) 409 410 # The same, but tack on a 1 bit so it rounds up to infinity. 411 big = (1 << 25) - 1 412 big = math.ldexp(big, 127 - 24) 413 self.assertRaises(OverflowError, struct.pack, ">f", big) 414 415 def test_1530559(self): 416 # SF bug 1530559. struct.pack raises TypeError where it used to convert. 417 for endian in ('', '>', '<'): 418 for fmt in integer_codes: 419 self.check_float_coerce(endian + fmt, 1.0) 420 self.check_float_coerce(endian + fmt, 1.5) 421 422 def test_unpack_from(self, cls=str): 423 data = cls('abcd01234') 424 fmt = '4s' 425 s = struct.Struct(fmt) 426 427 self.assertEqual(s.unpack_from(data), ('abcd',)) 428 self.assertEqual(struct.unpack_from(fmt, data), ('abcd',)) 429 for i in xrange(6): 430 self.assertEqual(s.unpack_from(data, i), (data[i:i+4],)) 431 self.assertEqual(struct.unpack_from(fmt, data, i), (data[i:i+4],)) 432 for i in xrange(6, len(data) + 1): 433 self.assertRaises(struct.error, s.unpack_from, data, i) 434 self.assertRaises(struct.error, struct.unpack_from, fmt, data, i) 435 436 def test_pack_into(self): 437 test_string = 'Reykjavik rocks, eow!' 438 writable_buf = array.array('c', ' '*100) 439 fmt = '21s' 440 s = struct.Struct(fmt) 441 442 # Test without offset 443 s.pack_into(writable_buf, 0, test_string) 444 from_buf = writable_buf.tostring()[:len(test_string)] 445 self.assertEqual(from_buf, test_string) 446 447 # Test with offset. 448 s.pack_into(writable_buf, 10, test_string) 449 from_buf = writable_buf.tostring()[:len(test_string)+10] 450 self.assertEqual(from_buf, test_string[:10] + test_string) 451 452 # Go beyond boundaries. 453 small_buf = array.array('c', ' '*10) 454 self.assertRaises((ValueError, struct.error), s.pack_into, small_buf, 0, 455 test_string) 456 self.assertRaises((ValueError, struct.error), s.pack_into, small_buf, 2, 457 test_string) 458 459 # Test bogus offset (issue 3694) 460 sb = small_buf 461 self.assertRaises((TypeError, struct.error), struct.pack_into, b'', sb, 462 None) 463 464 def test_pack_into_fn(self): 465 test_string = 'Reykjavik rocks, eow!' 466 writable_buf = array.array('c', ' '*100) 467 fmt = '21s' 468 pack_into = lambda *args: struct.pack_into(fmt, *args) 469 470 # Test without offset. 471 pack_into(writable_buf, 0, test_string) 472 from_buf = writable_buf.tostring()[:len(test_string)] 473 self.assertEqual(from_buf, test_string) 474 475 # Test with offset. 476 pack_into(writable_buf, 10, test_string) 477 from_buf = writable_buf.tostring()[:len(test_string)+10] 478 self.assertEqual(from_buf, test_string[:10] + test_string) 479 480 # Go beyond boundaries. 481 small_buf = array.array('c', ' '*10) 482 self.assertRaises((ValueError, struct.error), pack_into, small_buf, 0, 483 test_string) 484 self.assertRaises((ValueError, struct.error), pack_into, small_buf, 2, 485 test_string) 486 487 def test_unpack_with_buffer(self): 488 with check_py3k_warnings(("buffer.. not supported in 3.x", 489 DeprecationWarning)): 490 # SF bug 1563759: struct.unpack doesn't support buffer protocol objects 491 data1 = array.array('B', '\x12\x34\x56\x78') 492 data2 = buffer('......\x12\x34\x56\x78......', 6, 4) 493 for data in [data1, data2]: 494 value, = struct.unpack('>I', data) 495 self.assertEqual(value, 0x12345678) 496 497 self.test_unpack_from(cls=buffer) 498 499 def test_unpack_with_memoryview(self): 500 # Bug 10212: struct.unpack doesn't support new buffer protocol objects 501 data1 = memoryview('\x12\x34\x56\x78') 502 for data in [data1,]: 503 value, = struct.unpack('>I', data) 504 self.assertEqual(value, 0x12345678) 505 self.test_unpack_from(cls=memoryview) 506 507 def test_bool(self): 508 class ExplodingBool(object): 509 def __nonzero__(self): 510 raise IOError 511 for prefix in tuple("<>!=")+('',): 512 false = (), [], [], '', 0 513 true = [1], 'test', 5, -1, 0xffffffffL+1, 0xffffffff//2 514 515 falseFormat = prefix + '?' * len(false) 516 packedFalse = struct.pack(falseFormat, *false) 517 unpackedFalse = struct.unpack(falseFormat, packedFalse) 518 519 trueFormat = prefix + '?' * len(true) 520 packedTrue = struct.pack(trueFormat, *true) 521 unpackedTrue = struct.unpack(trueFormat, packedTrue) 522 523 self.assertEqual(len(true), len(unpackedTrue)) 524 self.assertEqual(len(false), len(unpackedFalse)) 525 526 for t in unpackedFalse: 527 self.assertFalse(t) 528 for t in unpackedTrue: 529 self.assertTrue(t) 530 531 packed = struct.pack(prefix+'?', 1) 532 533 self.assertEqual(len(packed), struct.calcsize(prefix+'?')) 534 535 if len(packed) != 1: 536 self.assertFalse(prefix, msg='encoded bool is not one byte: %r' 537 %packed) 538 539 self.assertRaises(IOError, struct.pack, prefix + '?', 540 ExplodingBool()) 541 542 for c in [b'\x01', b'\x7f', b'\xff', b'\x0f', b'\xf0']: 543 self.assertTrue(struct.unpack('>?', c)[0]) 544 545 @unittest.skipUnless(IS32BIT, "Specific to 32bit machines") 546 def test_crasher(self): 547 self.assertRaises(MemoryError, struct.pack, "357913941c", "a") 548 549 def test_count_overflow(self): 550 hugecount = '{}b'.format(sys.maxsize+1) 551 self.assertRaises(struct.error, struct.calcsize, hugecount) 552 553 hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2) 554 self.assertRaises(struct.error, struct.calcsize, hugecount2) 555 556 def check_sizeof(self, format_str, number_of_codes): 557 # The size of 'PyStructObject' 558 totalsize = support.calcobjsize('5P') 559 # The size taken up by the 'formatcode' dynamic array 560 totalsize += struct.calcsize('3P') * (number_of_codes + 1) 561 support.check_sizeof(self, struct.Struct(format_str), totalsize) 562 563 @support.cpython_only 564 def test__sizeof__(self): 565 for code in integer_codes: 566 self.check_sizeof(code, 1) 567 self.check_sizeof('BHILfdspP', 9) 568 self.check_sizeof('B' * 1234, 1234) 569 self.check_sizeof('fd', 2) 570 self.check_sizeof('xxxxxxxxxxxxxx', 0) 571 self.check_sizeof('100H', 100) 572 self.check_sizeof('187s', 1) 573 self.check_sizeof('20p', 1) 574 self.check_sizeof('0s', 1) 575 self.check_sizeof('0c', 0) 576 577 def test_main(): 578 support.run_unittest(StructTest) 579 580 if __name__ == '__main__': 581 test_main() 582