Skip to content

Commit 0a255b8

Browse files
committed
test: Hasher tested with Hypothesis
1 parent 160ad3b commit 0a255b8

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed

tests/test_misc.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from unittest import mock
1010

1111
import pytest
12+
from hypothesis import given, strategies as st
1213

1314
from coverage.exceptions import CoverageException
1415
from coverage.misc import file_be_gone
@@ -17,6 +18,68 @@
1718

1819
from tests.coveragetest import CoverageTest
1920

21+
# Hypothesis for testing Hasher
22+
23+
24+
# Leaf types (all hashable, usable in sets)
25+
hashable_leaves = [
26+
st.none(),
27+
st.booleans(),
28+
st.integers(min_value=-1000, max_value=10_000_000),
29+
st.floats(min_value=-100, max_value=100, allow_nan=False, allow_infinity=False).map(
30+
lambda x: 0.0 if x == 0.0 else x
31+
),
32+
st.text(max_size=10),
33+
st.binary(max_size=10),
34+
]
35+
36+
37+
def tuples_of(strat: st.SearchStrategy) -> st.SearchStrategy:
38+
"""Make a strategy for tuples of some other strategy."""
39+
return st.lists(strat, max_size=3).map(tuple)
40+
41+
42+
hashable_types = hashable_leaves + [tuples_of(s) for s in hashable_leaves]
43+
44+
leaf_strategies = st.sampled_from(hashable_types)
45+
46+
47+
def recur_dicts(s: st.SearchStrategy) -> st.SearchStrategy:
48+
"""Recursive dicts with leaves from the `s` strategy."""
49+
return st.recursive(
50+
s,
51+
lambda children: st.dictionaries(st.text(max_size=10), children, max_size=3),
52+
max_leaves=10,
53+
)
54+
55+
56+
# Schema: a strategy that generates strategies
57+
schema = st.recursive(
58+
leaf_strategies,
59+
lambda children: st.one_of(
60+
children.map(lambda s: st.lists(s, max_size=5)),
61+
children.map(lambda s: st.lists(s, max_size=5).map(tuple)),
62+
st.sampled_from(hashable_types).map(lambda s: st.sets(s, max_size=10)),
63+
st.sampled_from(hashable_types).map(recur_dicts),
64+
),
65+
max_leaves=3,
66+
)
67+
68+
# For ad-hoc testing, you can see data generated like this:
69+
#
70+
# import warnings
71+
# from hypothesis.errors import NonInteractiveExampleWarning
72+
# from tests.test_misc import schema, recur_dicts
73+
#
74+
# warnings.filterwarnings("ignore", category=NonInteractiveExampleWarning)
75+
#
76+
# for _ in range(50):
77+
# print(repr(schema.example().example()))
78+
79+
80+
# Two instances with the same schema
81+
paired_data = schema.flatmap(lambda s: st.tuples(s, s))
82+
2083

2184
class HasherTest(CoverageTest):
2285
"""Test our wrapper of fingerprint hashing."""
@@ -87,6 +150,19 @@ def test_set_hashing(self) -> None:
87150
assert h1.digest() == h2.digest()
88151
assert h1.digest() != h3.digest()
89152

153+
@given(paired_data)
154+
def test_same_schema(self, pair: tuple[st.SearchStrategy, st.SearchStrategy]) -> None:
155+
data1, data2 = pair
156+
h1, h2 = Hasher(), Hasher()
157+
h1.update(data1)
158+
h2.update(data2)
159+
if data1 == data2:
160+
assert h1.hexdigest() == h2.hexdigest()
161+
assert h1.digest() == h2.digest()
162+
else:
163+
assert h1.hexdigest() != h2.hexdigest()
164+
assert h1.digest() != h2.digest()
165+
90166

91167
class RemoveFileTest(CoverageTest):
92168
"""Tests of misc.file_be_gone."""

0 commit comments

Comments
 (0)