Skip to content

Commit d57d6b0

Browse files
committed
hypothesis: add end-to-end test for registering callable.
1 parent bdccc04 commit d57d6b0

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
Test that we can register a custom strategy for callables.
3+
4+
We use the strategy to generates `Maybe`s and `Result`s for
5+
`Kind1[_F_Applicative, _SecondType]`.
6+
7+
Without the custom strategy, we would simply get instances of `_Wrapper`, even
8+
though it is not an `Applicative`, because `_Wrapper` is a subtype of `KindN`
9+
and `hypothesis` doesn't know about that `KindN` is just emulating HKTs.
10+
"""
11+
12+
from abc import abstractmethod
13+
from collections.abc import Callable, Sequence
14+
from typing import (
15+
Any,
16+
ClassVar,
17+
Generic,
18+
TypeAlias,
19+
TypeVar,
20+
final,
21+
get_args,
22+
get_origin,
23+
)
24+
25+
from hypothesis import strategies as st
26+
from typing_extensions import Never
27+
28+
from returns.contrib.hypothesis.containers import strategy_from_container
29+
from returns.contrib.hypothesis.laws import check_all_laws
30+
from returns.contrib.hypothesis.type_resolver import StrategyFactory
31+
from returns.interfaces.applicative import ApplicativeN
32+
from returns.interfaces.specific.maybe import MaybeLike2
33+
from returns.maybe import Maybe
34+
from returns.primitives.asserts import assert_equal
35+
from returns.primitives.container import BaseContainer
36+
from returns.primitives.hkt import Kind1, KindN, SupportsKind1
37+
from returns.primitives.laws import (
38+
Law,
39+
Law2,
40+
Lawful,
41+
LawSpecDef,
42+
law_definition,
43+
)
44+
from returns.result import Result
45+
46+
_FirstType = TypeVar('_FirstType')
47+
_SecondType = TypeVar('_SecondType')
48+
_ThirdType = TypeVar('_ThirdType')
49+
50+
_ValueType = TypeVar('_ValueType')
51+
_F_Applicative = TypeVar('_F_Applicative', bound=ApplicativeN)
52+
53+
54+
@final
55+
class _LawSpec(LawSpecDef):
56+
"""Contrived laws for `_SomeIdentityN`."""
57+
58+
__slots__ = ()
59+
60+
@law_definition
61+
def idempotence_law(
62+
some_identity: '_SomeIdentityN',
63+
make_container: Callable[[], Kind1[_F_Applicative, _SecondType]],
64+
) -> None:
65+
"""Applying twice should give the same value as applying once.
66+
67+
NOTE: We use a `Callable` to generate the container so that we can
68+
override the strategy easily. Overriding the strategy directly for
69+
`KindN` is not currently possible.
70+
"""
71+
container = make_container()
72+
assert_equal(
73+
some_identity.do_nothing(some_identity.do_nothing(container)),
74+
some_identity.do_nothing(container),
75+
)
76+
77+
78+
class _SomeIdentityN(
79+
Lawful['_SomeIdentityN[_FirstType, _SecondType, _ThirdType]'],
80+
Generic[_FirstType, _SecondType, _ThirdType],
81+
):
82+
"""Dummy interface that does nothing to an `Applicative` container."""
83+
84+
__slots__ = ()
85+
86+
_laws: ClassVar[Sequence[Law]] = (Law2(_LawSpec.idempotence_law),)
87+
88+
@abstractmethod # noqa: WPS125
89+
def do_nothing(
90+
self,
91+
container: Kind1[_F_Applicative, _ValueType],
92+
) -> Kind1[_F_Applicative, _ValueType]:
93+
"""No-op method that returns the container."""
94+
95+
96+
_SomeIdentity1: TypeAlias = _SomeIdentityN[_FirstType, Never, Never]
97+
98+
99+
class _Wrapper(
100+
BaseContainer,
101+
SupportsKind1['_Wrapper', _FirstType],
102+
_SomeIdentity1[_FirstType],
103+
):
104+
"""Simple instance of `_SomeIdentityN`."""
105+
106+
_inner_value: _FirstType
107+
108+
def __init__(self, inner_value: _FirstType) -> None:
109+
super().__init__(inner_value)
110+
111+
def do_nothing(
112+
self,
113+
container: Kind1[_F_Applicative, _ValueType],
114+
) -> Kind1[_F_Applicative, _ValueType]:
115+
"""No-op method that returns the container."""
116+
return container
117+
118+
119+
def _callable_strategy(
120+
arg1: type[object], arg2: type[object]
121+
) -> StrategyFactory[Callable]:
122+
type_arg1 = int if arg1 == Any else arg1 # type: ignore[comparison-overlap]
123+
type_arg2 = int if arg2 == Any else arg2 # type: ignore[comparison-overlap]
124+
return_results = st.functions(
125+
pure=True,
126+
returns=strategy_from_container(Result)(Result[type_arg1, type_arg2]), # type: ignore[valid-type]
127+
)
128+
return_maybes = st.functions(
129+
pure=True,
130+
returns=strategy_from_container(Maybe)(
131+
MaybeLike2[type_arg1, type_arg2] # type: ignore[valid-type]
132+
),
133+
)
134+
return st.one_of(return_results, return_maybes)
135+
136+
137+
def _callable_factory(thing: type[object]) -> StrategyFactory[Callable]:
138+
if get_origin(thing) != Callable:
139+
raise NotImplementedError
140+
141+
match get_args(thing):
142+
case [[], return_type] if (
143+
get_origin(return_type) == KindN
144+
and get_args(return_type)[0] == _F_Applicative
145+
):
146+
type1, type2, *_ = get_args(return_type)[1:]
147+
return _callable_strategy(type1, type2)
148+
case _:
149+
raise NotImplementedError
150+
151+
152+
other_strategies: dict[type[object], StrategyFactory] = {
153+
Callable: _callable_factory # type: ignore[dict-item]
154+
}
155+
156+
check_all_laws(
157+
_Wrapper,
158+
container_strategy=st.builds(_Wrapper, st.integers()),
159+
other_strategies=other_strategies,
160+
)

0 commit comments

Comments
 (0)