Repository: HypothesisWorks/hypothesis

Issue:
Ghostwriter should infer `text=st.text()`
If we're falling back to the argument name and it's `text`, then `st.text()` is clearly the correct strategy.
Ghostwriter should be more resilient to Rich issues
Rich keeps changing the API for printing syntax-highlighted text, which can result in exceptions instead of pretty output.  We should swallow any errors, maybe log to stderr, and just print without highlighting in these cases.


Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py
--- a/hypothesis-python/src/hypothesis/core.py
+++ b/hypothesis-python/src/hypothesis/core.py
@@ -100,6 +100,7 @@
 )
 from hypothesis.statistics import describe_targets, note_statistics
 from hypothesis.strategies._internal.collections import TupleStrategy
+from hypothesis.strategies._internal.misc import NOTHING
 from hypothesis.strategies._internal.strategies import (
     Ex,
     MappedSearchStrategy,
@@ -241,9 +242,9 @@ def is_invalid_test(test, original_argspec, given_arguments, given_kwargs):
     only be reported for tests that actually ran.
     """
 
-    def invalid(message):
+    def invalid(message, *, exc=InvalidArgument):
         def wrapped_test(*arguments, **kwargs):
-            raise InvalidArgument(message)
+            raise exc(message)
 
         wrapped_test.is_hypothesis_test = True
         wrapped_test.hypothesis = HypothesisHandle(
@@ -301,6 +302,18 @@ def wrapped_test(*arguments, **kwargs):
             )
         )
 
+    # This case would raise Unsatisfiable *anyway*, but by detecting it here we can
+    # provide a much more helpful error message for people e.g. using the Ghostwriter.
+    empty = [
+        f"{s!r} (arg {idx})" for idx, s in enumerate(given_arguments) if s is NOTHING
+    ] + [f"{name}={s!r}" for name, s in given_kwargs.items() if s is NOTHING]
+    if empty:
+        strats = "strategies" if len(empty) > 1 else "strategy"
+        return invalid(
+            f"Cannot generate examples from empty {strats}: " + ", ".join(empty),
+            exc=Unsatisfiable,
+        )
+
 
 class ArtificialDataForExample(ConjectureData):
     """Dummy object that pretends to be a ConjectureData object for the purposes of
diff --git a/hypothesis-python/src/hypothesis/extra/cli.py b/hypothesis-python/src/hypothesis/extra/cli.py
--- a/hypothesis-python/src/hypothesis/extra/cli.py
+++ b/hypothesis-python/src/hypothesis/extra/cli.py
@@ -242,10 +242,10 @@ def write(func, writer, except_, style):  # noqa: D301  # \b disables autowrap
         except ImportError:
             print(code)
         else:
-            code = Syntax(
-                code,
-                "python",
-                background_color="default",
-                theme="default" if guess_background_color() == "light" else "monokai",
-            )
-            Console().print(code, soft_wrap=True)
+            try:
+                theme = "default" if guess_background_color() == "light" else "monokai"
+                code = Syntax(code, "python", background_color="default", theme=theme)
+                Console().print(code, soft_wrap=True)
+            except Exception:
+                print("# Error while syntax-highlighting code", file=sys.stderr)
+                print(code)
diff --git a/hypothesis-python/src/hypothesis/extra/ghostwriter.py b/hypothesis-python/src/hypothesis/extra/ghostwriter.py
--- a/hypothesis-python/src/hypothesis/extra/ghostwriter.py
+++ b/hypothesis-python/src/hypothesis/extra/ghostwriter.py
@@ -205,7 +205,7 @@ def _exceptions_from_docstring(doc: str) -> Tuple[Type[Exception], ...]:
 _GUESS_STRATEGIES_BY_NAME = (
     (st.integers(0, 32), ["ndims", "ndigits"]),
     (st.booleans(), ["keepdims"]),
-    (st.text(), ["name", "filename", "fname"]),
+    (st.text(), ["name", "filename", "fname", "text"]),
     (st.floats(), ["real", "imag"]),
     (st.functions(), ["function", "func", "f"]),
     (st.functions(returns=st.booleans(), pure=True), ["pred", "predicate"]),


Test patch:
diff --git a/hypothesis-python/tests/cover/test_explicit_examples.py b/hypothesis-python/tests/cover/test_explicit_examples.py
--- a/hypothesis-python/tests/cover/test_explicit_examples.py
+++ b/hypothesis-python/tests/cover/test_explicit_examples.py
@@ -29,7 +29,7 @@
     InvalidArgument,
     MultipleFailures,
 )
-from hypothesis.strategies import floats, integers, nothing, text
+from hypothesis.strategies import floats, integers, text
 
 from tests.common.utils import assert_falsifying_output, capture_out
 
@@ -233,7 +233,7 @@ def test(a):
 def test_runs_deadline_for_examples():
     @example(10)
     @settings(phases=[Phase.explicit])
-    @given(nothing())
+    @given(integers())
     def test(x):
         time.sleep(10)
 
diff --git a/hypothesis-python/tests/cover/test_given_error_conditions.py b/hypothesis-python/tests/cover/test_given_error_conditions.py
--- a/hypothesis-python/tests/cover/test_given_error_conditions.py
+++ b/hypothesis-python/tests/cover/test_given_error_conditions.py
@@ -12,7 +12,7 @@
 
 from hypothesis import assume, given, infer, reject, settings
 from hypothesis.errors import InvalidArgument, Unsatisfiable
-from hypothesis.strategies import booleans, integers
+from hypothesis.strategies import booleans, integers, nothing
 
 from tests.common.utils import fails_with
 
@@ -34,6 +34,18 @@ def test_assume_x(x):
     test_assume_x()
 
 
+def test_raises_unsatisfiable_if_passed_explicit_nothing():
+    @given(x=nothing())
+    def test_never_runs(x):
+        raise Exception("Can't ever execute this")
+
+    with pytest.raises(
+        Unsatisfiable,
+        match=r"Cannot generate examples from empty strategy: x=nothing\(\)",
+    ):
+        test_never_runs()
+
+
 def test_error_if_has_no_hints():
     @given(a=infer)
     def inner(a):