From 4880b11cb141bdcbb226f638c2ca004f4ebf2da0 Mon Sep 17 00:00:00 2001 From: Vince Reuter Date: Fri, 22 Dec 2023 10:24:06 +0100 Subject: [PATCH] add warning all elements to sampled_from are strategies, suggestive of one_of; close #3819 --- AUTHORS.rst | 1 + .../hypothesis/strategies/_internal/core.py | 4 +++ hypothesis-python/tests/cover/test_lookup.py | 26 ++++++++++++------- .../tests/cover/test_sampled_from.py | 10 +++++++ 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index ff0c99ac001..e992d3dfb4f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -181,6 +181,7 @@ their individual contributions. * `Tyler Gibbons `_ (tyler.gibbons@flexport.com) * `Tyler Nickerson `_ * `Vidya Rani `_ (vidyarani.d.g@gmail.com) +* `Vince Reuter `_ (vince.reuter@gmail.com) * `Vincent Michel `_ (vxgmichel@gmail.com) * `Viorel Pluta `_ (viopluta@gmail.com) * `Vytautas Strimaitis `_ diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py index 03653c61e11..bfa6e390bd8 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py @@ -214,6 +214,10 @@ def sampled_from( "so maybe you tried to write an enum as if it was a dataclass?" ) raise InvalidArgument("Cannot sample from a length-zero sequence.") + elif all(isinstance(x, SearchStrategy) for x in values): + warnings.warn( + "sample_from was given a collection of strategies; was one_of intended?" + ) if len(values) == 1: return just(values[0]) try: diff --git a/hypothesis-python/tests/cover/test_lookup.py b/hypothesis-python/tests/cover/test_lookup.py index 9801d6d3cfb..12d39adae49 100644 --- a/hypothesis-python/tests/cover/test_lookup.py +++ b/hypothesis-python/tests/cover/test_lookup.py @@ -311,21 +311,29 @@ class Baz(Foo): @pytest.mark.parametrize( - "var,expected", + "var,expected,exp_warn", [ - (typing.TypeVar("V"), object), - (typing.TypeVar("V", bound=int), int), - (typing.TypeVar("V", bound=Foo), (Bar, Baz)), - (typing.TypeVar("V", bound=typing.Union[int, str]), (int, str)), - (typing.TypeVar("V", int, str), (int, str)), + # Expect a warning exactly when the type constraint/bound should trigger + # passing a strategy to sampled_from. + (typing.TypeVar("V"), object, False), + (typing.TypeVar("V", bound=int), int, False), + (typing.TypeVar("V", bound=Foo), (Bar, Baz), True), + (typing.TypeVar("V", bound=typing.Union[int, str]), (int, str), True), + (typing.TypeVar("V", int, str), (int, str), False), ], ) @settings(suppress_health_check=[HealthCheck.too_slow]) @given(data=st.data()) -def test_typevar_type_is_consistent(data, var, expected): +def test_typevar_type_is_consistent(data, var, expected, exp_warn): strat = st.from_type(var) - v1 = data.draw(strat) - v2 = data.draw(strat) + if exp_warn: + with warnings.catch_warnings: + warnings.filterwarnings(action="ignore", category=UserWarning) + v1 = data.draw(strat) + v2 = data.draw(strat) + else: + v1 = data.draw(strat) + v2 = data.draw(strat) assume(v1 != v2) # Values may vary, just not types assert type(v1) == type(v2) assert isinstance(v1, expected) diff --git a/hypothesis-python/tests/cover/test_sampled_from.py b/hypothesis-python/tests/cover/test_sampled_from.py index 8624fbc326b..e9cbf425465 100644 --- a/hypothesis-python/tests/cover/test_sampled_from.py +++ b/hypothesis-python/tests/cover/test_sampled_from.py @@ -190,3 +190,13 @@ class AnnotationsInsteadOfElements(enum.Enum): def test_suggests_elements_instead_of_annotations(): with pytest.raises(InvalidArgument, match="Cannot sample.*annotations.*dataclass"): st.sampled_from(AnnotationsInsteadOfElements).example() + + +@pytest.mark.parametrize("wrap", [list, tuple]) +def test_warns_when_given_entirely_strategies_as_elements(wrap): + elements = wrap([st.booleans(), st.decimals(), st.integers(), st.text()]) + with pytest.warns( + UserWarning, + match="sample_from was given a collection of strategies; was one_of intended?", + ): + st.sampled_from(elements)