diff --git a/hypothesis-python/src/hypothesis/internal/filtering.py b/hypothesis-python/src/hypothesis/internal/filtering.py index 28856197ed..01520a7622 100644 --- a/hypothesis-python/src/hypothesis/internal/filtering.py +++ b/hypothesis-python/src/hypothesis/internal/filtering.py @@ -110,7 +110,7 @@ def comp_to_kwargs(x: ast.AST, op: ast.AST, y: ast.AST, *, argname: str) -> dict if a is ARG: return {"min_value": b, "exclude_min": True} return {"max_value": a, "exclude_max": True} - raise ValueError("Unhandled comparison operator") + raise ValueError("Unhandled comparison operator") # e.g. ast.Ne def merge_preds(*con_predicates: ConstructivePredicate) -> ConstructivePredicate: @@ -131,16 +131,12 @@ def merge_preds(*con_predicates: ConstructivePredicate) -> ConstructivePredicate base["min_value"] = kw["min_value"] elif kw["min_value"] == base["min_value"]: base["exclude_min"] |= kw.get("exclude_min", False) - else: - base["exclude_min"] = False if "max_value" in kw: if kw["max_value"] < base["max_value"]: base["exclude_max"] = kw.get("exclude_max", False) base["max_value"] = kw["max_value"] elif kw["max_value"] == base["max_value"]: base["exclude_max"] |= kw.get("exclude_max", False) - else: - base["exclude_max"] = False if not base["exclude_min"]: del base["exclude_min"] @@ -167,9 +163,6 @@ def numeric_bounds_from_ast( See also https://greentreesnakes.readthedocs.io/en/latest/ """ - while isinstance(tree, ast.Expr): - tree = tree.value - if isinstance(tree, ast.Compare): ops = tree.ops vals = tree.comparators @@ -237,7 +230,7 @@ def get_numeric_predicate_bounds(predicate: Predicate) -> ConstructivePredicate: else: source = inspect.getsource(predicate) tree: ast.AST = ast.parse(source) - except Exception: # pragma: no cover + except Exception: return unchanged # Dig down to the relevant subtree - our tree is probably a Module containing diff --git a/hypothesis-python/tests/cover/test_filter_rewriting.py b/hypothesis-python/tests/cover/test_filter_rewriting.py index 6697a0ff58..4ca67a55ed 100644 --- a/hypothesis-python/tests/cover/test_filter_rewriting.py +++ b/hypothesis-python/tests/cover/test_filter_rewriting.py @@ -22,6 +22,7 @@ from hypothesis import given, strategies as st from hypothesis.errors import Unsatisfiable +from hypothesis.internal.reflection import get_pretty_function_description from hypothesis.strategies._internal.lazy import LazyStrategy from hypothesis.strategies._internal.numbers import IntegersStrategy from hypothesis.strategies._internal.strategies import FilteredStrategy @@ -70,7 +71,14 @@ (st.integers(), lambda x: 3 < x, 4, None), # More complicated lambdas (st.integers(), lambda x: 0 < x < 5, 1, 4), + (st.integers(), lambda x: 0 < x >= 1, 1, None), + (st.integers(), lambda x: 1 > x <= 0, None, 0), + (st.integers(), lambda x: x > 0 and x > 0, 1, None), + (st.integers(), lambda x: x < 1 and x < 1, None, 0), + (st.integers(), lambda x: x > 1 and x > 0, 2, None), + (st.integers(), lambda x: x < 1 and x < 2, None, 0), ], + ids=get_pretty_function_description, ) @given(data=st.data()) def test_filter_rewriting(data, strategy, predicate, start, end): @@ -165,14 +173,27 @@ def test_rewrite_filter_chains_with_some_unhandled(data, predicates): assert pred is mod2 or pred.__name__ == "" +class NotAFunction: + def __call__(self, bar): + return True + + +lambda_without_source = eval("lambda x: x > 2", {}, {}) + + @pytest.mark.parametrize( "start, end, predicate", [ (1, 4, lambda x: 0 < x < 5 and x % 7), + (0, 9, lambda x: 0 <= x < 10 and x % 3), (1, None, lambda x: 0 < x <= Y), (None, None, lambda x: x == x), (None, None, lambda x: 1 == 1), (None, None, lambda x: 1 <= 2), + (None, None, lambda x: x != 0), + (None, None, NotAFunction()), + (None, None, lambda_without_source), + (None, None, lambda x, y=2: x >= 0), ], ) @given(data=st.data())