|
11 | 11 | import abc |
12 | 12 | import builtins |
13 | 13 | import collections |
| 14 | +import contextlib |
14 | 15 | import datetime |
15 | 16 | import enum |
16 | 17 | import inspect |
|
21 | 22 | import sys |
22 | 23 | import typing |
23 | 24 | import warnings |
| 25 | +from dataclasses import dataclass |
24 | 26 | from inspect import signature |
25 | 27 | from numbers import Real |
26 | 28 |
|
@@ -371,6 +373,25 @@ def test_typevars_can_be_redefine_with_factory(): |
371 | 373 | assert_all_examples(st.from_type(A), lambda obj: obj == "A") |
372 | 374 |
|
373 | 375 |
|
| 376 | +def test_typevars_can_be_resolved_conditionally(): |
| 377 | + sentinel = object() |
| 378 | + A = typing.TypeVar("A") |
| 379 | + B = typing.TypeVar("B") |
| 380 | + |
| 381 | + def resolve_type_var(thing): |
| 382 | + assert thing in (A, B) |
| 383 | + if thing == A: |
| 384 | + return st.just(sentinel) |
| 385 | + return NotImplemented |
| 386 | + |
| 387 | + with temp_registered(typing.TypeVar, resolve_type_var): |
| 388 | + assert st.from_type(A).example() is sentinel |
| 389 | + # We've re-defined the default TypeVar resolver, so there is no fallback. |
| 390 | + # This causes the lookup to fail. |
| 391 | + with pytest.raises(InvalidArgument): |
| 392 | + st.from_type(B).example() |
| 393 | + |
| 394 | + |
374 | 395 | def annotated_func(a: int, b: int = 2, *, c: int, d: int = 4): |
375 | 396 | return a + b + c + d |
376 | 397 |
|
@@ -465,6 +486,24 @@ def test_resolves_NewType(): |
465 | 486 | assert isinstance(from_type(uni).example(), (int, type(None))) |
466 | 487 |
|
467 | 488 |
|
| 489 | +@pytest.mark.parametrize("is_handled", [True, False]) |
| 490 | +def test_resolves_NewType_conditionally(is_handled): |
| 491 | + sentinel = object() |
| 492 | + typ = typing.NewType("T", int) |
| 493 | + |
| 494 | + def resolve_custom_strategy(thing): |
| 495 | + assert thing is typ |
| 496 | + if is_handled: |
| 497 | + return st.just(sentinel) |
| 498 | + return NotImplemented |
| 499 | + |
| 500 | + with temp_registered(typ, resolve_custom_strategy): |
| 501 | + if is_handled: |
| 502 | + assert st.from_type(typ).example() is sentinel |
| 503 | + else: |
| 504 | + assert isinstance(st.from_type(typ).example(), int) |
| 505 | + |
| 506 | + |
468 | 507 | E = enum.Enum("E", "a b c") |
469 | 508 |
|
470 | 509 |
|
@@ -802,6 +841,58 @@ def test_supportsop_types_support_protocol(protocol, data): |
802 | 841 | assert issubclass(type(value), protocol) |
803 | 842 |
|
804 | 843 |
|
| 844 | +@pytest.mark.parametrize("restrict_custom_strategy", [True, False]) |
| 845 | +def test_generic_aliases_can_be_conditionally_resolved_by_registered_function( |
| 846 | + restrict_custom_strategy, |
| 847 | +): |
| 848 | + # Check that a custom strategy function may provide no strategy for a |
| 849 | + # generic alias request like Container[T]. We test this under two scenarios: |
| 850 | + # - where CustomContainer CANNOT be generated from requests for Container[T] |
| 851 | + # (only for requests for exactly CustomContainer[T]) |
| 852 | + # - where CustomContainer CAN be generated from requests for Container[T] |
| 853 | + T = typing.TypeVar("T") |
| 854 | + |
| 855 | + @dataclass |
| 856 | + class CustomContainer(typing.Container[T]): |
| 857 | + content: T |
| 858 | + |
| 859 | + def __contains__(self, value: object) -> bool: |
| 860 | + return self.content == value |
| 861 | + |
| 862 | + def get_custom_container_strategy(thing): |
| 863 | + if restrict_custom_strategy and typing.get_origin(thing) != CustomContainer: |
| 864 | + return NotImplemented |
| 865 | + return st.builds( |
| 866 | + CustomContainer, content=st.from_type(typing.get_args(thing)[0]) |
| 867 | + ) |
| 868 | + |
| 869 | + with temp_registered(CustomContainer, get_custom_container_strategy): |
| 870 | + |
| 871 | + def is_custom_container_with_str(example): |
| 872 | + return isinstance(example, CustomContainer) and isinstance( |
| 873 | + example.content, str |
| 874 | + ) |
| 875 | + |
| 876 | + def is_non_custom_container(example): |
| 877 | + return isinstance(example, typing.Container) and not isinstance( |
| 878 | + example, CustomContainer |
| 879 | + ) |
| 880 | + |
| 881 | + assert_all_examples( |
| 882 | + st.from_type(CustomContainer[str]), is_custom_container_with_str |
| 883 | + ) |
| 884 | + # If the strategy function is restricting, it doesn't return a strategy |
| 885 | + # for requests for Container[...], so it's never generated. When not |
| 886 | + # restricting, it is generated. |
| 887 | + if restrict_custom_strategy: |
| 888 | + assert_all_examples( |
| 889 | + st.from_type(typing.Container[str]), is_non_custom_container |
| 890 | + ) |
| 891 | + else: |
| 892 | + find_any(st.from_type(typing.Container[str]), is_custom_container_with_str) |
| 893 | + find_any(st.from_type(typing.Container[str]), is_non_custom_container) |
| 894 | + |
| 895 | + |
805 | 896 | @pytest.mark.parametrize( |
806 | 897 | "protocol, typ", |
807 | 898 | [ |
@@ -1053,3 +1144,31 @@ def f(x: int): |
1053 | 1144 | msg = "@no_type_check decorator prevented Hypothesis from inferring a strategy" |
1054 | 1145 | with pytest.raises(TypeError, match=msg): |
1055 | 1146 | st.builds(f).example() |
| 1147 | + |
| 1148 | + |
| 1149 | +def test_custom_strategy_function_resolves_types_conditionally(): |
| 1150 | + sentinel = object() |
| 1151 | + |
| 1152 | + class A: |
| 1153 | + pass |
| 1154 | + |
| 1155 | + class B(A): |
| 1156 | + pass |
| 1157 | + |
| 1158 | + class C(A): |
| 1159 | + pass |
| 1160 | + |
| 1161 | + def resolve_custom_strategy_for_b(thing): |
| 1162 | + if thing == B: |
| 1163 | + return st.just(sentinel) |
| 1164 | + return NotImplemented |
| 1165 | + |
| 1166 | + with contextlib.ExitStack() as stack: |
| 1167 | + stack.enter_context(temp_registered(B, resolve_custom_strategy_for_b)) |
| 1168 | + stack.enter_context(temp_registered(C, st.builds(C))) |
| 1169 | + |
| 1170 | + # C's strategy can be used for A, but B's cannot because its function |
| 1171 | + # only returns a strategy for requests for exactly B. |
| 1172 | + assert_all_examples(st.from_type(A), lambda example: type(example) == C) |
| 1173 | + assert_all_examples(st.from_type(B), lambda example: example is sentinel) |
| 1174 | + assert_all_examples(st.from_type(C), lambda example: type(example) == C) |
0 commit comments