Skip to content

Commit 352ab49

Browse files
committed
gh-107862: Use Hypothesis for the zlib checksum combination tests
These ran 1000 iterations over random 32 and 64 byte inputs with unseeded random initial values, so a failure could not be reproduced. Hypothesis gives the same coverage with shrinking and a persistent example database. The Adler-32 initial values are now restricted to valid checksums (both 16-bit sums below 65521). adler32_combine() is only defined on those, and the old test only avoided the invalid region by chance, since random.randint(1, 0x80000000) rarely produced a low half of 65521 or more.
1 parent f65beb0 commit 352ab49

1 file changed

Lines changed: 64 additions & 36 deletions

File tree

‎Lib/test/test_zlib.py‎

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from test import support
33
from test.support import import_helper
4+
from test.support.hypothesis_helper import hypothesis
45
import binascii
56
import copy
67
import pickle
@@ -182,20 +183,18 @@ def test_same_as_binascii_crc32(self):
182183
class ChecksumCombineMixin:
183184
"""Mixin class for testing checksum combination."""
184185

185-
N = 1000
186186
default_iv: int
187+
# Initial values that are valid checksums for the algorithm.
188+
iv_strategy: object
187189

188190
def parse_iv(self, iv):
189191
"""Parse an IV value.
190192
191193
- The default IV is returned if *iv* is None.
192-
- A random IV is returned if *iv* is -1.
193194
- Otherwise, *iv* is returned as is.
194195
"""
195196
if iv is None:
196197
return self.default_iv
197-
if iv == -1:
198-
return random.randint(1, 0x80000000)
199198
return iv
200199

201200
def checksum(self, data, init=None):
@@ -220,18 +219,24 @@ def get_random_data(self, data_len, *, iv=None):
220219
checksum = self.checksum(data, init)
221220
return data, init, checksum
222221

223-
def test_combine_empty(self):
224-
for _ in range(self.N):
225-
a, iv, checksum = self.get_random_data(32, iv=-1)
226-
res = self.combine(iv, self.checksum(a), len(a))
227-
self.assertEqual(res, checksum)
228-
229-
def test_combine_no_iv(self):
230-
for _ in range(self.N):
231-
a, _, chk_a = self.get_random_data(32)
232-
b, _, chk_b = self.get_random_data(64)
233-
res = self.combine(chk_a, chk_b, len(b))
234-
self.assertEqual(res, self.checksum(a + b))
222+
@hypothesis.given(a=hypothesis.strategies.binary(max_size=256),
223+
iv=hypothesis.strategies.runner().flatmap(
224+
lambda self: self.iv_strategy))
225+
@hypothesis.example(a=b'', iv=0)
226+
@hypothesis.example(a=b'\x00' * 32, iv=1)
227+
@hypothesis.example(a=bytes(range(256)), iv=0x7fffffff)
228+
def test_combine_empty(self, a, iv):
229+
res = self.combine(iv, self.checksum(a), len(a))
230+
self.assertEqual(res, self.checksum(a, iv))
231+
232+
@hypothesis.given(a=hypothesis.strategies.binary(max_size=256),
233+
b=hypothesis.strategies.binary(max_size=256))
234+
@hypothesis.example(a=b'', b=b'')
235+
@hypothesis.example(a=b'\x00' * 32, b=b'\xff' * 64)
236+
@hypothesis.example(a=bytes(range(256)), b=bytes(range(255, -1, -1)))
237+
def test_combine_no_iv(self, a, b):
238+
res = self.combine(self.checksum(a), self.checksum(b), len(b))
239+
self.assertEqual(res, self.checksum(a + b))
235240

236241
def test_combine_no_iv_invalid_length(self):
237242
a, _, chk_a = self.get_random_data(32)
@@ -246,31 +251,48 @@ def test_combine_no_iv_invalid_length(self):
246251
self.assertRaises(OverflowError, self.combine, 0, 0, 2**1000)
247252
self.assertRaises(OverflowError, self.combine, 0, 0, -2**1000)
248253

249-
def test_combine_with_iv(self):
250-
for _ in range(self.N):
251-
a, iv_a, chk_a_with_iv = self.get_random_data(32, iv=-1)
252-
chk_a_no_iv = self.checksum(a)
253-
b, iv_b, chk_b_with_iv = self.get_random_data(64, iv=-1)
254-
chk_b_no_iv = self.checksum(b)
255-
256-
# We can represent c = COMBINE(CHK(a, iv_a), CHK(b, iv_b)) as:
257-
#
258-
# c = CHK(CHK(b'', iv_a) + CHK(a) + CHK(b'', iv_b) + CHK(b))
259-
# = COMBINE(
260-
# COMBINE(CHK(b'', iv_a), CHK(a)),
261-
# COMBINE(CHK(b'', iv_b), CHK(b)),
262-
# )
263-
# = COMBINE(COMBINE(iv_a, CHK(a)), COMBINE(iv_b, CHK(b)))
264-
tmp0 = self.combine(iv_a, chk_a_no_iv, len(a))
265-
tmp1 = self.combine(iv_b, chk_b_no_iv, len(b))
266-
expected = self.combine(tmp0, tmp1, len(b))
267-
checksum = self.combine(chk_a_with_iv, chk_b_with_iv, len(b))
268-
self.assertEqual(checksum, expected)
254+
@hypothesis.given(a=hypothesis.strategies.binary(max_size=256),
255+
b=hypothesis.strategies.binary(max_size=256),
256+
iv_a=hypothesis.strategies.runner().flatmap(
257+
lambda self: self.iv_strategy),
258+
iv_b=hypothesis.strategies.runner().flatmap(
259+
lambda self: self.iv_strategy))
260+
@hypothesis.example(a=b'', b=b'', iv_a=0, iv_b=0)
261+
@hypothesis.example(a=b'\x00' * 32, b=b'\xff' * 64, iv_a=1,
262+
iv_b=0x7fffffff)
263+
@hypothesis.example(a=bytes(range(256)), b=bytes(range(255, -1, -1)),
264+
iv_a=0x12345678, iv_b=0x0badf00d)
265+
def test_combine_with_iv(self, a, b, iv_a, iv_b):
266+
chk_a_with_iv = self.checksum(a, iv_a)
267+
chk_a_no_iv = self.checksum(a)
268+
chk_b_with_iv = self.checksum(b, iv_b)
269+
chk_b_no_iv = self.checksum(b)
270+
271+
# We can represent c = COMBINE(CHK(a, iv_a), CHK(b, iv_b)) as:
272+
#
273+
# c = CHK(CHK(b'', iv_a) + CHK(a) + CHK(b'', iv_b) + CHK(b))
274+
# = COMBINE(
275+
# COMBINE(CHK(b'', iv_a), CHK(a)),
276+
# COMBINE(CHK(b'', iv_b), CHK(b)),
277+
# )
278+
# = COMBINE(COMBINE(iv_a, CHK(a)), COMBINE(iv_b, CHK(b)))
279+
tmp0 = self.combine(iv_a, chk_a_no_iv, len(a))
280+
tmp1 = self.combine(iv_b, chk_b_no_iv, len(b))
281+
expected = self.combine(tmp0, tmp1, len(b))
282+
checksum = self.combine(chk_a_with_iv, chk_b_with_iv, len(b))
283+
self.assertEqual(checksum, expected)
284+
285+
286+
def _adler32_value(halves):
287+
"""Build a valid Adler-32 value from its two 16-bit sums."""
288+
s1, s2 = halves
289+
return (s2 << 16) | s1
269290

270291

271292
class CRC32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
272293

273294
default_iv = 0
295+
iv_strategy = hypothesis.strategies.integers(0, 0xffffffff)
274296

275297
def _checksum(self, data, init):
276298
return zlib.crc32(data, init)
@@ -282,6 +304,12 @@ def combine(self, a, b, blen):
282304
class Adler32CombineTestCase(ChecksumCombineMixin, unittest.TestCase):
283305

284306
default_iv = 1
307+
# Both 16-bit sums of an Adler-32 value are reduced modulo 65521, and
308+
# adler32_combine() is only defined on such values.
309+
iv_strategy = hypothesis.strategies.tuples(
310+
hypothesis.strategies.integers(0, 65520),
311+
hypothesis.strategies.integers(0, 65520),
312+
).map(_adler32_value)
285313

286314
def _checksum(self, data, init):
287315
return zlib.adler32(data, init)

0 commit comments

Comments
 (0)