|
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 |
|
@@ -376,6 +378,25 @@ def test_typevars_can_be_redefine_with_factory(): |
376 | 378 | assert_all_examples(st.from_type(A), lambda obj: obj == "A") |
377 | 379 |
|
378 | 380 |
|
| 381 | +def test_typevars_can_be_resolved_conditionally(): |
| 382 | + sentinel = object() |
| 383 | + A = typing.TypeVar("A") |
| 384 | + B = typing.TypeVar("B") |
| 385 | + |
| 386 | + def resolve_type_var(thing): |
| 387 | + assert thing in (A, B) |
| 388 | + if thing == A: |
| 389 | + return st.just(sentinel) |
| 390 | + return NotImplemented |
| 391 | + |
| 392 | + with temp_registered(typing.TypeVar, resolve_type_var): |
| 393 | + assert st.from_type(A).example() is sentinel |
| 394 | + # We've re-defined the default TypeVar resolver, so there is no fallback. |
| 395 | + # This causes the lookup to fail. |
| 396 | + with pytest.raises(InvalidArgument): |
| 397 | + st.from_type(B).example() |
| 398 | + |
| 399 | + |
379 | 400 | def annotated_func(a: int, b: int = 2, *, c: int, d: int = 4): |
380 | 401 | return a + b + c + d |
381 | 402 |
|
@@ -470,6 +491,24 @@ def test_resolves_NewType(): |
470 | 491 | assert isinstance(from_type(uni).example(), (int, type(None))) |
471 | 492 |
|
472 | 493 |
|
| 494 | +@pytest.mark.parametrize("is_handled", [True, False]) |
| 495 | +def test_resolves_NewType_conditionally(is_handled): |
| 496 | + sentinel = object() |
| 497 | + typ = typing.NewType("T", int) |
| 498 | + |
| 499 | + def resolve_custom_strategy(thing): |
| 500 | + assert thing is typ |
| 501 | + if is_handled: |
| 502 | + return st.just(sentinel) |
| 503 | + return NotImplemented |
| 504 | + |
| 505 | + with temp_registered(typ, resolve_custom_strategy): |
| 506 | + if is_handled: |
| 507 | + assert st.from_type(typ).example() is sentinel |
| 508 | + else: |
| 509 | + assert isinstance(st.from_type(typ).example(), int) |
| 510 | + |
| 511 | + |
473 | 512 | E = enum.Enum("E", "a b c") |
474 | 513 |
|
475 | 514 |
|
@@ -807,6 +846,58 @@ def test_supportsop_types_support_protocol(protocol, data): |
807 | 846 | assert issubclass(type(value), protocol) |
808 | 847 |
|
809 | 848 |
|
| 849 | +@pytest.mark.parametrize("restrict_custom_strategy", [True, False]) |
| 850 | +def test_generic_aliases_can_be_conditionally_resolved_by_registered_function( |
| 851 | + restrict_custom_strategy, |
| 852 | +): |
| 853 | + # Check that a custom strategy function may provide no strategy for a |
| 854 | + # generic alias request like Container[T]. We test this under two scenarios: |
| 855 | + # - where CustomContainer CANNOT be generated from requests for Container[T] |
| 856 | + # (only for requests for exactly CustomContainer[T]) |
| 857 | + # - where CustomContainer CAN be generated from requests for Container[T] |
| 858 | + T = typing.TypeVar("T") |
| 859 | + |
| 860 | + @dataclass |
| 861 | + class CustomContainer(typing.Container[T]): |
| 862 | + content: T |
| 863 | + |
| 864 | + def __contains__(self, value: object) -> bool: |
| 865 | + return self.content == value |
| 866 | + |
| 867 | + def get_custom_container_strategy(thing): |
| 868 | + if restrict_custom_strategy and typing.get_origin(thing) != CustomContainer: |
| 869 | + return NotImplemented |
| 870 | + return st.builds( |
| 871 | + CustomContainer, content=st.from_type(typing.get_args(thing)[0]) |
| 872 | + ) |
| 873 | + |
| 874 | + with temp_registered(CustomContainer, get_custom_container_strategy): |
| 875 | + |
| 876 | + def is_custom_container_with_str(example): |
| 877 | + return isinstance(example, CustomContainer) and isinstance( |
| 878 | + example.content, str |
| 879 | + ) |
| 880 | + |
| 881 | + def is_non_custom_container(example): |
| 882 | + return isinstance(example, typing.Container) and not isinstance( |
| 883 | + example, CustomContainer |
| 884 | + ) |
| 885 | + |
| 886 | + assert_all_examples( |
| 887 | + st.from_type(CustomContainer[str]), is_custom_container_with_str |
| 888 | + ) |
| 889 | + # If the strategy function is restricting, it doesn't return a strategy |
| 890 | + # for requests for Container[...], so it's never generated. When not |
| 891 | + # restricting, it is generated. |
| 892 | + if restrict_custom_strategy: |
| 893 | + assert_all_examples( |
| 894 | + st.from_type(typing.Container[str]), is_non_custom_container |
| 895 | + ) |
| 896 | + else: |
| 897 | + find_any(st.from_type(typing.Container[str]), is_custom_container_with_str) |
| 898 | + find_any(st.from_type(typing.Container[str]), is_non_custom_container) |
| 899 | + |
| 900 | + |
810 | 901 | @pytest.mark.parametrize( |
811 | 902 | "protocol, typ", |
812 | 903 | [ |
@@ -1069,3 +1160,31 @@ def test_tuple_subclasses_not_generic_sequences(): |
1069 | 1160 | with temp_registered(TupleSubtype, st.builds(TupleSubtype)): |
1070 | 1161 | s = st.from_type(typing.Sequence[int]) |
1071 | 1162 | assert_no_examples(s, lambda x: isinstance(x, tuple)) |
| 1163 | + |
| 1164 | + |
| 1165 | +def test_custom_strategy_function_resolves_types_conditionally(): |
| 1166 | + sentinel = object() |
| 1167 | + |
| 1168 | + class A: |
| 1169 | + pass |
| 1170 | + |
| 1171 | + class B(A): |
| 1172 | + pass |
| 1173 | + |
| 1174 | + class C(A): |
| 1175 | + pass |
| 1176 | + |
| 1177 | + def resolve_custom_strategy_for_b(thing): |
| 1178 | + if thing == B: |
| 1179 | + return st.just(sentinel) |
| 1180 | + return NotImplemented |
| 1181 | + |
| 1182 | + with contextlib.ExitStack() as stack: |
| 1183 | + stack.enter_context(temp_registered(B, resolve_custom_strategy_for_b)) |
| 1184 | + stack.enter_context(temp_registered(C, st.builds(C))) |
| 1185 | + |
| 1186 | + # C's strategy can be used for A, but B's cannot because its function |
| 1187 | + # only returns a strategy for requests for exactly B. |
| 1188 | + assert_all_examples(st.from_type(A), lambda example: type(example) == C) |
| 1189 | + assert_all_examples(st.from_type(B), lambda example: example is sentinel) |
| 1190 | + assert_all_examples(st.from_type(C), lambda example: type(example) == C) |
0 commit comments