Skip to content

Commit

Permalink
Always write a 0 or 1 into the choice sequence when the last loop ite…
Browse files Browse the repository at this point in the history
…ration isn't already <= 1
  • Loading branch information
DRMacIver committed Jul 12, 2020
1 parent e305178 commit 8f752f0
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 5 deletions.
16 changes: 16 additions & 0 deletions hypothesis-python/src/hypothesis/internal/conjecture/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def combine_labels(*labels):

INTEGER_RANGE_DRAW_LABEL = calc_label_from_name("another draw in integer_range()")
BIASED_COIN_LABEL = calc_label_from_name("biased_coin()")
BIASED_COIN_INNER_LABEL = calc_label_from_name("inside biased_coin()")
SAMPLE_IN_SAMPLER_LABLE = calc_label_from_name("a sample() in Sampler")
ONE_FROM_MANY_LABEL = calc_label_from_name("one more from many()")

Expand Down Expand Up @@ -161,6 +162,10 @@ def biased_coin(data, p, *, forced=None):
will always return that value but will write choices appropriate to having
drawn that value randomly."""

# NB this function is vastly more complicated than it may seem reasonable
# for it to be. This is because it is used in a lot of places and it's
# important for it to shrink well, so it's worth the engineering effort.

if p <= 0 or p >= 1:
bits = 1
else:
Expand Down Expand Up @@ -218,7 +223,15 @@ def biased_coin(data, p, *, forced=None):
partial = True

if forced is None:
# We want to get to the point where True is represented by
# 1 and False is represented by 0 as quickly as possible, so
# we use the remove_discarded machinery in the shrinker to
# achieve that by discarding any draws that are > 1 and writing
# a suitable draw into the choice sequence at the end of the
# loop.
data.start_example(BIASED_COIN_INNER_LABEL)
i = data.draw_bits(bits)
data.stop_example(discard=i > 1)
else:
i = data.draw_bits(bits, forced=int(forced))

Expand Down Expand Up @@ -249,6 +262,9 @@ def biased_coin(data, p, *, forced=None):
# except for i = 1. We know i > 1 here, so the test for truth
# becomes i > falsey.
result = i > falsey

if i > 1:
data.draw_bits(bits, forced=int(result))
break
data.stop_example()
return result
Expand Down
10 changes: 5 additions & 5 deletions hypothesis-python/tests/conjecture/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,18 @@ def test_coin_biased_towards_truth():
p = 1 - 1.0 / 500

for i in range(1, 255):
assert cu.biased_coin(ConjectureData.for_buffer([0, i]), p)
assert cu.biased_coin(ConjectureData.for_buffer([0, i, 0, 0]), p)

assert not cu.biased_coin(ConjectureData.for_buffer([0, 0]), p)
assert not cu.biased_coin(ConjectureData.for_buffer([0, 0, 0, 1]), p)


def test_coin_biased_towards_falsehood():
p = 1.0 / 500

for i in range(255):
if i != 1:
assert not cu.biased_coin(ConjectureData.for_buffer([0, i]), p)
assert cu.biased_coin(ConjectureData.for_buffer([0, 1]), p)
assert not cu.biased_coin(ConjectureData.for_buffer([0, i, 0, 1]), p)
assert cu.biased_coin(ConjectureData.for_buffer([0, 1, 0, 0]), p)


def test_unbiased_coin_has_no_second_order():
Expand Down Expand Up @@ -106,7 +106,7 @@ def test_drawing_an_exact_fraction_coin():
for i in range(4):
for j in range(4):
total += 1
if cu.biased_coin(ConjectureData.for_buffer([i, j]), p):
if cu.biased_coin(ConjectureData.for_buffer([i, j, 0]), p):
count += 1
assert p == Fraction(count, total)

Expand Down

0 comments on commit 8f752f0

Please sign in to comment.