Skip to content

Commit fc7361f

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

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

metacov.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ exclude_lines =
7878
# Exclude an entire file.
7979
\A(?s:.*# pragma: exclude file from coverage.*)\Z
8080

81+
# Ad-hoc running code.
82+
^if __name__ == "__main__":
83+
8184
partial_branches =
8285
pragma: part covered
8386
# A for-loop that always hits its break statement

tests/strategies.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3+
4+
"""Hypothesis strategies for use in tests."""
5+
6+
from __future__ import annotations
7+
8+
from hypothesis import strategies as st
9+
10+
11+
# Leaf types, all hashable. Some are limited to the kinds of values coverage
12+
# deals with, to focus Hypothesis.
13+
hashable_leaves = [
14+
st.none(),
15+
st.booleans(),
16+
st.integers(min_value=-1000, max_value=10_000_000),
17+
st.floats(min_value=-100, max_value=100, allow_nan=False, allow_infinity=False).map(
18+
lambda x: 0.0 if x == 0.0 else x
19+
),
20+
st.text(max_size=10),
21+
st.binary(max_size=10),
22+
]
23+
24+
25+
def tuples_of(strat: st.SearchStrategy) -> st.SearchStrategy:
26+
"""Make a strategy for tuples of some other strategy."""
27+
return st.lists(strat, max_size=3).map(tuple)
28+
29+
30+
# List of strategies that produce hashable data.
31+
hashable = hashable_leaves + [tuples_of(s) for s in hashable_leaves]
32+
33+
34+
def nested_dicts_of(s: st.SearchStrategy) -> st.SearchStrategy:
35+
"""Make a strategy to produce recursive dicts with leaves from the `s` strategy."""
36+
return st.recursive(
37+
s,
38+
lambda children: st.dictionaries(st.text(max_size=10), children, max_size=3),
39+
max_leaves=10,
40+
)
41+
42+
43+
# Schema: a strategy that generates strategies for nested data.
44+
nested_data_schemas = st.recursive(
45+
st.sampled_from(hashable),
46+
lambda children: st.one_of(
47+
children.map(lambda s: st.lists(s, max_size=5)),
48+
children.map(tuples_of),
49+
st.sampled_from(hashable).map(lambda s: st.sets(s, max_size=10)),
50+
children.map(nested_dicts_of),
51+
),
52+
max_leaves=3,
53+
)
54+
55+
if __name__ == "__main__":
56+
import warnings
57+
from hypothesis.errors import NonInteractiveExampleWarning
58+
59+
warnings.filterwarnings("ignore", category=NonInteractiveExampleWarning)
60+
61+
for _ in range(50):
62+
print(repr(nested_data_schemas.example().example()))

tests/test_misc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
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
1516
from coverage.misc import Hasher, substitute_variables, import_third_party
1617
from coverage.misc import human_sorted, human_sorted_items, stdout_link
1718

1819
from tests.coveragetest import CoverageTest
20+
from tests.strategies import nested_data_schemas
1921

2022

2123
class HasherTest(CoverageTest):
@@ -87,6 +89,22 @@ def test_set_hashing(self) -> None:
8789
assert h1.digest() == h2.digest()
8890
assert h1.digest() != h3.digest()
8991

92+
# Use nested_data_schema to generate data schemas, then use it twice in
93+
# the test to get two chunks of data with the "same shape" but different
94+
# data.
95+
@given(nested_data_schemas.flatmap(lambda s: st.tuples(s, s)))
96+
def test_same_schema(self, pair: tuple[st.SearchStrategy, st.SearchStrategy]) -> None:
97+
data1, data2 = pair
98+
h1, h2 = Hasher(), Hasher()
99+
h1.update(data1)
100+
h2.update(data2)
101+
if data1 == data2:
102+
assert h1.hexdigest() == h2.hexdigest()
103+
assert h1.digest() == h2.digest()
104+
else:
105+
assert h1.hexdigest() != h2.hexdigest()
106+
assert h1.digest() != h2.digest()
107+
90108

91109
class RemoveFileTest(CoverageTest):
92110
"""Tests of misc.file_be_gone."""

0 commit comments

Comments
 (0)