Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | from test.test_support import TestFailed, verbose, verify |
2 | import struct | |
3 | ||
4 | import sys | |
5 | ISBIGENDIAN = sys.byteorder == "big" | |
6 | del sys | |
7 | verify((struct.pack('=i', 1)[0] == chr(0)) == ISBIGENDIAN, | |
8 | "bigendian determination appears wrong") | |
9 | ||
10 | def string_reverse(s): | |
11 | chars = list(s) | |
12 | chars.reverse() | |
13 | return "".join(chars) | |
14 | ||
15 | def bigendian_to_native(value): | |
16 | if ISBIGENDIAN: | |
17 | return value | |
18 | else: | |
19 | return string_reverse(value) | |
20 | ||
21 | def simple_err(func, *args): | |
22 | try: | |
23 | func(*args) | |
24 | except struct.error: | |
25 | pass | |
26 | else: | |
27 | raise TestFailed, "%s%s did not raise struct.error" % ( | |
28 | func.__name__, args) | |
29 | ||
30 | def any_err(func, *args): | |
31 | try: | |
32 | func(*args) | |
33 | except (struct.error, OverflowError, TypeError): | |
34 | pass | |
35 | else: | |
36 | raise TestFailed, "%s%s did not raise error" % ( | |
37 | func.__name__, args) | |
38 | ||
39 | ||
40 | simple_err(struct.calcsize, 'Z') | |
41 | ||
42 | sz = struct.calcsize('i') | |
43 | if sz * 3 != struct.calcsize('iii'): | |
44 | raise TestFailed, 'inconsistent sizes' | |
45 | ||
46 | fmt = 'cbxxxxxxhhhhiillffd' | |
47 | fmt3 = '3c3b18x12h6i6l6f3d' | |
48 | sz = struct.calcsize(fmt) | |
49 | sz3 = struct.calcsize(fmt3) | |
50 | if sz * 3 != sz3: | |
51 | raise TestFailed, 'inconsistent sizes (3*%r -> 3*%d = %d, %r -> %d)' % ( | |
52 | fmt, sz, 3*sz, fmt3, sz3) | |
53 | ||
54 | simple_err(struct.pack, 'iii', 3) | |
55 | simple_err(struct.pack, 'i', 3, 3, 3) | |
56 | simple_err(struct.pack, 'i', 'foo') | |
57 | simple_err(struct.pack, 'P', 'foo') | |
58 | simple_err(struct.unpack, 'd', 'flap') | |
59 | s = struct.pack('ii', 1, 2) | |
60 | simple_err(struct.unpack, 'iii', s) | |
61 | simple_err(struct.unpack, 'i', s) | |
62 | ||
63 | c = 'a' | |
64 | b = 1 | |
65 | h = 255 | |
66 | i = 65535 | |
67 | l = 65536 | |
68 | f = 3.1415 | |
69 | d = 3.1415 | |
70 | ||
71 | for prefix in ('', '@', '<', '>', '=', '!'): | |
72 | for format in ('xcbhilfd', 'xcBHILfd'): | |
73 | format = prefix + format | |
74 | if verbose: | |
75 | print "trying:", format | |
76 | s = struct.pack(format, c, b, h, i, l, f, d) | |
77 | cp, bp, hp, ip, lp, fp, dp = struct.unpack(format, s) | |
78 | if (cp != c or bp != b or hp != h or ip != i or lp != l or | |
79 | int(100 * fp) != int(100 * f) or int(100 * dp) != int(100 * d)): | |
80 | # ^^^ calculate only to two decimal places | |
81 | raise TestFailed, "unpack/pack not transitive (%s, %s)" % ( | |
82 | str(format), str((cp, bp, hp, ip, lp, fp, dp))) | |
83 | ||
84 | # Test some of the new features in detail | |
85 | ||
86 | # (format, argument, big-endian result, little-endian result, asymmetric) | |
87 | tests = [ | |
88 | ('c', 'a', 'a', 'a', 0), | |
89 | ('xc', 'a', '\0a', '\0a', 0), | |
90 | ('cx', 'a', 'a\0', 'a\0', 0), | |
91 | ('s', 'a', 'a', 'a', 0), | |
92 | ('0s', 'helloworld', '', '', 1), | |
93 | ('1s', 'helloworld', 'h', 'h', 1), | |
94 | ('9s', 'helloworld', 'helloworl', 'helloworl', 1), | |
95 | ('10s', 'helloworld', 'helloworld', 'helloworld', 0), | |
96 | ('11s', 'helloworld', 'helloworld\0', 'helloworld\0', 1), | |
97 | ('20s', 'helloworld', 'helloworld'+10*'\0', 'helloworld'+10*'\0', 1), | |
98 | ('b', 7, '\7', '\7', 0), | |
99 | ('b', -7, '\371', '\371', 0), | |
100 | ('B', 7, '\7', '\7', 0), | |
101 | ('B', 249, '\371', '\371', 0), | |
102 | ('h', 700, '\002\274', '\274\002', 0), | |
103 | ('h', -700, '\375D', 'D\375', 0), | |
104 | ('H', 700, '\002\274', '\274\002', 0), | |
105 | ('H', 0x10000-700, '\375D', 'D\375', 0), | |
106 | ('i', 70000000, '\004,\035\200', '\200\035,\004', 0), | |
107 | ('i', -70000000, '\373\323\342\200', '\200\342\323\373', 0), | |
108 | ('I', 70000000L, '\004,\035\200', '\200\035,\004', 0), | |
109 | ('I', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), | |
110 | ('l', 70000000, '\004,\035\200', '\200\035,\004', 0), | |
111 | ('l', -70000000, '\373\323\342\200', '\200\342\323\373', 0), | |
112 | ('L', 70000000L, '\004,\035\200', '\200\035,\004', 0), | |
113 | ('L', 0x100000000L-70000000, '\373\323\342\200', '\200\342\323\373', 0), | |
114 | ('f', 2.0, '@\000\000\000', '\000\000\000@', 0), | |
115 | ('d', 2.0, '@\000\000\000\000\000\000\000', | |
116 | '\000\000\000\000\000\000\000@', 0), | |
117 | ('f', -2.0, '\300\000\000\000', '\000\000\000\300', 0), | |
118 | ('d', -2.0, '\300\000\000\000\000\000\000\000', | |
119 | '\000\000\000\000\000\000\000\300', 0), | |
120 | ] | |
121 | ||
122 | for fmt, arg, big, lil, asy in tests: | |
123 | if verbose: | |
124 | print "%r %r %r %r" % (fmt, arg, big, lil) | |
125 | for (xfmt, exp) in [('>'+fmt, big), ('!'+fmt, big), ('<'+fmt, lil), | |
126 | ('='+fmt, ISBIGENDIAN and big or lil)]: | |
127 | res = struct.pack(xfmt, arg) | |
128 | if res != exp: | |
129 | raise TestFailed, "pack(%r, %r) -> %r # expected %r" % ( | |
130 | fmt, arg, res, exp) | |
131 | n = struct.calcsize(xfmt) | |
132 | if n != len(res): | |
133 | raise TestFailed, "calcsize(%r) -> %d # expected %d" % ( | |
134 | xfmt, n, len(res)) | |
135 | rev = struct.unpack(xfmt, res)[0] | |
136 | if rev != arg and not asy: | |
137 | raise TestFailed, "unpack(%r, %r) -> (%r,) # expected (%r,)" % ( | |
138 | fmt, res, rev, arg) | |
139 | ||
140 | ########################################################################### | |
141 | # Simple native q/Q tests. | |
142 | ||
143 | has_native_qQ = 1 | |
144 | try: | |
145 | struct.pack("q", 5) | |
146 | except struct.error: | |
147 | has_native_qQ = 0 | |
148 | ||
149 | if verbose: | |
150 | print "Platform has native q/Q?", has_native_qQ and "Yes." or "No." | |
151 | ||
152 | any_err(struct.pack, "Q", -1) # can't pack -1 as unsigned regardless | |
153 | simple_err(struct.pack, "q", "a") # can't pack string as 'q' regardless | |
154 | simple_err(struct.pack, "Q", "a") # ditto, but 'Q' | |
155 | ||
156 | def test_native_qQ(): | |
157 | bytes = struct.calcsize('q') | |
158 | # The expected values here are in big-endian format, primarily because | |
159 | # I'm on a little-endian machine and so this is the clearest way (for | |
160 | # me) to force the code to get exercised. | |
161 | for format, input, expected in ( | |
162 | ('q', -1, '\xff' * bytes), | |
163 | ('q', 0, '\x00' * bytes), | |
164 | ('Q', 0, '\x00' * bytes), | |
165 | ('q', 1L, '\x00' * (bytes-1) + '\x01'), | |
166 | ('Q', (1L << (8*bytes))-1, '\xff' * bytes), | |
167 | ('q', (1L << (8*bytes-1))-1, '\x7f' + '\xff' * (bytes - 1))): | |
168 | got = struct.pack(format, input) | |
169 | native_expected = bigendian_to_native(expected) | |
170 | verify(got == native_expected, | |
171 | "%r-pack of %r gave %r, not %r" % | |
172 | (format, input, got, native_expected)) | |
173 | retrieved = struct.unpack(format, got)[0] | |
174 | verify(retrieved == input, | |
175 | "%r-unpack of %r gave %r, not %r" % | |
176 | (format, got, retrieved, input)) | |
177 | ||
178 | if has_native_qQ: | |
179 | test_native_qQ() | |
180 | ||
181 | ########################################################################### | |
182 | # Standard integer tests (bBhHiIlLqQ). | |
183 | ||
184 | import binascii | |
185 | ||
186 | class IntTester: | |
187 | ||
188 | # XXX Most std integer modes fail to test for out-of-range. | |
189 | # The "i" and "l" codes appear to range-check OK on 32-bit boxes, but | |
190 | # fail to check correctly on some 64-bit ones (Tru64 Unix + Compaq C | |
191 | # reported by Mark Favas). | |
192 | BUGGY_RANGE_CHECK = "bBhHiIlL" | |
193 | ||
194 | def __init__(self, formatpair, bytesize): | |
195 | assert len(formatpair) == 2 | |
196 | self.formatpair = formatpair | |
197 | for direction in "<>!=": | |
198 | for code in formatpair: | |
199 | format = direction + code | |
200 | verify(struct.calcsize(format) == bytesize) | |
201 | self.bytesize = bytesize | |
202 | self.bitsize = bytesize * 8 | |
203 | self.signed_code, self.unsigned_code = formatpair | |
204 | self.unsigned_min = 0 | |
205 | self.unsigned_max = 2L**self.bitsize - 1 | |
206 | self.signed_min = -(2L**(self.bitsize-1)) | |
207 | self.signed_max = 2L**(self.bitsize-1) - 1 | |
208 | ||
209 | def test_one(self, x, pack=struct.pack, | |
210 | unpack=struct.unpack, | |
211 | unhexlify=binascii.unhexlify): | |
212 | if verbose: | |
213 | print "trying std", self.formatpair, "on", x, "==", hex(x) | |
214 | ||
215 | # Try signed. | |
216 | code = self.signed_code | |
217 | if self.signed_min <= x <= self.signed_max: | |
218 | # Try big-endian. | |
219 | expected = long(x) | |
220 | if x < 0: | |
221 | expected += 1L << self.bitsize | |
222 | assert expected > 0 | |
223 | expected = hex(expected)[2:-1] # chop "0x" and trailing 'L' | |
224 | if len(expected) & 1: | |
225 | expected = "0" + expected | |
226 | expected = unhexlify(expected) | |
227 | expected = "\x00" * (self.bytesize - len(expected)) + expected | |
228 | ||
229 | # Pack work? | |
230 | format = ">" + code | |
231 | got = pack(format, x) | |
232 | verify(got == expected, | |
233 | "'%s'-pack of %r gave %r, not %r" % | |
234 | (format, x, got, expected)) | |
235 | ||
236 | # Unpack work? | |
237 | retrieved = unpack(format, got)[0] | |
238 | verify(x == retrieved, | |
239 | "'%s'-unpack of %r gave %r, not %r" % | |
240 | (format, got, retrieved, x)) | |
241 | ||
242 | # Adding any byte should cause a "too big" error. | |
243 | any_err(unpack, format, '\x01' + got) | |
244 | ||
245 | # Try little-endian. | |
246 | format = "<" + code | |
247 | expected = string_reverse(expected) | |
248 | ||
249 | # Pack work? | |
250 | got = pack(format, x) | |
251 | verify(got == expected, | |
252 | "'%s'-pack of %r gave %r, not %r" % | |
253 | (format, x, got, expected)) | |
254 | ||
255 | # Unpack work? | |
256 | retrieved = unpack(format, got)[0] | |
257 | verify(x == retrieved, | |
258 | "'%s'-unpack of %r gave %r, not %r" % | |
259 | (format, got, retrieved, x)) | |
260 | ||
261 | # Adding any byte should cause a "too big" error. | |
262 | any_err(unpack, format, '\x01' + got) | |
263 | ||
264 | else: | |
265 | # x is out of range -- verify pack realizes that. | |
266 | if code in self.BUGGY_RANGE_CHECK: | |
267 | if verbose: | |
268 | print "Skipping buggy range check for code", code | |
269 | else: | |
270 | any_err(pack, ">" + code, x) | |
271 | any_err(pack, "<" + code, x) | |
272 | ||
273 | # Much the same for unsigned. | |
274 | code = self.unsigned_code | |
275 | if self.unsigned_min <= x <= self.unsigned_max: | |
276 | # Try big-endian. | |
277 | format = ">" + code | |
278 | expected = long(x) | |
279 | expected = hex(expected)[2:-1] # chop "0x" and trailing 'L' | |
280 | if len(expected) & 1: | |
281 | expected = "0" + expected | |
282 | expected = unhexlify(expected) | |
283 | expected = "\x00" * (self.bytesize - len(expected)) + expected | |
284 | ||
285 | # Pack work? | |
286 | got = pack(format, x) | |
287 | verify(got == expected, | |
288 | "'%s'-pack of %r gave %r, not %r" % | |
289 | (format, x, got, expected)) | |
290 | ||
291 | # Unpack work? | |
292 | retrieved = unpack(format, got)[0] | |
293 | verify(x == retrieved, | |
294 | "'%s'-unpack of %r gave %r, not %r" % | |
295 | (format, got, retrieved, x)) | |
296 | ||
297 | # Adding any byte should cause a "too big" error. | |
298 | any_err(unpack, format, '\x01' + got) | |
299 | ||
300 | # Try little-endian. | |
301 | format = "<" + code | |
302 | expected = string_reverse(expected) | |
303 | ||
304 | # Pack work? | |
305 | got = pack(format, x) | |
306 | verify(got == expected, | |
307 | "'%s'-pack of %r gave %r, not %r" % | |
308 | (format, x, got, expected)) | |
309 | ||
310 | # Unpack work? | |
311 | retrieved = unpack(format, got)[0] | |
312 | verify(x == retrieved, | |
313 | "'%s'-unpack of %r gave %r, not %r" % | |
314 | (format, got, retrieved, x)) | |
315 | ||
316 | # Adding any byte should cause a "too big" error. | |
317 | any_err(unpack, format, '\x01' + got) | |
318 | ||
319 | else: | |
320 | # x is out of range -- verify pack realizes that. | |
321 | if code in self.BUGGY_RANGE_CHECK: | |
322 | if verbose: | |
323 | print "Skipping buggy range check for code", code | |
324 | else: | |
325 | any_err(pack, ">" + code, x) | |
326 | any_err(pack, "<" + code, x) | |
327 | ||
328 | def run(self): | |
329 | from random import randrange | |
330 | ||
331 | # Create all interesting powers of 2. | |
332 | values = [] | |
333 | for exp in range(self.bitsize + 3): | |
334 | values.append(1L << exp) | |
335 | ||
336 | # Add some random values. | |
337 | for i in range(self.bitsize): | |
338 | val = 0L | |
339 | for j in range(self.bytesize): | |
340 | val = (val << 8) | randrange(256) | |
341 | values.append(val) | |
342 | ||
343 | # Try all those, and their negations, and +-1 from them. Note | |
344 | # that this tests all power-of-2 boundaries in range, and a few out | |
345 | # of range, plus +-(2**n +- 1). | |
346 | for base in values: | |
347 | for val in -base, base: | |
348 | for incr in -1, 0, 1: | |
349 | x = val + incr | |
350 | try: | |
351 | x = int(x) | |
352 | except OverflowError: | |
353 | pass | |
354 | self.test_one(x) | |
355 | ||
356 | # Some error cases. | |
357 | for direction in "<>": | |
358 | for code in self.formatpair: | |
359 | for badobject in "a string", 3+42j, randrange: | |
360 | any_err(struct.pack, direction + code, badobject) | |
361 | ||
362 | for args in [("bB", 1), | |
363 | ("hH", 2), | |
364 | ("iI", 4), | |
365 | ("lL", 4), | |
366 | ("qQ", 8)]: | |
367 | t = IntTester(*args) | |
368 | t.run() | |
369 | ||
370 | ||
371 | ########################################################################### | |
372 | # The p ("Pascal string") code. | |
373 | ||
374 | def test_p_code(): | |
375 | for code, input, expected, expectedback in [ | |
376 | ('p','abc', '\x00', ''), | |
377 | ('1p', 'abc', '\x00', ''), | |
378 | ('2p', 'abc', '\x01a', 'a'), | |
379 | ('3p', 'abc', '\x02ab', 'ab'), | |
380 | ('4p', 'abc', '\x03abc', 'abc'), | |
381 | ('5p', 'abc', '\x03abc\x00', 'abc'), | |
382 | ('6p', 'abc', '\x03abc\x00\x00', 'abc'), | |
383 | ('1000p', 'x'*1000, '\xff' + 'x'*999, 'x'*255)]: | |
384 | got = struct.pack(code, input) | |
385 | if got != expected: | |
386 | raise TestFailed("pack(%r, %r) == %r but expected %r" % | |
387 | (code, input, got, expected)) | |
388 | (got,) = struct.unpack(code, got) | |
389 | if got != expectedback: | |
390 | raise TestFailed("unpack(%r, %r) == %r but expected %r" % | |
391 | (code, input, got, expectedback)) | |
392 | ||
393 | test_p_code() | |
394 | ||
395 | ||
396 | ########################################################################### | |
397 | # SF bug 705836. "<f" and ">f" had a severe rounding bug, where a carry | |
398 | # from the low-order discarded bits could propagate into the exponent | |
399 | # field, causing the result to be wrong by a factor of 2. | |
400 | ||
401 | def test_705836(): | |
402 | import math | |
403 | ||
404 | for base in range(1, 33): | |
405 | # smaller <- largest representable float less than base. | |
406 | delta = 0.5 | |
407 | while base - delta / 2.0 != base: | |
408 | delta /= 2.0 | |
409 | smaller = base - delta | |
410 | # Packing this rounds away a solid string of trailing 1 bits. | |
411 | packed = struct.pack("<f", smaller) | |
412 | unpacked = struct.unpack("<f", packed)[0] | |
413 | # This failed at base = 2, 4, and 32, with unpacked = 1, 2, and | |
414 | # 16, respectively. | |
415 | verify(base == unpacked) | |
416 | bigpacked = struct.pack(">f", smaller) | |
417 | verify(bigpacked == string_reverse(packed), | |
418 | ">f pack should be byte-reversal of <f pack") | |
419 | unpacked = struct.unpack(">f", bigpacked)[0] | |
420 | verify(base == unpacked) | |
421 | ||
422 | # Largest finite IEEE single. | |
423 | big = (1 << 24) - 1 | |
424 | big = math.ldexp(big, 127 - 23) | |
425 | packed = struct.pack(">f", big) | |
426 | unpacked = struct.unpack(">f", packed)[0] | |
427 | verify(big == unpacked) | |
428 | ||
429 | # The same, but tack on a 1 bit so it rounds up to infinity. | |
430 | big = (1 << 25) - 1 | |
431 | big = math.ldexp(big, 127 - 24) | |
432 | try: | |
433 | packed = struct.pack(">f", big) | |
434 | except OverflowError: | |
435 | pass | |
436 | else: | |
437 | TestFailed("expected OverflowError") | |
438 | ||
439 | test_705836() |