Skip to content

Commit c7f91f0

Browse files
committed
improve node.trivial check and strengthen tests
1 parent a38bae9 commit c7f91f0

File tree

2 files changed

+166
-13
lines changed

2 files changed

+166
-13
lines changed

hypothesis-python/src/hypothesis/internal/conjecture/data.py

+20-13
Original file line numberDiff line numberDiff line change
@@ -987,23 +987,30 @@ def trivial(self):
987987
return True
988988

989989
if self.ir_type == "integer":
990-
return self.value == self.kwargs["min_value"]
990+
shrink_towards = self.kwargs["shrink_towards"]
991+
min_value = self.kwargs["min_value"]
992+
max_value = self.kwargs["max_value"]
993+
994+
if min_value is not None:
995+
shrink_towards = max(min_value, shrink_towards)
996+
if max_value is not None:
997+
shrink_towards = min(max_value, shrink_towards)
998+
999+
return self.value == shrink_towards
9911000
if self.ir_type == "float":
992-
return float_to_int(self.value) == float_to_int(self.kwargs["min_value"])
1001+
# floats shrink "like integers" (for now, anyway), except shrink_towards
1002+
# is not configurable and is always 0.
1003+
shrink_towards = 0
1004+
shrink_towards = max(self.kwargs["min_value"], shrink_towards)
1005+
shrink_towards = min(self.kwargs["max_value"], shrink_towards)
1006+
1007+
return ir_value_equal("float", self.value, shrink_towards)
9931008
if self.ir_type == "boolean":
9941009
return self.value is False
9951010
if self.ir_type == "string":
996-
# we can do better here with "length is equal to min_size and value is
997-
# filled with simplest-in-shrink-order characters". This requires
998-
# computing what the simplest character in a given IntervalSet is,
999-
# and since we redefine the shrink order for characters, that requires
1000-
# computing the order for all characters in an IntervalSet. Which
1001-
# may be expensive for large intervals. (would @cached_property be
1002-
# good enough here?)
1003-
#
1004-
# This naive check is hopefully good enough for now? I'm trying to
1005-
# avoid doing more work than we save when computing `trivial`.
1006-
return self.value == ""
1011+
# smallest size and contains only the smallest-in-shrink-order character.
1012+
minimal_char = self.kwargs["intervals"].char_in_shrink_order(0)
1013+
return self.value == (minimal_char * self.kwargs["min_size"])
10071014
if self.ir_type == "bytes":
10081015
# smallest size and all-zero value.
10091016
return len(self.value) == self.kwargs["size"] and not any(self.value)

hypothesis-python/tests/conjecture/test_ir.py

+146
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from hypothesis.internal.floats import SMALLEST_SUBNORMAL, next_down, next_up
3131
from hypothesis.internal.intervalsets import IntervalSet
3232

33+
from tests.common.debug import minimal
3334
from tests.conjecture.common import fresh_data, ir_types_and_kwargs, kwargs_strategy
3435

3536

@@ -584,6 +585,17 @@ def test_forced_nodes_are_trivial(node):
584585
},
585586
was_forced=False,
586587
),
588+
IRNode(
589+
ir_type="float",
590+
value=0.0,
591+
kwargs={
592+
"min_value": -5.0,
593+
"max_value": 5.0,
594+
"allow_nan": True,
595+
"smallest_nonzero_magnitude": SMALLEST_SUBNORMAL,
596+
},
597+
was_forced=False,
598+
),
587599
IRNode(
588600
ir_type="boolean",
589601
value=False,
@@ -600,6 +612,16 @@ def test_forced_nodes_are_trivial(node):
600612
},
601613
was_forced=False,
602614
),
615+
IRNode(
616+
ir_type="string",
617+
value="aaaa",
618+
kwargs={
619+
"intervals": IntervalSet.from_string("bcda"),
620+
"min_size": 4,
621+
"max_size": None,
622+
},
623+
was_forced=False,
624+
),
603625
IRNode(
604626
ir_type="bytes",
605627
value=bytes(8),
@@ -617,7 +639,131 @@ def test_forced_nodes_are_trivial(node):
617639
},
618640
was_forced=False,
619641
),
642+
IRNode(
643+
ir_type="integer",
644+
value=0,
645+
kwargs={
646+
"min_value": -10,
647+
"max_value": 10,
648+
"weights": None,
649+
"shrink_towards": 0,
650+
},
651+
was_forced=False,
652+
),
653+
IRNode(
654+
ir_type="integer",
655+
value=2,
656+
kwargs={
657+
"min_value": -10,
658+
"max_value": 10,
659+
"weights": None,
660+
"shrink_towards": 2,
661+
},
662+
was_forced=False,
663+
),
664+
IRNode(
665+
ir_type="integer",
666+
value=-10,
667+
kwargs={
668+
"min_value": -10,
669+
"max_value": 10,
670+
"weights": None,
671+
"shrink_towards": -12,
672+
},
673+
was_forced=False,
674+
),
675+
IRNode(
676+
ir_type="integer",
677+
value=10,
678+
kwargs={
679+
"min_value": -10,
680+
"max_value": 10,
681+
"weights": None,
682+
"shrink_towards": 12,
683+
},
684+
was_forced=False,
685+
),
620686
],
621687
)
622688
def test_trivial_nodes(node):
623689
assert node.trivial
690+
691+
@st.composite
692+
def values(draw):
693+
data = draw(st.data()).conjecture_data
694+
return getattr(data, f"draw_{node.ir_type}")(**node.kwargs)
695+
696+
# if we're trivial, then shrinking should produce the same value.
697+
assert ir_value_equal(node.ir_type, minimal(values()), node.value)
698+
699+
700+
@pytest.mark.parametrize(
701+
"node",
702+
[
703+
IRNode(
704+
ir_type="float",
705+
value=6.0,
706+
kwargs={
707+
"min_value": 5.0,
708+
"max_value": 10.0,
709+
"allow_nan": True,
710+
"smallest_nonzero_magnitude": SMALLEST_SUBNORMAL,
711+
},
712+
was_forced=False,
713+
),
714+
IRNode(
715+
ir_type="float",
716+
value=-5.0,
717+
kwargs={
718+
"min_value": -5.0,
719+
"max_value": 5.0,
720+
"allow_nan": True,
721+
"smallest_nonzero_magnitude": SMALLEST_SUBNORMAL,
722+
},
723+
was_forced=False,
724+
),
725+
IRNode(
726+
ir_type="boolean",
727+
value=True,
728+
kwargs={"p": 0.5},
729+
was_forced=False,
730+
),
731+
IRNode(
732+
ir_type="string",
733+
value="d",
734+
kwargs={
735+
"intervals": IntervalSet.from_string("abcd"),
736+
"min_size": 1,
737+
"max_size": None,
738+
},
739+
was_forced=False,
740+
),
741+
IRNode(
742+
ir_type="bytes",
743+
value=b"\x01",
744+
kwargs={"size": 1},
745+
was_forced=False,
746+
),
747+
IRNode(
748+
ir_type="integer",
749+
value=-10,
750+
kwargs={
751+
"min_value": -10,
752+
"max_value": 10,
753+
"weights": None,
754+
"shrink_towards": 0,
755+
},
756+
was_forced=False,
757+
),
758+
],
759+
)
760+
def test_nontrivial_nodes(node):
761+
assert not node.trivial
762+
763+
@st.composite
764+
def values(draw):
765+
data = draw(st.data()).conjecture_data
766+
return getattr(data, f"draw_{node.ir_type}")(**node.kwargs)
767+
768+
# if we're nontrivial, then shrinking should produce something different.
769+
assert not ir_value_equal(node.ir_type, minimal(values()), node.value)

0 commit comments

Comments
 (0)