Skip to content

Commit

Permalink
handle bool/float cases correctly in choice_key
Browse files Browse the repository at this point in the history
  • Loading branch information
tybug committed Jan 15, 2025
1 parent 3356b67 commit 5af0549
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 6 deletions.
15 changes: 11 additions & 4 deletions hypothesis-python/src/hypothesis/internal/conjecture/choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class BooleanKWargs(TypedDict):
IntegerKWargs, FloatKWargs, StringKWargs, BytesKWargs, BooleanKWargs
]
ChoiceNameT: "TypeAlias" = Literal["integer", "string", "boolean", "float", "bytes"]
ChoiceKeyT: "TypeAlias" = Union[
int, str, bytes, tuple[Literal["bool"], bool], tuple[Literal["float"], int]
]


def _size_to_index(size: int, *, alphabet_size: int) -> int:
Expand Down Expand Up @@ -456,13 +459,17 @@ def choice_permitted(choice: ChoiceT, kwargs: ChoiceKwargsT) -> bool:
raise NotImplementedError(f"unhandled type {type(choice)} with value {choice}")


def choices_key(choices: Sequence[ChoiceT]) -> tuple[ChoiceT, ...]:
def choices_key(choices: Sequence[ChoiceT]) -> tuple[ChoiceKeyT, ...]:
return tuple(choice_key(choice) for choice in choices)


def choice_key(choice: ChoiceT) -> ChoiceT:
if type(choice) is float:
return float_to_int(choice)
def choice_key(choice: ChoiceT) -> ChoiceKeyT:
if isinstance(choice, float):
# distinguish -0.0/0.0, signaling/nonsignaling nans, etc.
return ("float", float_to_int(choice))
if isinstance(choice, bool):
# avoid choice_key(0) == choice_key(False)
return ("bool", choice)
return choice


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def __stoppable_test_function(self, data: ConjectureData) -> None:
# correct engine.
raise

def _cache_key(self, choices: Sequence[ChoiceT]) -> tuple[ChoiceT, ...]:
def _cache_key(self, choices: Sequence[ChoiceT]) -> tuple[ChoiceKeyT, ...]:
return choices_key(choices)

def _cache(self, data: ConjectureData) -> None:
Expand Down
24 changes: 24 additions & 0 deletions hypothesis-python/tests/conjecture/test_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
choice_from_index,
choice_permitted,
choice_to_index,
choices_key,
)
from hypothesis.internal.conjecture.data import (
COLLECTION_DEFAULT_MAX_SIZE,
Expand Down Expand Up @@ -884,3 +885,26 @@ def test_draw_directly_explicit():
)
== 10
)


@pytest.mark.parametrize(
"choices1, choices2",
[
[(True,), (1,)],
[(False,), (0,)],
[(0.0,), (-0.0,)],
],
)
def test_choices_key_distinguishes_weird_cases(choices1, choices2):
assert choices_key(choices1) != choices_key(choices2)


@given(st.lists(ir_nodes()), st.lists(ir_nodes()))
def test_choices_key_respects_inequality(nodes1, nodes2):
choices1 = [n.value for n in nodes1]
choices2 = [n.value for n in nodes2]
if choices_key(choices1) != choices_key(choices2):
assert set(choices1) != set(choices2)

# note that the other direction is not necessarily true: {False} == {0},
# but choices_key([False]) != choices_key([0]).
2 changes: 1 addition & 1 deletion hypothesis-python/tests/cover/test_database_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def test_background_write_database():
@example(ir("a"))
@example(ir(b"a"))
@example(ir(b"a" * 50))
def test_ir_nodes_rountrips(nodes1):
def test_ir_nodes_roundtrips(nodes1):
s1 = ir_to_bytes([n.value for n in nodes1])
assert isinstance(s1, bytes)
ir2 = ir_from_bytes(s1)
Expand Down

0 comments on commit 5af0549

Please sign in to comment.