From 54777d60d80157368944a0be01ce3add737fcc72 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sat, 7 Dec 2024 02:30:21 -0500 Subject: [PATCH] recommend remedies in unsatsisfiable --- hypothesis-python/src/hypothesis/core.py | 27 ++++++++++++++++++- .../hypothesis/internal/conjecture/engine.py | 8 +++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py index d60fcab01f..306f6497bb 100644 --- a/hypothesis-python/src/hypothesis/core.py +++ b/hypothesis-python/src/hypothesis/core.py @@ -1231,8 +1231,33 @@ def run_engine(self): ) else: if runner.valid_examples == 0: + explanations = [] + # use a somewhat arbitrary cutoff to avoid recommending spurious + # fixes. + # eg, a few invalid examples from internal filters when the + # problem is the user generating large inputs, or a + # few overruns during internal mutation when the problem is + # impossible user filters/assumes. + if runner.invalid_examples > min(20, runner.call_count // 5): + explanations.append( + f"{runner.invalid_examples} of {runner.call_count} " + "examples failed a .filter() or assume() condition. Try " + "making your filters or assumes less strict, or rewrite " + "using strategy parameters: " + "st.integers().filter(lambda x: x > 0) fails less often " + "(that is, never) when rewritten as st.integers(min_value=1)." + ) + if runner.overrun_examples > min(20, runner.call_count // 5): + explanations.append( + f"{runner.overrun_examples} of {runner.call_count} " + "examples were too large to finish generating. Try " + "reducing the size of your inputs." + ) rep = get_pretty_function_description(self.test) - raise Unsatisfiable(f"Unable to satisfy assumptions of {rep}") + raise Unsatisfiable( + f"Unable to satisfy assumptions of {rep}. " + f"{' Also, '.join(explanations)}" + ) # If we have not traced executions, warn about that now (but only when # we'd expect to do so reliably, i.e. on CPython>=3.12) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/engine.py b/hypothesis-python/src/hypothesis/internal/conjecture/engine.py index a5c388683b..894a8b67ee 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/engine.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/engine.py @@ -228,6 +228,8 @@ def __init__( self.call_count: int = 0 self.misaligned_count: int = 0 self.valid_examples: int = 0 + self.invalid_examples = 0 + self.overrun_examples = 0 self.random: Random = random or Random(getrandbits(128)) self.database_key: Optional[bytes] = database_key self.ignore_limits: bool = ignore_limits @@ -567,8 +569,12 @@ def test_function(self, data: ConjectureData) -> None: assert not isinstance(data_as_result, _Overrun) self.best_examples_of_observed_targets[k] = data_as_result - if data.status == Status.VALID: + if data.status is Status.VALID: self.valid_examples += 1 + if data.status is Status.INVALID: + self.invalid_examples += 1 + if data.status is Status.OVERRUN: + self.overrun_examples += 1 if data.status == Status.INTERESTING: if not self.using_hypothesis_backend: