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