def test_keeps_using_solid_passes_while_they_shrink_size():
    good = {
        hbytes([0, 1, 2, 3, 4, 5]),
        hbytes([0, 1, 2, 3, 5]),
        hbytes([0, 1, 3, 5]),
        hbytes([1, 3, 5]),
        hbytes([1, 5]),
    }
    initial = max(good, key=sort_key)

    @shrinking_from(initial)
    def shrinker(data):
        while True:
            data.draw_bits(8)
            if hbytes(data.buffer) in good:
                data.mark_interesting()
    shrinker.clear_passes()

    d1 = shrinker.add_new_pass(block_program('X'))
    d2 = shrinker.add_new_pass(block_program('-'))

    for _ in range(3):
        shrinker.single_greedy_shrink_iteration()
        assert d1.classification == PassClassification.HOPEFUL
        assert d2.classification == PassClassification.CANDIDATE
def test_block_deletion_can_delete_short_ranges(monkeypatch):
    @shrinking_from([
        v for i in range(5) for _ in range(i + 1) for v in [0, i]]
    )
    def shrinker(data):
        while True:
            n = data.draw_bits(16)
            for _ in range(n):
                if data.draw_bits(16) != n:
                    data.mark_invalid()
            if n == 4:
                data.mark_interesting()
    for i in range(1, 5):
        block_program('X' * i)(shrinker)
    assert list(shrinker.shrink_target.buffer) == [0, 4] * 5
def test_will_enable_previously_bad_passes_when_failing_to_shrink():
    # We lead the shrinker down the garden path a bit where it keeps making
    # progress but only lexically. When it finally gets down to the minimum
    good = {
        hbytes([1, 2, 3, 4, 5, 6]),
        hbytes([1, 2, 3, 4, 5, 5]),
        hbytes([1, 2, 2, 4, 5, 5]),
        hbytes([1, 2, 2, 4, 4, 5]),
        hbytes([0, 2, 2, 4, 4, 5]),
        hbytes([0, 1, 2, 4, 4, 5]),
    }

    initial = max(good)
    final = min(good)

    @shrinking_from(initial + hbytes([0, 7]))
    def shrinker(data):
        string = hbytes([data.draw_bits(8) for _ in range(6)])
        if string in good:
            n = 0
            while data.draw_bits(8) != 7:
                n += 1
            if not (string == final or n > 0):
                data.mark_invalid()
            data.mark_interesting()

    # In order to get to the minimized result we want to run both of these,
    # but the second pass starts out as disabled (and anyway won't work until
    # the first has hit fixity).
    shrinker.clear_passes()
    shrinker.add_new_pass(block_program('-'))
    shrinker.add_new_pass(block_program('X'))

    shrinker.shrink()

    assert shrinker.shrink_target.buffer == final + hbytes([7])
def test_passes_can_come_back_to_life():
    initial = hbytes([1, 2, 3, 4, 5, 6])
    buf1 = hbytes([0, 1, 3, 4, 5, 6])
    buf2 = hbytes([0, 1, 3, 4, 4, 6])

    good = {
        initial, buf1, buf2
    }

    @shrinking_from(initial)
    def shrinker(data):
        string = hbytes([data.draw_bits(8) for _ in range(6)])
        if string in good:
            data.mark_interesting()

    shrinker.clear_passes()
    shrinker.add_new_pass(block_program('--'))
    shrinker.add_new_pass(block_program('-'))

    shrinker.single_greedy_shrink_iteration()
    assert shrinker.shrink_target.buffer == buf1

    shrinker.single_greedy_shrink_iteration()
    assert shrinker.shrink_target.buffer == buf2