1111import decimal
1212import math
1313import operator
14+ from fractions import Fraction
1415from functools import partial
16+ from sys import float_info
1517
1618import pytest
1719
1820from hypothesis import given , strategies as st
1921from hypothesis .errors import HypothesisWarning , Unsatisfiable
22+ from hypothesis .internal .floats import next_down , next_up
2023from hypothesis .internal .reflection import get_pretty_function_description
2124from hypothesis .strategies ._internal .lazy import LazyStrategy , unwrap_strategies
22- from hypothesis .strategies ._internal .numbers import IntegersStrategy
25+ from hypothesis .strategies ._internal .numbers import FloatStrategy , IntegersStrategy
2326from hypothesis .strategies ._internal .strategies import FilteredStrategy
2427
2528from tests .common .utils import fails_with
@@ -87,20 +90,81 @@ def test_filter_rewriting(data, strategy, predicate, start, end):
8790
8891
8992@pytest .mark .parametrize (
90- "s " ,
93+ "strategy, predicate, min_value, max_value " ,
9194 [
92- st .integers (1 , 5 ).filter (partial (operator .lt , 6 )),
93- st .integers (1 , 5 ).filter (partial (operator .eq , 3.5 )),
94- st .integers (1 , 5 ).filter (partial (operator .eq , "can't compare to strings" )),
95- st .integers (1 , 5 ).filter (partial (operator .ge , 0 )),
96- st .integers (1 , 5 ).filter (partial (operator .lt , math .inf )),
97- st .integers (1 , 5 ).filter (partial (operator .gt , - math .inf )),
95+ # Floats with integer bounds
96+ (st .floats (1 , 5 ), partial (operator .lt , 3 ), next_up (3.0 ), 5 ), # 3 < x
97+ (st .floats (1 , 5 ), partial (operator .le , 3 ), 3 , 5 ), # lambda x: 3 <= x
98+ (st .floats (1 , 5 ), partial (operator .eq , 3 ), 3 , 3 ), # lambda x: 3 == x
99+ (st .floats (1 , 5 ), partial (operator .ge , 3 ), 1 , 3 ), # lambda x: 3 >= x
100+ (st .floats (1 , 5 ), partial (operator .gt , 3 ), 1 , next_down (3.0 )), # 3 > x
101+ # Floats with non-integer bounds
102+ (st .floats (1 , 5 ), partial (operator .lt , 3.5 ), next_up (3.5 ), 5 ),
103+ (st .floats (1 , 5 ), partial (operator .le , 3.5 ), 3.5 , 5 ),
104+ (st .floats (1 , 5 ), partial (operator .ge , 3.5 ), 1 , 3.5 ),
105+ (st .floats (1 , 5 ), partial (operator .gt , 3.5 ), 1 , next_down (3.5 )),
106+ (st .floats (1 , 5 ), partial (operator .lt , - math .inf ), 1 , 5 ),
107+ (st .floats (1 , 5 ), partial (operator .gt , math .inf ), 1 , 5 ),
108+ # Floats with only one bound
109+ (st .floats (min_value = 1 ), partial (operator .lt , 3 ), next_up (3.0 ), math .inf ),
110+ (st .floats (min_value = 1 ), partial (operator .le , 3 ), 3 , math .inf ),
111+ (st .floats (max_value = 5 ), partial (operator .ge , 3 ), - math .inf , 3 ),
112+ (st .floats (max_value = 5 ), partial (operator .gt , 3 ), - math .inf , next_down (3.0 )),
113+ # Unbounded floats
114+ (st .floats (), partial (operator .lt , 3 ), next_up (3.0 ), math .inf ),
115+ (st .floats (), partial (operator .le , 3 ), 3 , math .inf ),
116+ (st .floats (), partial (operator .eq , 3 ), 3 , 3 ),
117+ (st .floats (), partial (operator .ge , 3 ), - math .inf , 3 ),
118+ (st .floats (), partial (operator .gt , 3 ), - math .inf , next_down (3.0 )),
119+ # Simple lambdas
120+ (st .floats (), lambda x : x < 3 , - math .inf , next_down (3.0 )),
121+ (st .floats (), lambda x : x <= 3 , - math .inf , 3 ),
122+ (st .floats (), lambda x : x == 3 , 3 , 3 ),
123+ (st .floats (), lambda x : x >= 3 , 3 , math .inf ),
124+ (st .floats (), lambda x : x > 3 , next_up (3.0 ), math .inf ),
125+ # Simple lambdas, reverse comparison
126+ (st .floats (), lambda x : 3 > x , - math .inf , next_down (3.0 )),
127+ (st .floats (), lambda x : 3 >= x , - math .inf , 3 ),
128+ (st .floats (), lambda x : 3 == x , 3 , 3 ),
129+ (st .floats (), lambda x : 3 <= x , 3 , math .inf ),
130+ (st .floats (), lambda x : 3 < x , next_up (3.0 ), math .inf ),
131+ # More complicated lambdas
132+ (st .floats (), lambda x : 0 < x < 5 , next_up (0.0 ), next_down (5.0 )),
133+ (st .floats (), lambda x : 0 < x >= 1 , 1 , math .inf ),
134+ (st .floats (), lambda x : 1 > x <= 0 , - math .inf , 0 ),
135+ (st .floats (), lambda x : x > 0 and x > 0 , next_up (0.0 ), math .inf ),
136+ (st .floats (), lambda x : x < 1 and x < 1 , - math .inf , next_down (1.0 )),
137+ (st .floats (), lambda x : x > 1 and x > 0 , next_up (1.0 ), math .inf ),
138+ (st .floats (), lambda x : x < 1 and x < 2 , - math .inf , next_down (1.0 )),
98139 ],
140+ ids = get_pretty_function_description ,
99141)
100- @fails_with (Unsatisfiable )
101142@given (data = st .data ())
102- def test_rewrite_unsatisfiable_filter (data , s ):
103- data .draw (s )
143+ def test_filter_rewriting_floats (data , strategy , predicate , min_value , max_value ):
144+ s = strategy .filter (predicate )
145+ assert isinstance (s , LazyStrategy )
146+ assert isinstance (s .wrapped_strategy , FloatStrategy )
147+ assert s .wrapped_strategy .min_value == min_value
148+ assert s .wrapped_strategy .max_value == max_value
149+ value = data .draw (s )
150+ assert predicate (value )
151+
152+
153+ @pytest .mark .parametrize (
154+ "pred" ,
155+ [
156+ partial (operator .lt , 6 ),
157+ partial (operator .eq , Fraction (10 , 3 )),
158+ partial (operator .eq , "can't compare to strings" ),
159+ partial (operator .ge , 0 ),
160+ partial (operator .lt , math .inf ),
161+ partial (operator .gt , - math .inf ),
162+ ],
163+ )
164+ @pytest .mark .parametrize ("s" , [st .integers (1 , 5 ), st .floats (1 , 5 )])
165+ @fails_with (Unsatisfiable )
166+ def test_rewrite_unsatisfiable_filter (s , pred ):
167+ s .filter (pred ).example ()
104168
105169
106170@given (st .integers (0 , 2 ).filter (partial (operator .ne , 1 )))
@@ -115,14 +179,8 @@ def test_rewriting_does_not_compare_decimal_snan():
115179 s .example ()
116180
117181
118- @pytest .mark .parametrize (
119- "strategy, lo, hi" ,
120- [
121- (st .integers (0 , 1 ), - 1 , 2 ),
122- ],
123- ids = repr ,
124- )
125- def test_applying_noop_filter_returns_self (strategy , lo , hi ):
182+ @pytest .mark .parametrize ("strategy" , [st .integers (0 , 1 ), st .floats (0 , 1 )], ids = repr )
183+ def test_applying_noop_filter_returns_self (strategy ):
126184 s = strategy .wrapped_strategy
127185 s2 = s .filter (partial (operator .le , - 1 )).filter (partial (operator .ge , 2 ))
128186 assert s is s2
@@ -135,6 +193,7 @@ def mod2(x):
135193Y = 2 ** 20
136194
137195
196+ @pytest .mark .parametrize ("s" , [st .integers (1 , 5 ), st .floats (1 , 5 )])
138197@given (
139198 data = st .data (),
140199 predicates = st .permutations (
@@ -149,9 +208,8 @@ def mod2(x):
149208 ]
150209 ),
151210)
152- def test_rewrite_filter_chains_with_some_unhandled (data , predicates ):
211+ def test_rewrite_filter_chains_with_some_unhandled (data , predicates , s ):
153212 # Set up our strategy
154- s = st .integers (1 , 5 )
155213 for p in predicates :
156214 s = s .filter (p )
157215
@@ -163,7 +221,7 @@ def test_rewrite_filter_chains_with_some_unhandled(data, predicates):
163221 # No matter the order of the filters, we get the same resulting structure
164222 unwrapped = s .wrapped_strategy
165223 assert isinstance (unwrapped , FilteredStrategy )
166- assert isinstance (unwrapped .filtered_strategy , IntegersStrategy )
224+ assert isinstance (unwrapped .filtered_strategy , ( IntegersStrategy , FloatStrategy ) )
167225 for pred in unwrapped .flat_conditions :
168226 assert pred is mod2 or pred .__name__ == "<lambda>"
169227
@@ -246,3 +304,17 @@ def test_bumps_min_size_and_filters_for_content_str_methods(method):
246304 fs = s .filter (method )
247305 assert fs .filtered_strategy .min_size == 1
248306 assert fs .flat_conditions == (method ,)
307+
308+
309+ @pytest .mark .parametrize (
310+ "op, attr, value, expected" ,
311+ [
312+ (operator .lt , "min_value" , - float_info .min / 2 , 0 ),
313+ (operator .lt , "min_value" , float_info .min / 2 , float_info .min ),
314+ (operator .gt , "max_value" , float_info .min / 2 , 0 ),
315+ (operator .gt , "max_value" , - float_info .min / 2 , - float_info .min ),
316+ ],
317+ )
318+ def test_filter_floats_can_skip_subnormals (op , attr , value , expected ):
319+ base = st .floats (allow_subnormal = False ).filter (partial (op , value ))
320+ assert getattr (base .wrapped_strategy , attr ) == expected
0 commit comments