Repository: HypothesisWorks/hypothesis

Issue:
Default max_size strategy argument value is ambiguous
If you read through the [latest documentation](https://hypothesis.readthedocs.io/en/latest/index.html) from start without skipping anything, the first `max_size` argument explanation is happens to be in [`hypothesis.strategies.binary(*, min_size=0, max_size=None)`](https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.binary) strategy documentation.

It says:

>Generates bytes.

>min_size and max_size have the usual interpretations.

`max_size` has a `None` default value, which can be interpreted differently by different people:

* will it generate 0 bytes (to run faster)?
* will it generate some good-enough amount of bytes (to meet the middle ground)?
* will it generate a lot of bytes (to cover lots of different cases)?

It would be nice if this would be made more clear.
Reference pytest docs on fixture scopes in warning
Like this users will know what to do when getting this warning. See #2356.


Discussion:
Thanks for bringing this to our attention!  `max_size=None` means that there is no maximum size, but we should definitely document this clearly the first time it appears (i.e. `binary()`, as you say).

It would also be good to check that `min_value` / `max_value` are explained the first time *they* appear...


Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/extra/pytestplugin.py b/hypothesis-python/src/hypothesis/extra/pytestplugin.py
--- a/hypothesis-python/src/hypothesis/extra/pytestplugin.py
+++ b/hypothesis-python/src/hypothesis/extra/pytestplugin.py
@@ -149,6 +149,14 @@ def pytest_runtest_call(item):
             # Warn about function-scoped fixtures, excluding autouse fixtures because
             # the advice is probably not actionable and the status quo seems OK...
             # See https://github.com/HypothesisWorks/hypothesis/issues/377 for detail.
+            msg = item.nodeid + (
+                " uses the %r fixture, which is reset between function calls but not "
+                "between test cases generated by `@given(...)`.  You can change it to "
+                "a module- or session-scoped fixture if it is safe to reuse; if not "
+                "we recommend using a context manager inside your test function.  See "
+                "https://docs.pytest.org/en/latest/fixture.html#sharing-test-data "
+                "for details on fixture scope."
+            )
             argnames = None
             for fx_defs in item._request._fixturemanager.getfixtureinfo(
                 node=item, func=item.function, cls=None
@@ -159,14 +167,7 @@ def pytest_runtest_call(item):
                     if fx.argname in argnames:
                         active_fx = item._request._get_active_fixturedef(fx.argname)
                         if active_fx.scope == "function":
-                            note_deprecation(
-                                "%s uses the %r fixture, but function-scoped"
-                                " fixtures should not be used with @given(...)"
-                                " tests, because fixtures are not reset "
-                                "between generated examples!"
-                                % (item.nodeid, fx.argname),
-                                since="2020-02-29",
-                            )
+                            note_deprecation(msg % (fx.argname,), since="2020-02-29")
 
             if item.get_closest_marker("parametrize") is not None:
                 # Give every parametrized test invocation a unique database key
diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/core.py b/hypothesis-python/src/hypothesis/strategies/_internal/core.py
--- a/hypothesis-python/src/hypothesis/strategies/_internal/core.py
+++ b/hypothesis-python/src/hypothesis/strategies/_internal/core.py
@@ -377,8 +377,8 @@ def integers(min_value: int = None, max_value: int = None) -> SearchStrategy[int
 def booleans() -> SearchStrategy[bool]:
     """Returns a strategy which generates instances of :class:`python:bool`.
 
-    Examples from this strategy will shrink towards False (i.e.
-    shrinking will try to replace True with False where possible).
+    Examples from this strategy will shrink towards ``False`` (i.e.
+    shrinking will replace ``True`` with ``False`` where possible).
     """
     return sampled_from([False, True])
 
@@ -656,7 +656,7 @@ def sampled_from(elements):
         return sets(sampled_from(values), min_size=1).map(
             lambda s: reduce(operator.or_, s)
         )
-    return SampledFromStrategy(values)
+    return SampledFromStrategy(elements)
 
 
 @cacheable
@@ -1108,13 +1108,14 @@ def from_regex(
 def binary(*, min_size: int = 0, max_size: int = None) -> SearchStrategy[bytes]:
     """Generates :class:`python:bytes`.
 
-    min_size and max_size have the usual interpretations.
+    The generated :class:`python:bytes` will have a length of at least ``min_size``
+    and at most ``max_size`.  If ``max_size is None`` there is no upper limit.
 
     Examples from this strategy shrink towards smaller strings and lower byte
     values.
     """
     check_valid_sizes(min_size, max_size)
-    if min_size == max_size is not None:
+    if min_size == max_size:
         return FixedSizeBytes(min_size)
     return lists(
         integers(min_value=0, max_value=255), min_size=min_size, max_size=max_size
diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py b/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py
--- a/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py
+++ b/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py
@@ -13,6 +13,7 @@
 #
 # END HEADER
 
+import enum
 import sys
 import warnings
 from collections import defaultdict
@@ -435,10 +436,18 @@ class SampledFromStrategy(SearchStrategy):
 
     def __init__(self, elements):
         SearchStrategy.__init__(self)
+        self.raw_elements = elements
         self.elements = cu.check_sample(elements, "sampled_from")
         assert self.elements
 
     def __repr__(self):
+        if isinstance(self.raw_elements, type) and issubclass(
+            self.raw_elements, enum.Enum
+        ):
+            return "sampled_from(%s.%s)" % (
+                self.raw_elements.__module__,
+                self.raw_elements.__name__,
+            )
         return "sampled_from([%s])" % ", ".join(map(repr, self.elements))
 
     def calc_has_reusable_values(self, recur):


Test patch:
diff --git a/hypothesis-python/tests/nocover/test_sampled_from.py b/hypothesis-python/tests/nocover/test_sampled_from.py
--- a/hypothesis-python/tests/nocover/test_sampled_from.py
+++ b/hypothesis-python/tests/nocover/test_sampled_from.py
@@ -13,6 +13,8 @@
 #
 # END HEADER
 
+import enum
+
 import pytest
 
 import hypothesis.strategies as st
@@ -68,3 +70,19 @@ def test_unsat_sets_of_samples(x):
 @given(st.sets(st.sampled_from(range(50)), min_size=50))
 def test_efficient_sets_of_samples(x):
     assert x == set(range(50))
+
+
+class AnEnum(enum.Enum):
+    a = enum.auto()
+    b = enum.auto()
+
+
+def test_enum_repr_uses_class_not_a_list():
+    strat = st.sampled_from(AnEnum)
+    # Our lazy repr looks exactly like our call
+    lazy_repr = repr(strat)
+    assert lazy_repr == "sampled_from(AnEnum)"
+    # The inner repr should have enough detail to find the class again
+    # (which is very useful for the ghostwriter logic we're working on)
+    inner_repr = repr(strat.wrapped_strategy)
+    assert inner_repr == "sampled_from(tests.nocover.test_sampled_from.AnEnum)"