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